溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

一文搞懂如何在Spring Boot 正確中使用JPA

發(fā)布時間:2020-06-28 17:44:51 來源:網絡 閱讀:417 作者:wx5d30212829a35 欄目:編程語言

JPA 這部分內容上手很容易,但是涉及到的東西還是挺多的,網上大部分關于 JPA 的資料都不是特別齊全,大部分用的版本也是比較落后的。另外,我下面講到了的內容也不可能涵蓋所有 JPA 相關內容,我只是把自己覺得比較重要的知識點總結在了下面。很多地方我自己也是參考著官方文檔寫的,官方文檔非常詳細了,非常推薦閱讀一下。這篇文章可以幫助對 JPA 不了解或者不太熟悉的人來在實際項目中正確使用 JPA。

另外,我發(fā)現網上關于連表查詢這一塊并沒有太多比較有參考價值的博客,所以對這部分也做了詳細的總結,以供大家學習參考

項目代碼基于 Spring Boot 最新的 2.1.9.RELEASE 版本構建(截止到這篇文章寫完),另外,新建項目的過程就不多說了。

一 JPA 基礎:常見操作

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這個配置選項。

這個屬性常用的選項有四種:

  1. create:每次重新啟動項目都會重新創(chuàng)新表結構,會導致數據丟失

  2. create-drop:每次啟動項目創(chuàng)建表結構,關閉項目刪除表結構

  3. update:每次啟動項目會更新表結構

  4. validate:驗證表結構,不對數據庫進行任何更改

但是,一定要不要在生產環(huán)境使用 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
#?創(chuàng)建的表的?ENGINE?為?InnoDB
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL55Dialect

3.實體類

我們?yōu)檫@個類添加了 @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 步?很簡單,運行項目,查看數據如果發(fā)現控制臺打印出創(chuàng)建表的 sql 語句,并且數據庫中表真的被創(chuàng)建出來的話,說明你成功完成前面 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.創(chuàng)建操作數據庫的 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 自帶方法實戰(zhàn)

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 語句實戰(zhàn)

很多時候我們自定義 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 創(chuàng)建異步方法

如果我們需要創(chuàng)建異步方法的話,也比較方便。

異步方法在調用時立即返回,然后會被提交給TaskExecutor執(zhí)行。當然你也可以選擇得出結果后才返回給客戶端。如果對 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 的基本用法:

  1. 使用 JPA 自帶的方法進行增刪改查以及條件查詢。

  2. 自定義 SQL 語句進行查詢或者更新數據庫。

  3. 創(chuàng)建異步的方法。

在下一篇關于 JPA 的文章中我會介紹到非常重要的兩個知識點:

  1. 基本分頁功能實現

  2. 多表聯(lián)合查詢以及多表聯(lián)合查詢下的分頁功能實現。

二 JPA 連表查詢和分頁

對于連表查詢,在 JPA 中還是非常常見的,由于 JPA 可以在 respository 層自定義 SQL 語句,所以通過自定義 SQL 語句的方式實現連表還是挺簡單。這篇文章是在上一篇入門 JPA的文章的基礎上寫的,不了解 JPA 的可以先看上一篇文章。

在上一節(jié)的基礎上我們新建了兩個實體類,如下:

1.相關實體類創(chuàng)建

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 的信息都查詢出來的話是需要進行連表查詢的。

首先我們需要創(chuàng)建一個包含我們需要的 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 語句和我們平時寫的沒啥區(qū)別,差別比較大的就是里面有一個 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語句的其他用法

下面我只介紹兩種比較常用的:

  1. IN 查詢

  2. 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);
?}
}

六 總結

本節(jié)我們主要學習了下面幾個知識點:

  1. 自定義 SQL 語句實現連表查詢;

  2. 自定義 SQL 語句連表查詢并實現分頁操作;

  3. 條件查詢:IN 查詢,BETWEEN查詢。

我們這一節(jié)是把 SQl 語句連表查詢的邏輯放在 Dao 層直接寫的,這樣寫的好處是比較方便,也比較簡單明了。但是可能會不太好維護,很多時候我們會選擇將這些邏輯放到 Service 層去做,這樣也是可以實現的,后面章我就會介紹到如何將這些寫在 Dao 層的邏輯轉移到 Service 層去。


讀者福利

加微信:haolagui521備注51CTO領取附送學習進階架構資料、PDF書籍文檔、面試資料

一文搞懂如何在Spring Boot 正確中使用JPA

一文搞懂如何在Spring Boot 正確中使用JPA


向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI