Spring Boot integracija JPA-e
Uvod u Spring Data JPA
Spring Data je okvir koje Spring pruža za rad sa podacima, a Spring Data JPA je modul u okviru Spring Data koji služi za rad sa podacima na osnovu standarda JPA.
JPA (Java Persistence API) je skup Java pravila za trajnost koji je definisao Sun, "roditelj" Jave. Pravila podrazumevaju da se definiše samo standard, bez konkretne implementacije.
JPA je prvenstveno predložen kako bi se objedinile postojeće ORM biblioteke na tržištu, kao što su Hibernate, EclipseLink itd. Zvanična strana je smatrala da je u redu praviti biblioteke, ali ne naprasito, već po standardu koji ona propisuje.
Spring Data JPA je samo apstrakcioni sloj: iznad njega je JPA, ispod ORM biblioteke. Pomoću interfejsa Respository baziranog na JPA-i drastično smanjuje količinu koda u kojoj JPA nastupa kao rešenje za pristup podacima, pojednostavljuje razvoj trajnog sloja i prikriva razlike između pojedinačnih ORM biblioteka.
Ukratko:
- JPA jeste standard; objedinjavanje standarda olakšava upotrebu.
- Hibernate jeste implementacija JPA-e, zreo ORM framework.
- Spring Data JPA je predložen od strane Spring-a i dodaje apstrakcioni sloj koji prikriva razlike između ORM biblioteka.
Spring Boot integracija sa Spring Data JPA
Prvi korak: u pom.xml dodajte starter zavisnost za JPA.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>Drugi korak: u application.yml dodajte podatke o konekciji na bazu.
spring:
datasource:
username: codingmore-mysql
password: dddd
url: jdbc:mysql://xxxx:3306/codingmore-mysql?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=falseTreći korak: napravite klasu entiteta User.java.
@Data
@Entity
@Table(name = "user")
public class User {
@Id
private Integer id;
private Integer age;
private String name;
private String password;
}- Anotacija
@Dataje iz Lombok-a i automatski klasi dodaje getter/setter metode. - Anotacije
@Entityi@Tablesu metapodaci uvedeni posle JDK 1.5, slede upitni jezik JPQL definisan u JPA specifikaciji, slično SQL sintaksi, namenjen Java klasama. @Entityukazuje da je klasa entitet, po podrazumevanim ORM pravilima: ime klase postaje ime tabele, a imena polja u klasi postaju imena kolona tabele.@Tablenije obavezna; njen prioritet je viši od@Entity— na primer ako istovremeno postoje@Entity(name="user")i@Table(name="users"), odgovarajuća tabela biće users.@Idoznačava da je to polje primarni ključ; kada je deklarisana anotacija @Entity, i@Idmora biti deklarisan.
Preporučujemo vam da u Intellij IDEA-u instalirate dodatak JPA Buddy; on pruža vizuelni generator koda koji može da pomogne u pojednostavljivanju razvoja sa JPA-om.

Nakon instalacije dodatka JPA Buddy, kada kreiramo klasu entiteta automatski se otvaraju tri panela: JPA Structure, JPA Palette i JPA Inspector.

Više detalja: https://codingdict.com/blog/1406
Pomoću dodatka JPA Buddy možemo zapravo direktno, desnim klikom na direktorijum projekta, izabrati da kreiramo klasu entiteta na JPA način.

Izaberite tabelu podataka.

Kod izgleda ovako:
@Entity
@Table(name = "user")
public class User {
@Id
@Column(name = "id", nullable = false)
private Integer id;
@Column(name = "name", nullable = false, length = 10)
private String name;
@Column(name = "password", nullable = false, length = 10)
private String password;
@Column(name = "age", nullable = false)
private Integer age;
// getter/setter izostavljeni
}Četvrti korak: kreirajte interfejs UserRepository.
Desni klik na putanju projekta i izaberite kreiranje JPA Repository.

Generisani kod:
public interface UserRepository extends JpaRepository<User, Integer> {
}Ako je potrebno samo jednostavno dodavanje, brisanje, izmena i čitanje nad tabelom, dovoljno je naslediti interfejs JpaRepository i proslediti dva parametra (prvi je klasa entiteta, drugi tip primarnog ključa).
Peti korak: kreirajte interfejs usluge UserService i klasu implementacije UserServiceImpl.
public interface UserService {
User findById(Integer id);
List<User> findAll();
User save(User user);
void delete(Integer id);
}UserService definiše 4 metoda:
- findById — prema ID-u čita jedan zapis
- findAll — čita sve
- save — služi za čuvanje i ažuriranje
- delete — služi za brisanje
@Service
public class UserServiceImpl implements UserService{
@Resource
private UserRepository userRepository;
@Override
public User findById(Integer id) {
return userRepository.getById(id);
}
@Override
public List<User> findAll() {
return userRepository.findAll();
}
@Override
public User save(User user) {
return userRepository.save(user);
}
@Override
public void delete(Integer id) {
userRepository.deleteById(id);
}
}- Anotacija
@Servicekoristi se u sloju usluga, sa istom svrhom kao i@Component(opšta anotacija); Spring Boot automatski skenira klase koje nose ovu anotaciju i dodaje ih u Spring kontejner. - Anotacije
@Resourcei@Autowiredobe služe za automatsko povezivanje objekata; mogu se koristiti nad poljima ili nad setter metodama. @Autowired je anotacija koju pruža Spring, dok @Resource pruža Java — drugim rečima, ako projekat ne koristi Spring framework već JFinal, @Resource je i dalje podržan. Pored toga, @Resource povezuje po imenu (byName), a @Autowired po tipu (byType); kada postoje dva potpuno ista objekta, @Autowired će prijaviti grešku.
Su San je napisao članak o @Autowired koji je veoma dobar: https://www.zhihu.com/question/39356740
Naravno, obično dodavanje, brisanje, izmena i čitanje nam više ne pobuđuje interesovanje za učenjem, pa moramo dodati nešto drugačije; zato ćemo u interfejs UserService dodati i interfejs za straničenje.
Page<User> findAll(Pageable pageable);Klasa implementacije:
@Override
public Page<User> findAll(Pageable pageable) {
return userRepository.findAll(pageable);
}- Pageable je interfejs za straničene upite koji pruža Spring; prilikom upita dovoljno je proslediti klasu implementaciju interfejsa Pageable i zadati broj stranice (pageNumber) i veličinu stranice (pageSize).
- Page je interfejs povratnog rezultata straničenja koji pruža Spring.
Dodajmo i interfejs prilagođenog upita (učna pretraga prema imenu).
Najpre UserRepository, direktno preko dodatka JPA Buddy:

Generisani kod:
public interface UserRepository extends JpaRepository<User, Integer> {
List<User> findByNameLikeIgnoreCase(String name);
}Zatim interfejs UserService:
List<User> findByNameLikeIgnoreCase(String name);I konačno UserServiceImpl:
@Override
public List<User> findByNameLikeIgnoreCase(String name) {
return userRepository.findByNameLikeIgnoreCase(name);
}Testiranje Spring Data JPA
U klasi za testove testiraćemo 5 metoda usluge, a usput ćemo u application.yml uključiti ispis SQL naredbi, kako bismo videli kako izgleda SQL koji JPA automatski generiše.

Klasa za testove je prilično jednostavna:
@SpringBootTest
@Slf4j
class CodingmoreJpaApplicationTests {
@Resource
private UserService userService;
@Test
void contextLoads() {
// čita sve
userService.findAll().stream().forEach(user -> log.info("čita sve {}", user));
// dodavanje podataka
userService.save(new User().setId(2).setAge(12).setName("Chenmo Wang San").setPassword("123456"));
userService.save(new User().setId(3).setAge(12).setName("Chenmo Wang Si").setPassword("123456"));
userService.save(new User().setId(4).setAge(12).setName("Chenmo Wang Wu").setPassword("123456"));
// straničeni upit
userService.findAll(PageRequest.of(1,2)).stream().forEach(user -> log.info("straničeni upit {}", user));
// učni upit
log.info("učni upit {}",userService.findByNameLike("Chenmo"));
// brisanje
userService.delete(1);
}
}Iz loga se vidi da Spring Data JPA podrazumevano koristi Hibernate framework; evo upita za čitanje svega:
Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.name as name3_0_, user0_.password as password4_0_ from user user0_Evo dodavanja:
Hibernate: insert into user (age, name, password, id) values (?, ?, ?, ?)Evo ažuriranja:
Hibernate: update user set age=?, name=?, password=? where id=?Evo straničenja:
Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.name as name3_0_, user0_.password as password4_0_ from user user0_ limit ?, ?Evo učnog upita:
Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.name as name3_0_, user0_.password as password4_0_ from user user0_ where user0_.name like ? escape ?Međutim, ovaj učni upit ne odgovara našim očekivanjima — nema % na početku i kraju. Možemo izabrati @Query koji pruža Spring Data i sami prilagoditi SQL naredbu.
Podrazumevano, anotacija @Query koristi JPQL za upite. Na primer:
@Query("select u from User u where u.name like concat('%',?1,'%')")
List<User> findByNameLike(String name);"User" u anotaciji @Query jeste ime klase entiteta, a ne ime tabele user u bazi; to je razlika između JPQL i nativnog SQL-a. Pogledajte kako izgleda nativni SQL:
@Query(value = "SELECT * FROM user u WHERE u.name like '%'|| ?1 || '%'",
nativeQuery = true)
List<User> findByNameLikeNativeQuery(String name);"user" u anotaciji @Query jeste ime tabele u bazi; uz to je potrebno dodati parametar nativeQuery čija je podrazumevana vrednost false — postavljanje na true ukazuje na uključivanje nativnog SQL upita. Ovaj put umesto "concat" koristimo "||".
Pored prosleđivanja parametara preko oblika ?1, ?2, parametar možemo proslediti i pomoću anotacije @Param, pri čemu se u SQL naredbi za prihvatanje parametra koristi oblik :param.
@Query("select u from User u where u.name like concat('%',:name,'%')")
List<User> findByNameLikeParam(@Param("name")String name);Evo brisanja:
Hibernate: delete from user where id=?Poređenje Spring Data JPA i MyBatis-a
Adresa izvornog koda
- codingmore: https://github.com/itwanger/coding-more
- Spring Boot integracija JPA-e: https://github.com/itwanger/codingmore-learning/tree/main/codingmore-jpa
