您好,登錄后才能下訂單哦!
JPA 這部分內容上手很容易,但是涉及到的東西還是挺多的,網上大部分關于 JPA 的資料都不是特別齊全,大部分用的版本也是比較落后的。另外,我下面講到了的內容也不可能涵蓋所有 JPA 相關內容,我只是把自己覺得比較重要的知識點總結在了下面。很多地方我自己也是參考著官方文檔寫的,官方文檔非常詳細了,非常推薦閱讀一下。這篇文章可以幫助對 JPA 不了解或者不太熟悉的人來在實際項目中正確使用 JPA。
另外,我發現網上關于連表查詢這一塊并沒有太多比較有參考價值的博客,所以對這部分也做了詳細的總結,以供大家學習參考
項目代碼基于 Spring Boot 最新的 2.1.9.RELEASE 版本構建(截止到這篇文章寫完),另外,新建項目的過程就不多說了。
1.相關依賴
我們需要下面這些依賴支持我們完成這部分內容的學習:
?<dependencies> ?<dependency> ?<groupId>org.springframework.boot</groupId> ?<artifactId>spring-boot-starter-web</artifactId> ?</dependency> ?<dependency> ?<groupId>org.springframework.boot</groupId> ?<artifactId>spring-boot-starter-data-jpa</artifactId> ?</dependency> ?<dependency> ?<groupId>mysql</groupId> ?<artifactId>mysql-connector-java</artifactId> ?<scope>runtime</scope> ?</dependency> ?<dependency> ?<groupId>org.projectlombok</groupId> ?<artifactId>lombok</artifactId> ?<optional>true</optional> ?</dependency> ?<dependency> ?<groupId>org.springframework.boot</groupId> ?<artifactId>spring-boot-starter-test</artifactId> ?<scope>test</scope> ?</dependency> ?</dependencies>
2.配置數據庫連接信息和JPA配置
下面的配置中需要單獨說一下 spring.jpa.hibernate.ddl-auto=create這個配置選項。
這個屬性常用的選項有四種:
create:每次重新啟動項目都會重新創新表結構,會導致數據丟失
create-drop:每次啟動項目創建表結構,關閉項目刪除表結構
update:每次啟動項目會更新表結構
validate:驗證表結構,不對數據庫進行任何更改
但是,一定要不要在生產環境使用 ddl 自動生成表結構,一般推薦手寫 SQL 語句配合 Flyway 來做這些事情。
spring.datasource.url=jdbc:mysql://localhost:3306/springboot_jpa?useSSL=false&serverTimezone=CTT spring.datasource.username=root spring.datasource.password=123456 #?打印出?sql?語句 spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=create spring.jpa.open-in-view=false #?創建的表的?ENGINE?為?InnoDB spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL55Dialect
3.實體類
我們為這個類添加了 @Entity 注解代表它是數據庫持久化類,還配置了主鍵 id。
import?lombok.Data; import?lombok.NoArgsConstructor; import?javax.persistence.Column; import?javax.persistence.Entity; import?javax.persistence.GeneratedValue; import?javax.persistence.GenerationType; import?javax.persistence.Id; @Entity @Data @NoArgsConstructor public?class?Person?{ ? ?@Id ?@GeneratedValue(strategy?=?GenerationType.IDENTITY) ?private?Long?id; ?@Column(unique?=?true) ?private?String?name; ?private?Integer?age; ?public?Person(String?name,?Integer?age)?{ ?this.name?=?name; ?this.age?=?age; ?} }
如何檢驗你是否正確完成了上面 3 步?很簡單,運行項目,查看數據如果發現控制臺打印出創建表的 sql 語句,并且數據庫中表真的被創建出來的話,說明你成功完成前面 3 步。
控制臺打印出來的 sql 語句類似下面這樣:
drop?table?if?exists?person CREATE?TABLE?`person`?( ?`id`?bigint(20)?NOT?NULL?AUTO_INCREMENT, ?`age`?int(11)?DEFAULT?NULL, ?`name`?varchar(255)?DEFAULT?NULL, ?PRIMARY?KEY?(`id`) )?ENGINE=InnoDB?DEFAULT?CHARSET=utf8; alter?table?person?add?constraint?UK_p0wr4vfyr2lyifm8avi67mqw5?unique?(name)
4.創建操作數據庫的 Repository 接口
@Repository public?interface?PersonRepository?extends?JpaRepository<Person,?Long>?{ }
首先這個接口加了 @Repository 注解,代表它和數據庫操作有關。另外,它繼承了 JpaRepository<Person, Long>接口,而JpaRepository<Person, Long>長這樣:
@NoRepositoryBean public?interface?JpaRepository<T,?ID>?extends?PagingAndSortingRepository<T,?ID>,?QueryByExampleExecutor<T>?{ ?List<T>?findAll(); ?List<T>?findAll(Sort?var1); ?List<T>?findAllById(Iterable<ID>?var1); ?<S?extends?T>?List<S>?saveAll(Iterable<S>?var1); ?void?flush(); ?<S?extends?T>?S?saveAndFlush(S?var1); ?void?deleteInBatch(Iterable<T>?var1); ?void?deleteAllInBatch(); ?T?getOne(ID?var1); ?<S?extends?T>?List<S>?findAll(Example<S>?var1); ?<S?extends?T>?List<S>?findAll(Example<S>?var1,?Sort?var2); }
這表明我們只要繼承了JpaRepository<T, ID> 就具有了 JPA 為我們提供好的增刪改查、分頁查詢以及根據條件查詢等方法。
4.1 JPA 自帶方法實戰
1) 增刪改查
1.保存用戶到數據庫
?Person?person?=?new?Person("SnailClimb",?23); ?personRepository.save(person);
save()方法對應 sql 語句就是:insert into person (age, name) values (23,"snailclimb")
2.根據 id 查找用戶
?Optional<Person>?personOptional?=?personRepository.findById(id);
findById()方法對應 sql 語句就是:select * from person p where p.id = id
3.根據 id 刪除用戶
?personRepository.deleteById(id);
deleteById()方法對應 sql 語句就是:delete from person where id=id
4.更新用戶
更新操作也要通過 save()方法來實現,比如:
?Person?person?=?new?Person("SnailClimb",?23); ?Person?savedPerson?=?personRepository.save(person); ?//?更新?person?對象的姓名 ?savedPerson.setName("UpdatedName"); ?personRepository.save(savedPerson);
在這里 save()方法相當于 sql 語句:update person set name="UpdatedName" where id=id
2) 帶條件的查詢
下面這些方法是我們根據 JPA 提供的語法自定義的,你需要將下面這些方法寫到PersonRepository 中。
假如我們想要根據 Name 來查找 Person ,你可以這樣:
?Optional<Person>?findByName(String?name);
如果你想要找到年齡大于某個值的人,你可以這樣:
?List<Person>?findByAgeGreaterThan(int?age);
4.2 自定義 SQL 語句實戰
很多時候我們自定義 sql 語句會非常有用。
根據 name 來查找 Person:
?@Query("select?p?from?Person?p?where?p.name?=?:name") ?Optional<Person>?findByNameCustomeQuery(@Param("name")?String?name);
Person 部分屬性查詢,避免 select *操作:
?@Query("select?p.name?from?Person?p?where?p.id?=?:id") ?String?findPersonNameById(@Param("id")?Long?id);
根據 id 更新Person name:
?@Modifying ?@Transactional ?@Query("update?Person?p?set?p.name?=??1?where?p.id?=??2") ?void?updatePersonNameById(String?name,?Long?id);
4.3 創建異步方法
如果我們需要創建異步方法的話,也比較方便。
異步方法在調用時立即返回,然后會被提交給TaskExecutor執行。當然你也可以選擇得出結果后才返回給客戶端。如果對 Spring Boot 異步編程感興趣的話可以看這篇文章:《新手也能看懂的 SpringBoot 異步編程指南》 。
@Async Future<User>?findByName(String?name); @Async CompletableFuture<User>?findByName(String?name);
5.測試類和源代碼地址
測試類:
@SpringBootTest @RunWith(SpringRunner.class) public?class?PersonRepositoryTest?{ ?@Autowired ?private?PersonRepository?personRepository; ?private?Long?id; ?/** ?*?保存person到數據庫 ?*/ ?@Before ?public?void?setUp()?{ ?assertNotNull(personRepository); ?Person?person?=?new?Person("SnailClimb",?23); ?Person?savedPerson?=?personRepository.saveAndFlush(person);//?更新?person?對象的姓名 ?savedPerson.setName("UpdatedName"); ?personRepository.save(savedPerson); ?id?=?savedPerson.getId(); ?} ?/** ?*?使用?JPA?自帶的方法查找?person ?*/ ?@Test ?public?void?should_get_person()?{ ?Optional<Person>?personOptional?=?personRepository.findById(id); ?assertTrue(personOptional.isPresent()); ?assertEquals("SnailClimb",?personOptional.get().getName()); ?assertEquals(Integer.valueOf(23),?personOptional.get().getAge()); ?List<Person>?personList?=?personRepository.findByAgeGreaterThan(18); ?assertEquals(1,?personList.size()); ?//?清空數據庫 ?personRepository.deleteAll(); ?} ?/** ?*?自定義?query?sql?查詢語句查找?person ?*/ ?@Test ?public?void?should_get_person_use_custom_query()?{ ?//?查找所有字段 ?Optional<Person>?personOptional?=?personRepository.findByNameCustomeQuery("SnailClimb"); ?assertTrue(personOptional.isPresent()); ?assertEquals(Integer.valueOf(23),?personOptional.get().getAge()); ?//?查找部分字段 ?String?personName?=?personRepository.findPersonNameById(id); ?assertEquals("SnailClimb",?personName); ?System.out.println(id); ?//?更新 ?personRepository.updatePersonNameById("UpdatedName",?id); ?Optional<Person>?updatedName?=?personRepository.findByNameCustomeQuery("UpdatedName"); ?assertTrue(updatedName.isPresent()); ?//?清空數據庫 ?personRepository.deleteAll(); ?} }
源代碼地址:https://github.com/Snailclimb/springboot-guide/tree/master/source-code/basis/jpa-demo
6. 總結
本文主要介紹了 JPA 的基本用法:
使用 JPA 自帶的方法進行增刪改查以及條件查詢。
自定義 SQL 語句進行查詢或者更新數據庫。
創建異步的方法。
在下一篇關于 JPA 的文章中我會介紹到非常重要的兩個知識點:
基本分頁功能實現
多表聯合查詢以及多表聯合查詢下的分頁功能實現。
對于連表查詢,在 JPA 中還是非常常見的,由于 JPA 可以在 respository 層自定義 SQL 語句,所以通過自定義 SQL 語句的方式實現連表還是挺簡單。這篇文章是在上一篇入門 JPA的文章的基礎上寫的,不了解 JPA 的可以先看上一篇文章。
在上一節的基礎上我們新建了兩個實體類,如下:
1.相關實體類創建
Company.java
@Entity @Data @NoArgsConstructor public?class?Company?{ ?@Id ?@GeneratedValue(strategy?=?GenerationType.IDENTITY) ?private?Long?id; ?@Column(unique?=?true) ?private?String?companyName; ?private?String?description; ?public?Company(String?name,?String?description)?{ ?this.companyName?=?name; ?this.description?=?description; ?} }
School.java
@Entity @Data @NoArgsConstructor @AllArgsConstructor public?class?School?{ ?@Id ?@GeneratedValue(strategy?=?GenerationType.IDENTITY) ?private?Long?id; ?@Column(unique?=?true) ?private?String?name; ?private?String?description; }
2.自定義 SQL語句實現連表查詢
假如我們當前要通過 person 表的 id 來查詢 Person 的話,我們知道 Person 的信息一共分布在Company、School、Person這三張表中,所以,我們如果要把 Person 的信息都查詢出來的話是需要進行連表查詢的。
首先我們需要創建一個包含我們需要的 Person 信息的 DTO 對象,我們簡單第將其命名為 UserDTO,用于保存和傳輸我們想要的信息。
@Data @NoArgsConstructor @Builder(toBuilder?=?true) @AllArgsConstructor public?class?UserDTO?{ ?private?String?name; ?private?int?age; ?private?String?companyName; ?private?String?schoolName; }
下面我們就來寫一個方法查詢出 Person 的基本信息。
?/** ?*?連表查詢 ?*/ ?@Query(value?=?"select?new?github.snailclimb.jpademo.model.dto.UserDTO(p.name,p.age,c.companyName,s.name)?"?+ ?"from?Person?p?left?join?Company?c?on?p.companyId=c.id?"?+ ?"left?join?School?s?on?p.schoolId=s.id?"?+ ?"where?p.id=:personId") ?Optional<UserDTO>?getUserInformation(@Param("personId")?Long?personId);
可以看出上面的 sql 語句和我們平時寫的沒啥區別,差別比較大的就是里面有一個 new 對象的操作。
3.自定義 SQL 語句連表查詢并實現分頁操作
假如我們要查詢當前所有的人員信息并實現分頁的話,你可以按照下面這種方式來做。可以看到,為了實現分頁,我們在@Query注解中還添加了?countQuery?屬性。
@Query(value?=?"select?new?github.snailclimb.jpademo.model.dto.UserDTO(p.name,p.age,c.companyName,s.name)?"?+ ?"from?Person?p?left?join?Company?c?on?p.companyId=c.id?"?+ ?"left?join?School?s?on?p.schoolId=s.id?", ?countQuery?=?"select?count(p.id)?"?+ ?"from?Person?p?left?join?Company?c?on?p.companyId=c.id?"?+ ?"left?join?School?s?on?p.schoolId=s.id?") Page<UserDTO>?getUserInformationList(Pageable?pageable);
實際使用:
//分頁選項 PageRequest?pageRequest?=?PageRequest.of(0,?3,?Sort.Direction.DESC,?"age"); Page<UserDTO>?userInformationList?=?personRepository.getUserInformationList(pageRequest); //查詢結果總數 System.out.println(userInformationList.getTotalElements());//?6 //按照當前分頁大小,總頁數 System.out.println(userInformationList.getTotalPages());//?2 System.out.println(userInformationList.getContent());
4.加餐:自定以SQL語句的其他用法
下面我只介紹兩種比較常用的:
IN 查詢
BETWEEN 查詢
當然,還有很多用法需要大家自己去實踐了。
4.1 IN 查詢
在 sql 語句中加入我們需要篩選出符合幾個條件中的一個的情況下,可以使用 IN 查詢,對應到 JPA 中也非常簡單。比如下面的方法就實現了,根據名字過濾需要的人員信息。
@Query(value?=?"select?new?github.snailclimb.jpademo.model.dto.UserDTO(p.name,p.age,c.companyName,s.name)?"?+ ?"from?Person?p?left?join?Company?c?on?p.companyId=c.id?"?+ ?"left?join?School?s?on?p.schoolId=s.id?"?+ ?"where?p.name?IN?:peopleList") List<UserDTO>?filterUserInfo(List?peopleList);
實際使用:
List<String>?personList=new?ArrayList<>(Arrays.asList("person1","person2")); List<UserDTO>?userDTOS?=?personRepository.filterUserInfo(personList);
4.2 BETWEEN 查詢
查詢滿足某個范圍的值。比如下面的方法就實現查詢滿足某個年齡范圍的人員的信息。
?@Query(value?=?"select?new?github.snailclimb.jpademo.model.dto.UserDTO(p.name,p.age,c.companyName,s.name)?"?+ ?"from?Person?p?left?join?Company?c?on?p.companyId=c.id?"?+ ?"left?join?School?s?on?p.schoolId=s.id?"?+ ?"where?p.age?between?:small?and?:big") ?List<UserDTO>?filterUserInfoByAge(int?small,int?big);
實際使用:
List<UserDTO>?userDTOS?=?personRepository.filterUserInfoByAge(19,20);
5.測試類和源代碼地址
@SpringBootTest @RunWith(SpringRunner.class) public?class?PersonRepositoryTest2?{ ?@Autowired ?private?PersonRepository?personRepository; ?@Sql(scripts?=?{"classpath:/init.sql"}) ?@Test ?public?void?find_person_age_older_than_18()?{ ?List<Person>?personList?=?personRepository.findByAgeGreaterThan(18); ?assertEquals(1,?personList.size()); ?} ?@Sql(scripts?=?{"classpath:/init.sql"}) ?@Test ?public?void?should_get_user_info()?{ ?Optional<UserDTO>?userInformation?=?personRepository.getUserInformation(1L); ?System.out.println(userInformation.get().toString()); ?} ?@Sql(scripts?=?{"classpath:/init.sql"}) ?@Test ?public?void?should_get_user_info_list()?{ ?PageRequest?pageRequest?=?PageRequest.of(0,?3,?Sort.Direction.DESC,?"age"); ?Page<UserDTO>?userInformationList?=?personRepository.getUserInformationList(pageRequest); ?//查詢結果總數 ?System.out.println(userInformationList.getTotalElements());//?6 ?//按照當前分頁大小,總頁數 ?System.out.println(userInformationList.getTotalPages());//?2 ?System.out.println(userInformationList.getContent()); ?} ?@Sql(scripts?=?{"classpath:/init.sql"}) ?@Test ?public?void?should_filter_user_info()?{ ?List<String>?personList=new?ArrayList<>(Arrays.asList("person1","person2")); ?List<UserDTO>?userDTOS?=?personRepository.filterUserInfo(personList); ?System.out.println(userDTOS); ?} ?@Sql(scripts?=?{"classpath:/init.sql"}) ?@Test ?public?void?should_filter_user_info_by_age()?{ ?List<UserDTO>?userDTOS?=?personRepository.filterUserInfoByAge(19,20); ?System.out.println(userDTOS); ?} }
六 總結
本節我們主要學習了下面幾個知識點:
自定義 SQL 語句實現連表查詢;
自定義 SQL 語句連表查詢并實現分頁操作;
條件查詢:IN 查詢,BETWEEN查詢。
我們這一節是把 SQl 語句連表查詢的邏輯放在 Dao 層直接寫的,這樣寫的好處是比較方便,也比較簡單明了。但是可能會不太好維護,很多時候我們會選擇將這些邏輯放到 Service 層去做,這樣也是可以實現的,后面章我就會介紹到如何將這些寫在 Dao 層的邏輯轉移到 Service 層去。
讀者福利
加微信:haolagui521備注51CTO領取附送學習進階架構資料、PDF書籍文檔、面試資料
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。