一、Spring Data JPA 1、簡介 (1)官網地址: https://spring.io/projects/spring-data-jpa參考文檔: https://docs.spring.io/spring-data/jpa/docs/2.2.3.RELEASE/reference/ht ...
一、Spring Data JPA
1、簡介
(1)官網地址:
https://spring.io/projects/spring-data-jpa
參考文檔:
https://docs.spring.io/spring-data/jpa/docs/2.2.3.RELEASE/reference/html/#preface
(2)基本介紹:
Spring Data JPA 是 Spring 基於 ORM 框架、JPA 規範封裝的一套 JPA 框架。使開發者通過極簡的代碼實現對資料庫的訪問和操作。
註:
ORM 框架:指的是 Object Relational Mapping,即對象關係映射。採用元數據來描述對象和關係映射的細節。
元數據:一般採用 XML 文件的形式。常見 ORM 框架如:mybatis、Hibernate。
JPA:指的是 Java Persistence API,即 Java 持久層 API。通過 xml 或註解的映射關係將運行期的實體對象持久化到資料庫中。
2、sping boot 項目中使用
(1)在 pom.xml 文件中引入依賴
【pom.xml】 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
(2)在 application.properties 中配置
【application.properties】
# jpa 配置
# 配置資料庫為 mysql
spring.jpa.database=mysql
# 在控制台列印 sql 語句
spring.jpa.show-sql=true
# 每次運行程式,沒有表格會新建表格,表內有數據不會清空,只會更新
spring.jpa.hibernate.ddl-auto=update
# 每次運行該程式,沒有表格會新建表格,表內有數據會清空
#spring.jpa.hibernate.ddl-auto=create
二、基本註解
1、@Entity
@Entity 寫在類上,用於指明一個類與資料庫表相對應。 屬性: name,可選,用於自定義映射的表名。若沒有,則預設以類名為表名。 【舉例1:預設類名為表名】 import javax.persistence.Entity; @Entity public class Blog { } 【舉例2:自定義表名】 import javax.persistence.Entity; @Entity(name="t_blog") public class Blog { }
2、@Table
@Table 寫在類上,一般與 @Entity 連用,用於指定數據表的相關信息。 屬性: name, 對應數據表名。 catalog, 可選,對應關係資料庫中的catalog。 schema,可選,對應關係資料庫中的schema。 【舉例:】 import javax.persistence.Entity; import javax.persistence.Table; @Entity(name = "blog") @Table(name = "t_blog") public class Blog { } 註:若 @Entity 與 @Table 同時定義了 name 屬性,那以 @Table 為主。
3、@Id、@GeneratedValue
@Id 寫在類中的變數上,用於指定當前變數為主鍵 Id。一般與 @GeneratedValue 連用。 @GeneratedValue 與 @Id 連用,用於設置主鍵生成策略(自增主鍵,依賴資料庫)。 註: @GeneratedValue(strategy = GenerationType.AUTO) 主鍵增長方式由資料庫自動選擇,當數據 庫選擇AUTO方式時就會自動生成hibernate_sequence表。 @GeneratedValue(strategy = GenerationType.IDENTITY) 要求資料庫選擇自增方式,oracle不 支持此種方式,mysql支持。 @GeneratedValue(strategy = GenerationType.SEQUENCE) 採用資料庫提供的sequence機制生 成主鍵,mysql不支持。 【舉例:】 package com.lyh.blog.bean; import lombok.Data; import javax.persistence.*; @Entity @Table(name = "t_blog") @Data public class Blog { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; }
4、@Column
@Column 寫在類的變數上,用於指定當前變數映射到數據表中的列的屬性(列名,是否唯一,是否允許為空,是否允許更新等)。 屬性: name: 列名。 unique: 是否唯一 nullable: 是否允許為空 insertable: 是否允許插入 updatable: 是否允許更新 length: 定義長度 【舉例:】 import lombok.Data; import javax.persistence.*; @Entity @Table(name = "t_blog") @Data public class Blog { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(name = "name", length = 36, unique = false, nullable = false, insertable = true, updatable = true) private String name; }
5、@Temporal
@Temporal 用於將 java.util 下的時間日期類型轉換 並存於資料庫中(日期、時間、時間戳)。 屬性: TemporalType.DATE java.sql.Date日期型,精確到年月日,例如“2019-12-17” TemporalType.TIME java.sql.Time時間型,精確到時分秒,例如“2019-12-17 00:00:00” TemporalType.TIMESTAMP java.sql.Timestamp時間戳,精確到納秒,例如“2019-12-17 00:00:00.000000001” 【舉例:】 package com.lyh.blog.bean; import lombok.Data; import javax.persistence.*; import java.util.Date; @Entity @Table(name = "t_blog") @Data public class Blog { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(name = "name", length = 36, unique = false, nullable = false, insertable = true, updatable = true) private String name; @Temporal(TemporalType.TIMESTAMP) private Date createTime; @Temporal(TemporalType.DATE) private Date updateTime; }
6、級聯(cascade)
對於 @OneToOne、@ManyToMany、@OneToMany等映射關係,涉及到級聯的操作。 CascadeType[] cascade() default {}; 定義級聯用於 給當前設置的實體 操作 另一個關聯的實體的許可權。 【級聯的類型:】 package javax.persistence; public enum CascadeType { ALL, PERSIST, MERGE, REMOVE, REFRESH, DETACH; private CascadeType() { } } CascadeType.ALL 擁有所有級聯操作的許可權。 CascadeType.PERSIST 當前實體類進行保存操作時,同時保存其關聯的實體。 CascadeType.MERGE 當前實體數據合併時,會影響其關聯的實體。 CascadeType.REMOVE 刪除當前實體,與其相關聯的實體也會被刪除。 CascadeType.REFRESH 刷新當前實體,與其相關聯的實體也會被刷新。 CascadeType.DETACH 去除外鍵關聯,當刪一個實體時,存在外鍵無法刪除,使用此級聯可以去除外鍵。
7、mappedby
只有 @OneToOne, @OneToMany, @ManyToMany上才有 mappedBy 屬性,@ManyToOne不存在該屬性。 該屬性的作用: 設置關聯關係。單向關聯關係不需要設置,雙向關係必須設置,避免雙方都建立外鍵欄位。 對於 一對多 的關係,外鍵總是建立在多的一方(用到@JoinColumn),而 mappedBy 存在相反的一方。 比如: 部門(department)與 員工(Employee) 一個部門對應多個員工。一個員工屬於一個部門。 即部門與員工間的關係是 一對多 的關係。 【舉例:】 public class Department { @OneToMany(mappedBy = "bookCategory", cascade = CascadeType.ALL) private List<Employee> employee; } public class Employee { @ManyToOne private Department department; }
8、@OneToOne
@OneToOne 用於描述兩個數據表間 一對一的關聯關係。 【屬性:】 cascade, 用於定義級聯屬性 fetch, 用於定義 懶載入(LAZY,不查詢就不載入)、熱載入(EAGER,預設) mappedBy, 用於定義 被維護的表(相關聯的表) optional, 用於定義 是否允許對象為 null。
三、JPA 實現 CRUD(以單個實體類為例)
1、搭建環境(以Spring Boot 2.0為例)
(1)添加依賴信息
【pom.xml】 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.lyh.demo</groupId> <artifactId>jpa</artifactId> <version>0.0.1-SNAPSHOT</version> <name>jpa</name> <description>JPA Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.18</version> </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> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
(2)配置連接
【application.properties】 # 資料庫連接配置 spring.datasource.url=jdbc:mysql://localhost:3306/lyh?useUnicode=true&characterEncoding=utf8 spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # jpa 配置 spring.jpa.database=mysql spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update
2、編寫實體類以及映射關係
【com.lyh.demo.jpa.bean.Employee】 package com.lyh.demo.jpa.bean; import lombok.Data; import javax.persistence.*; import java.util.Date; @Entity @Table(name = "emp") @Data @Proxy(lazy = false) public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(name = "name", length = 32) private String name; @Column(name = "age") private Integer age; @Temporal(TemporalType.TIMESTAMP) private Date createDate; }
3、編寫Dao層
不需要編寫實現類。只需要繼承兩個介面(JpaRepository、JpaSpecificationExecutor)。
package com.lyh.demo.jpa.dao; import com.lyh.demo.jpa.bean.Employee; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.stereotype.Component; /** * JpaRepository<操作的實體類型, 實體類中主鍵的類型>, 封裝了 CRUD 基本操作。 * JpaSpecificationExecutor<操作的實體類型>,封裝了複雜的操作,比如 分頁。 */ @Component public interface EmployeeDao extends JpaRepository<Employee, Integer>, JpaSpecificationExecutor<Employee> { }
4、編寫測試類
【com.lyh.demo.jpa.JpaApplicationTests】 package com.lyh.demo.jpa; import com.lyh.demo.jpa.bean.Employee; import com.lyh.demo.jpa.dao.EmployeeDao; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.Date; @SpringBootTest class JpaApplicationTests { @Autowired private EmployeeDao employeeDao; /** * 使用 save 方法時,若沒有 id,則直接進行 添加操作。 */ @Test void testSave() { Employee employee = new Employee(); employee.setName("tom"); employee.setAge(22); employee.setCreateDate(new Date()); employeeDao.save(employee); } /** * 使用 save 方法,若存在 id,會先進行一次查詢操作,若存在數據,則更新數據,否則保存數據。 */ @Test void testUpdate() { Employee employee = new Employee(); employee.setId(10); employee.setName("tom"); employee.setAge((int)(Math.random() * 100 + 1)); employee.setCreateDate(new Date()); employeeDao.save(employee); } /** * 根據 id 查詢某條數據 */ @Test void testFindOne() { System.out.println(employeeDao.getOne(1)); } /** * 查詢所有的數據 */ @Test void testFindAll() { System.out.println(employeeDao.findAll()); } /** * 根據id刪除數據 */ @Test void testDelete() { employeeDao.deleteById(1); } }
測試 save 插入
測試 save 更新。
測試 查詢。
測試刪除。
5、遇到的坑
(1)執行測試(getOne())的時候報錯:
org.hibernate.LazyInitializationException: could not initialize proxy [com.lyh.demo.jpa.bean.Employee#1] - no Session
原因:
getOne() 內部採用懶載入的方式執行,什麼時候用,什麼時候才會去觸發獲取值。
解決辦法一:
在實體類前加上 @Proxy(lazy = false) 用於取消懶載入
【即】 package com.lyh.demo.jpa.bean; import lombok.Data; import org.hibernate.annotations.Proxy; import javax.persistence.*; import java.util.Date; @Entity @Table(name = "emp") @Data @Proxy(lazy = false) public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(name = "name", length = 32) private String name; @Column(name = "age") private Integer age; @Temporal(TemporalType.TIMESTAMP) private Date createDate; }
解決方法二:
在方法執行前,加上 @Transactional。
【即】 /** * 根據 id 查詢某條數據 */ @Test @Transactional void testFindOne() { System.out.println(employeeDao.getOne(2)); }
四、JPA 編寫sql語句 -- jpql
1、簡介
Java Persistence Query Language,可以理解為 JPA 使用的 sql 語句,用於操作實體類以及實體類的屬性。
2、使用
(1)在 Dao 介面中定義相關方法,並通過 @Query 註解來定義 sql 語句。
需要更新數據時,需要使用 @Modifying 註解。測試的時候,需要使用 @Transactional 註解。
若方法參數為實體類對象,則通過 :#{#實體類名.實體類屬性名} 獲取。且方法參數需要使用 @Param聲明。
【com.lyh.demo.jpa.dao.EmployeeDao】 package com.lyh.demo.jpa.dao; import com.lyh.demo.jpa.bean.Employee; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Component; import java.util.List; /** * JpaRepository<操作的實體類型, 實體類中主鍵的類型>, 封裝了 CRUD 基本操作。 * JpaSpecificationExecutor<操作的實體類型>,封裝了複雜的操作,比如 分頁。 * 其中,使用到了方法命名規則寫法。 */ @Component public interface EmployeeDao extends JpaRepository<Employee, Integer>, JpaSpecificationExecutor<Employee> { public List<Employee> getEmployeeByAge(Integer age); @Query("from Employee where age = ?1") public List<Employee> getEmployeeByAge1(Integer age); public List<Employee> getEmployeeByAgeAndName(Integer age, String name); @Query("from Employee where name = ?2 and age = ?1") public List<Employee> getEmployeeByAgeAndName1(Integer age, String name); @Query("update Employee set age = :#{#employee.age} where name = :#{#employee.name}") @Modifying public void updateEmpAgeByName(@Param("employee") Employee employee); }
(2)測試
【com.lyh.demo.jpa.JpaApplicationTestJSQLs】 package com.lyh.demo.jpa; import com.lyh.demo.jpa.bean.Employee; import com.lyh.demo.jpa.dao.EmployeeDao; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.Rollback; import javax.transaction.Transactional; @SpringBootTest class JpaApplicationTestJSQLs { @Autowired private EmployeeDao employeeDao; @Test void testGetEmployeeByAge() { System.out.println(employeeDao.getEmployeeByAge(40)); } @Test void testGetEmployeeByAge1() { System.out.println(employeeDao.getEmployeeByAge1(40)); } @Test void testGetEmployeeByAgeAndName() { System.out.println(employeeDao.getEmployeeByAgeAndName(40, "tom")); } @Test void testGetEmployeeByAgeAndName1() { System.out.println(employeeDao.getEmployeeByAgeAndName1(41, "tom")); } @Test @Transactional void testUpdateEmpAgeByName() { Employee employee = new Employee(); employee.setName("tom"); employee.setAge(11); employeeDao.updateEmpAgeByName(employee); } }
測試 getEmployeeByAge
測試 getEmployeeByAge1,與getEmployeeByAge 的區別在於 getEmployeeByAge1 是自定義查詢方法。
測試 getEmployeeByAgeAndName
測試 getEmployeeByAgeAndName1,同樣屬於自定義查詢方法。
測試 updateEmpAgeByName,採用對象傳參的方式。進行更新操作 需要使用 @Modifying 註解。
3、遇到的坑
(1)報錯:(JDBC style parameters (?) are not supported for JPA queries.)
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'employeeDao': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: JDBC style parameters (?) are not supported for JPA queries.
解決: 在占位符上指定匹配的參數位置(從1開始)
【com.lyh.demo.jpa.dao.EmployeeDao】 @Query("from Employee where age = ?1") public List<Employee> getEmployeeByAge1(Integer age);
(2)使用 實體類對象 作為參數進行 jpql 查詢,獲取實體類某個參數報錯。
解決辦法:使用 :#{#employee.age} 獲取參數。
@Query("update Employee set age = :#{#employee.age} where name = :#{#employee.name}") @Modifying public void updateEmpAgeByName(@Param("employee") Employee employee);
(3)對於 update、delete 操作,需要使用 @Transactional 、 @Modifying 註解,否則會報錯。
五、JPA 編寫 sql 語句 -- sql、方法規則命名查詢
1、sql 語法規則
寫法類似於 jpql,需要使用 @Query 註解,但是需要使用 nativeQuery = true。
若 nativeQuery = false,則使用 jpql。
若 nativeQuery = true,則使用 sql。
(1)配置
【application.properties】 # 資料庫連接配置 spring.datasource.url=jdbc:mysql://localhost:3306/lyh?useUnicode=true&characterEncoding=utf8 spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # jpa 配置 spring.jpa.database=mysql spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
(2)dao層
【com/lyh/demo/jpa/dao/EmployeeDao.java】 package com.lyh.demo.jpa.dao; import com.lyh.demo.jpa.bean.Employee; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Component; import java.util.List; @Component public interface EmployeeDao extends JpaRepository<Employee, Integer>, JpaSpecificationExecutor<Employee> { @Query(value = "select * from emp", nativeQuery = true) public List<Employee> getEmployee(); }
(3)bean
實體類。
【com/lyh/demo/jpa/bean/Employee.java】 package com.lyh.demo.jpa.bean; import lombok.Data; import javax.persistence.*; import java.util.Date; @Entity @Table(name = "emp") @Data public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(name = "name", length = 32) private String name; @Column(name = "age") private Integer age; @Temporal(TemporalType.TIMESTAMP) private Date createTime; }
(4)test
【com/lyh/demo/jpa/JpaApplicationTests.java】 package com.lyh.demo.jpa; import com.lyh.demo.jpa.bean.Employee; import com.lyh.demo.jpa.dao.EmployeeDao; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.Date; import java.util.List; @SpringBootTest class JpaApplicationTests { @Autowired private EmployeeDao employeeDao; @Test void testSave() { Employee employee = new Employee(); employee.setName("tom"); employee.setAge(22); employee.setCreateTime(new Date()); employeeDao.save(employee); } @Test void testGetEmployee() { List<Employee> employeeList = employeeDao.getEmployee(); for (Employee employee: employeeList) { System.out.println(employee); } } }
測試截圖:
執行 兩次 testSave() 方法,添加幾條測試數據。
測試 testGetEmployee() 方法,測試 sql 語句。
2、方法命名規則查詢
是對 jpql 的進一步封裝。只需要根據 SpringDataJPA 提供的方法名規則去定義方法名,從而不需要配置 jpql 語句,會自動根據方法名去解析成 sql 語句。
(1)關鍵字定義:
https://docs.spring.io/spring-data/jpa/docs/2.2.3.RELEASE/reference/html/#repository-query-keywords
詳細文檔:
https://blog.csdn.net/qq_32448349/article/details/89445216
(2)舉例:
findEmployeesByAgeAndName 等價於 select * from emp where age = ? and name = ? 根據屬性名稱進行查詢。 findEmployeesByNameLike 等價於 select * from emp where name like ? 根據屬性進行模糊查詢
(3)測試:
【com/lyh/demo/jpa/dao/EmployeeDao.java】 package com.lyh.demo.jpa.dao; import com.lyh.demo.jpa.bean.Employee; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Component; import java.util.List; @Component public interface EmployeeDao extends JpaRepository<Employee, Integer>, JpaSpecificationExecutor<Employee> { @Query(value = "select * from emp", nativeQuery = true) public List<Employee> getEmployee(); public List<Employee> findEmployeesByAgeAndName(Integer age, String name); public List<Employee> findEmployeesByNameLike(String name); } 【com/lyh/demo/jpa/JpaApplicationTests.java】 package com.lyh.demo.jpa; import com.lyh.demo.jpa.bean.Employee; import com.lyh.demo.jpa.dao.EmployeeDao; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.Date; import java.util.List; @SpringBootTest class JpaApplicationTests { @Autowired private EmployeeDao employeeDao; @Test void testSave() { Employee employee = new Employee(); employee.setName("tom"); employee.setAge(22); employee.setCreateTime(new Date()); employeeDao.save(employee); } @Test void testGetEmployee() { List<Employee> employeeList = employeeDao.getEmployee(); for (Employee employee: employeeList) { System.out.println(employee); } } @Test void testFindEmployeesByAgeAndName() { List<Employee> employeeList = employeeDao.findEmployeesByAgeAndName(22, "tom"); for (Employee employee: employeeList) { System.out.println(employee); } } @Test void testFindEmployeesByNameLike() { List<Employee> employeeList = employeeDao.findEmployeesByNameLike("t%"); for (Employee employee: employeeList) { System.out.println(employee); } } }
基本查詢:
模糊查詢:
六、動態查詢(JpaSpecificationExecutor、Specification)
1、JpaSpecificationExecutor
JpaSpecificationExecutor 是一個介面。查詢語句都定義在 Specification 中。
package org.springframework.data.jpa.repository; import java.util.List; import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; import org.springframework.lang.Nullable; public interface JpaSpecificationExecutor<T> { // 查詢單個對象 Optional<T> findOne(@Nullable Specification<T> var1); // 查詢對象列表 List<T> findAll(@Nullable Specification<T> var1); // 查詢對象列表,並返回分頁數據 Page<T> findAll(@Nullable Specification<T> var1, Pageable var2); // 查詢對象列表,併排序 List<T> findAll(@Nullable Specification<T> var1, Sort var2); // 統計查詢的結果 long count(@Nullable Specification<T> var1); }
2、Specification
定義 sql 語句。同樣是一個介面,需要自定義實現類。需要重寫 toPredicate() 方法。
// Root 指查詢的根對象,可以獲取任何屬性。 // CriteriaQuery 標準查詢,可以自定義查詢方式(一般不用) // CriteriaBuilder 指查詢的構造器,封裝了很多查詢條件 Predicate toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3);
package org.springframework.data.jpa.domain; import java.io.Serializable; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import org.springframework.lang.Nullable; public interface Specification<T> extends Serializable { long serialVersionUID = 1L; static <T> Specification<T> not(@Nullable Specification<T> spec) { return spec == null ? (root, query, builder) -> { return null; } : (root, query, builder) -> { return builder.not(spec.toPredicate(root, query, builder)); }; } @Nullable static <T> Specification<T> where(@Nullable Specification<T> spec) { return spec == null ? (root, query, builder) -> { return null; } : spec; } @Nullable default Specification<T> and(@Nullable Specification<T> other) { return SpecificationComposition.composed(this, other, (builder, left, rhs) -> { return builder.and(left, rhs); }); } @Nullable default Specification<T> or(@Nullable Specification<T> other) { return SpecificationComposition.composed(this, other, (builder, left, rhs) -> { return builder.or(left, rhs); }); } @Nullable Predicate toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3); }
3、基本使用
(1)步驟:
Step1:實現 Specification 介面(定義泛型,為查詢的對象類型),重寫 toPredicate() 方法。
Step2:定義 CriteriaBuilder 查詢條件。
(2)普通查詢
package com.lyh.demo.jpa; import com.lyh.demo.jpa.bean.Employee; import com.lyh.demo.jpa.dao.EmployeeDao; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.jpa.domain.Specification; import javax.persistence.criteria.*; import java.util.Date; import java.util.List; @SpringBootTest class JpaApplicationTests { @Autowired private EmployeeDao employeeDao; @Test void testSave() { Employee employee = new Employee(); employee.setName("tom"); employee.setAge(22); employee.setCreateTime(new Date()); employeeDao.save(employee); } @Test void testSpecification() { // 定義內部類,泛型為 查詢的對象 Specification<Employee> specification = new Specification<Employee>() { @Override public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) { // 獲取比較的屬性 Path<Object> name = root.get("name"); // 構建查詢條件, select * from emp where name = "tom"; Predicate predicate = criteriaBuilder.equal(name, "tom"); return predicate; } }; List<Employee> employeeList = employeeDao.findAll(specification); for (Employee employee : employeeList) { System.out.println(employee); } } }
(3)多條件拼接、模糊查詢
package com.lyh.demo.jpa; import com.lyh.demo.jpa.bean.Employee; import com.lyh.demo.jpa.dao.EmployeeDao; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.jpa.domain.Specification; import javax.persistence.criteria.*; import java.util.Date; import java.util.List; @SpringBootTest class JpaApplicationTests { @Autowired private EmployeeDao employeeDao; @Test void testSave() { Employee employee = new Employee(); employee.setName("tom"); employee.setAge(22); employee.setCreateTime(new Date()); employeeDao.save(employee); } @Test void testSpecification() { // 定義內部類,泛型為 查詢的對象 Specification<Employee> specification = new Specification<Employee>() { @Override public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) { // 獲取比較的屬性 Path<String> name = root.get("name"); Path<Integer> age = root.get("age"); // 構建查詢條件, select * from emp where name like "to%" and age >= 22; Predicate predicate1 = criteriaBuilder.like(name, "to%"); Predicate predicate2 = criteriaBuilder.ge(age, 22); Predicate predicate = criteriaBuilder.and(predicate1, predicate2); return predicate; } }; List<Employee> employeeList = employeeDao.findAll(specification); for (Employee employee : employeeList) { System.out.println(employee); } } }