(1)官網地址:
https://spring.io/projects/spring-data-jpa
參考文檔:
https://docs.spring.io/spring-data/jpa/docs/2.2.3.RELEASE/reference/html/#prefacehtml
(2)基本介紹:
Spring Data JPA 是 Spring 基於 ORM 框架、JPA 規範封裝的一套 JPA 框架。使開發者經過極簡的代碼實現對數據庫的訪問和操做。
注:
ORM 框架:指的是 Object Relational Mapping,即對象關係映射。採用元數據來描述對象和關係映射的細節。
元數據:通常採用 XML 文件的形式。常見 ORM 框架如:mybatis、Hibernate。
JPA:指的是 Java Persistence API,即 Java 持久層 API。經過 xml 或註解的映射關係將運行期的實體對象持久化到數據庫中。java
(1)在 pom.xml 文件中引入依賴mysql
【pom.xml】 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
(2)在 application.properties 中配置spring
【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
@Entity 寫在類上,用於指明一個類與數據庫表相對應。 屬性: name,可選,用於自定義映射的表名。若沒有,則默認以類名爲表名。 【舉例1:默認類名爲表名】 import javax.persistence.Entity; @Entity public class Blog { } 【舉例2:自定義表名】 import javax.persistence.Entity; @Entity(name="t_blog") public class Blog { }
@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 爲主。
@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; }
@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; }
@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; }
對於 @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 去除外鍵關聯,當刪一個實體時,存在外鍵沒法刪除,使用此級聯能夠去除外鍵。
只有 @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; }
@OneToOne 用於描述兩個數據表間 一對一的關聯關係。 【屬性:】 cascade, 用於定義級聯屬性 fetch, 用於定義 懶加載(LAZY,不查詢就不加載)、熱加載(EAGER,默認) mappedBy, 用於定義 被維護的表(相關聯的表) optional, 用於定義 是否容許對象爲 null。
(1)添加依賴信息sql
【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
【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; }
不須要編寫實現類。只須要繼承兩個接口(JpaRepository、JpaSpecificationExecutor)。apache
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> { }
【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 插入api
測試 save 更新。mybatis
測試 查詢。oracle
測試刪除。
(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)); }
Java Persistence Query Language,能夠理解爲 JPA 使用的 sql 語句,用於操做實體類以及實體類的屬性。
(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 註解。
(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 註解,不然會報錯。
寫法相似於 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 語句。
是對 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 中。
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); }
定義 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); }
(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); } } }
(4)排序
在上例 多條件拼接 代碼的基礎上增長排序,使數據按照 id 降序輸出。
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.domain.Sort; 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; } }; // 定義排序(Sort.Direction.DESC,降序; Sort.Direction.ASC,升序) Sort sort = Sort.by(Sort.Direction.DESC, "id"); List<Employee> employeeList = employeeDao.findAll(specification, sort); for (Employee employee : employeeList) { System.out.println(employee); } } }
(5)分頁
在上例 多條件拼接 代碼的基礎上增長分頁。以下例,按每頁一條數據分頁,取第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.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; import javax.persistence.criteria.*; import java.util.Date; @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; } }; // 定義分頁,其中 第一個參數指的是 當前查詢的頁數(從0開始),第二個參數指的是每頁的數量 Pageable pageable = PageRequest.of(1, 1); Page<Employee> page = employeeDao.findAll(specification, pageable); // 獲取當前查詢數據的集合 System.out.println(page.getContent()); // 獲取總條數 System.out.println(page.getTotalElements()); // 獲取總頁數 System.out.println(page.getTotalPages()); } }
表的某條數據,對應另一張表的某條數據。
(1)基本概念:
表的某條數據,對應另一張表的多條數據。
將 「一」 的一方稱爲 :主表。
將 「多」 的一方稱爲 :從表。
一般將 外鍵 置於從表上,即 從表上增長一列做爲外鍵,並依賴於主表的某列。
(2)sql 語句建表
【舉例:】 員工與部門間的關係。 一個部門能夠有多個員工,而一個員工屬於一個部門。此時部門與員工間爲 一對多 的關係。 部門表爲主表,員工表爲從表。外鍵創建在 員工表(從表)上。 CREATE TABLE dept ( deptId int primary key auto_increment, deptName varchar(20) ); CREATE TABLE emp ( id int primary key auto_increment, name varchar(32), age int, deptId int, foreign key(deptId) references dept(deptId) );
(3)jpa建表
【步驟:】
Step1:明確兩表之間的關係
Step2:肯定表之間的關係,一對多(外鍵)仍是多對多(中間表)關係。
Step3:編寫實體類,在實體類中創建表關係(聲明相應的屬性)。
Step4:配置映射關係
Step一、Step2:
部門表 與 員工表間 屬於 一對多的關係,因此須要在員工表上創建外鍵。
【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; } 【com/lyh/demo/jpa/bean/Department.java】 package com.lyh.demo.jpa.bean; import lombok.Data; import javax.persistence.*; @Entity @Table(name = "dept") @Data public class Department { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int deptId; private String deptName; }
Step三、Step4:
在實體類間創建聯繫,並添加映射關係。
【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; /** * 員工表與部門表間 屬於 多對一的關係。因此在員工類中應定義 一個普通屬性去保存部門信息。 * 並使用 @ManyToOne 去定義映射關係(多對一). * 使用 @JoinColumn 定義外鍵(在從表上定義,name指的是 外鍵名,referencedColumnName指的是依賴的主表的主鍵)。 */ @ManyToOne(targetEntity = Department.class) @JoinColumn(name = "deptId", referencedColumnName = "deptId") private Department department; } 【com/lyh/demo/jpa/bean/Department.java】 package com.lyh.demo.jpa.bean; import lombok.Data; import javax.persistence.*; import java.util.HashSet; import java.util.Set; @Entity @Table(name = "dept") @Data public class Department { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int deptId; private String deptName; /** * 部門表與員工表是一對多的關係,因此部門實體類中 應定義集合 去保存員工信息。 * 並使用 @OneToMany 去指定映射關係(一對多)。 * 可使用 @JoinColumn 去創建外鍵,此時能夠對外鍵進行維護(一的一方),若對此外鍵賦值,相對於多的一方,會多出一條 update。 * 若放棄外鍵維護,可使用 mapperBy 指定關聯關係,其值爲對應的類維護的屬性名稱。 */ // @OneToMany(targetEntity = Employee.class) // @JoinColumn(name = "id", referencedColumnName = "deptId") @OneToMany(mappedBy = "department") private Set<Employee> employees = new HashSet<Employee>(); }
(4)測試
文件結構以下圖:
代碼:
【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 【com/lyh/demo/jpa/bean/Department.java】 package com.lyh.demo.jpa.bean; import lombok.Data; import javax.persistence.*; import java.util.HashSet; import java.util.Set; @Entity @Table(name = "dept") @Data public class Department { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int deptId; private String deptName; /** * 部門表與員工表是一對多的關係,因此部門實體類中 應定義集合 去保存員工信息。 * 並使用 @OneToMany 去指定映射關係(一對多)。 * 可使用 @JoinColumn 去創建外鍵,此時能夠對外鍵進行維護(一的一方),若對此外鍵賦值,相對於多的一方,會多出一條 update。 * 若放棄外鍵維護,可使用 mapperBy 指定關聯關係,其值爲對應的類維護的屬性名稱。 */ // @OneToMany(targetEntity = Employee.class) // @JoinColumn(name = "id", referencedColumnName = "deptId") @OneToMany(mappedBy = "department") private Set<Employee> employees = new HashSet<Employee>(); } 【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; /** * 員工表與部門表間 屬於 多對一的關係。因此在員工類中應定義 一個普通屬性去保存部門信息。 * 並使用 @ManyToOne 去定義映射關係(多對一). * 使用 @JoinColumn 定義外鍵(在從表上定義,name指的是 外鍵名,referencedColumnName指的是依賴的主表的主鍵)。 */ @ManyToOne(targetEntity = Department.class) @JoinColumn(name = "deptId", referencedColumnName = "deptId") private Department department; } 【com/lyh/demo/jpa/dao/DepartmentDao.java】 package com.lyh.demo.jpa.dao; import com.lyh.demo.jpa.bean.Department; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.stereotype.Component; @Component public interface DepartmentDao extends JpaRepository<Department, Integer>, JpaSpecificationExecutor<Department> { } 【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> { } 【com/lyh/demo/jpa/JpaApplicationTests.java】 package com.lyh.demo.jpa; import com.lyh.demo.jpa.bean.Department; import com.lyh.demo.jpa.bean.Employee; import com.lyh.demo.jpa.dao.DepartmentDao; 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.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; import org.springframework.transaction.annotation.Transactional; import javax.persistence.criteria.*; import java.util.Date; @SpringBootTest class JpaApplicationTests { @Autowired private EmployeeDao employeeDao; @Autowired private DepartmentDao departmentDao; @Test void testSave1() { Employee employee = new Employee(); employee.setName("tom"); employee.setAge(22); Department department = new Department(); department.setDeptId(1); department.setDeptName("開發"); employeeDao.save(employee); departmentDao.save(department); } @Test void testSave2() { Employee employee = new Employee(); employee.setName("tom"); employee.setAge(22); Department department = new Department(); department.setDeptId(1); department.setDeptName("開發"); // 維護外鍵,即添加值(此處執行順序可能會致使出錯) employee.setDepartment(department); // departmentDao.save(department); employeeDao.save(employee); departmentDao.save(department); } }
測試截圖:
測試 testSave1(),因爲沒有維護外鍵,因此外鍵爲 null。
測試 testSave2(),維護外鍵,外鍵有值。
(5)級聯操做
注意,上例操做,須要對每一個表進行一次操做,這樣有時候會很繁瑣。
此時級聯就能夠派上用場了,級聯用於 操做一個實體類的同時 操做其關聯的另外一個實體類。
上例 testSave2() 可能會出現的問題:當數據爲空時,因爲先執行了 employeeDao.save(employee);
再執行的 departmentDao.save(department); 此時因爲 主表沒有數據, 從表添加外鍵會出錯。
解決方法一:
調換執行 sql 的順序。
解決方法二:
採用級聯屬性(cascade = CascadeType.ALL)。
修改上例代碼。
【com/lyh/demo/jpa/bean/Department.java】 package com.lyh.demo.jpa.bean; import lombok.Data; import javax.persistence.*; import java.util.HashSet; import java.util.Set; @Entity @Table(name = "dept") public class Department { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int deptId; private String deptName; /** * 部門表與員工表是一對多的關係,因此部門實體類中 應定義集合 去保存員工信息。 * 並使用 @OneToMany 去指定映射關係(一對多)。 * 可使用 @JoinColumn 去創建外鍵,此時能夠對外鍵進行維護(一的一方),若對此外鍵賦值,相對於多的一方,會多出一條 update。 * 若放棄外鍵維護,可使用 mapperBy 指定關聯關係,其值爲對應的類維護的屬性名稱。 * 使用 cascade 用於定義級聯屬性。 */ // @OneToMany(targetEntity = Employee.class) // @JoinColumn(name = "id", referencedColumnName = "deptId") @OneToMany(mappedBy = "department", cascade = CascadeType.ALL) private Set<Employee> employees = new HashSet<Employee>(); public int getDeptId() { return deptId; } public void setDeptId(int deptId) { this.deptId = deptId; } public String getDeptName() { return deptName; } public void setDeptName(String deptName) { this.deptName = deptName; } public Set<Employee> getEmployees() { return employees; } public void setEmployees(Set<Employee> employees) { this.employees = employees; } } 【com/lyh/demo/jpa/JpaApplicationTests.java】 package com.lyh.demo.jpa; import com.lyh.demo.jpa.bean.Department; import com.lyh.demo.jpa.bean.Employee; import com.lyh.demo.jpa.dao.DepartmentDao; 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.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; import org.springframework.transaction.annotation.Transactional; import javax.persistence.criteria.*; import java.util.Date; @SpringBootTest class JpaApplicationTests { @Autowired private EmployeeDao employeeDao; @Autowired private DepartmentDao departmentDao; @Test void testSave1() { Employee employee = new Employee(); employee.setName("tom"); employee.setAge(22); Department department = new Department(); department.setDeptId(1); department.setDeptName("開發"); employeeDao.save(employee); departmentDao.save(department); } @Test void testSave2() { Employee employee = new Employee(); employee.setName("tom"); employee.setAge(22); Department department = new Department(); department.setDeptId(1); department.setDeptName("開發"); // 維護外鍵,即添加值 employee.setDepartment(department); department.getEmployees().add(employee); departmentDao.save(department); } }
注:
使用級聯遇到的坑(堆棧溢出 java.lang.StackOverflowError)。去除 @Data,手動 getter、setter。或者重寫 toString() 方法,讓其不輸出 外鍵關聯的屬性。
(6)對象導航查詢
經過查詢一個對象,能夠查詢到其關聯的對象。
對於 一對多 關係,若從 一 的對象 去 查詢 多的對象,則默認採用延遲加載的形式。
若從 多 的對象 去 查詢 一的對象,則默認採用當即加載的形式。
對上例代碼進行修改。 【com/lyh/demo/jpa/bean/Department.java】 package com.lyh.demo.jpa.bean; import lombok.Data; import javax.persistence.*; import java.util.HashSet; import java.util.Set; @Entity @Table(name = "dept") public class Department { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int deptId; private String deptName; /** * 部門表與員工表是一對多的關係,因此部門實體類中 應定義集合 去保存員工信息。 * 並使用 @OneToMany 去指定映射關係(一對多)。 * 可使用 @JoinColumn 去創建外鍵,此時能夠對外鍵進行維護(一的一方),若對此外鍵賦值,相對於多的一方,會多出一條 update。 * 若放棄外鍵維護,可使用 mapperBy 指定關聯關係,其值爲對應的類維護的屬性名稱。 * 使用 cascade 用於定義級聯屬性。 */ // @OneToMany(targetEntity = Employee.class) // @JoinColumn(name = "id", referencedColumnName = "deptId") @OneToMany(mappedBy = "department", cascade = CascadeType.ALL) private Set<Employee> employees = new HashSet<Employee>(); public int getDeptId() { return deptId; } public void setDeptId(int deptId) { this.deptId = deptId; } public String getDeptName() { return deptName; } public void setDeptName(String deptName) { this.deptName = deptName; } public Set<Employee> getEmployees() { return employees; } public void setEmployees(Set<Employee> employees) { this.employees = employees; } @Override public String toString() { return "Department{" + "deptId=" + deptId + ", deptName='" + deptName + '}'; } } 【com/lyh/demo/jpa/JpaApplicationTests.java】 package com.lyh.demo.jpa; import com.lyh.demo.jpa.bean.Department; import com.lyh.demo.jpa.bean.Employee; import com.lyh.demo.jpa.dao.DepartmentDao; 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.transaction.annotation.Transactional; @SpringBootTest class JpaApplicationTests { @Autowired private EmployeeDao employeeDao; @Autowired private DepartmentDao departmentDao; /** * 測試級聯添加數據 */ @Test void testSave() { Employee employee = new Employee(); employee.setName("tom"); employee.setAge(22); Department department = new Department(); department.setDeptId(1); department.setDeptName("開發"); // 維護外鍵,即添加值 employee.setDepartment(department); department.getEmployees().add(employee); departmentDao.save(department); } /** * 測試對象查詢(獲取多 的一方的對象,並獲取其關聯的對象。其默認加載方式爲 當即加載。) */ @Test @Transactional void testObjectQueryOneFromMany() { Employee employee = employeeDao.getOne(1); System.out.println(employee.getDepartment()); } /** * 測試對象查詢(獲取一 的一方的對象,並獲取其關聯的對象。其默認加載方式爲 延遲加載。) */ @Test @Transactional void testObjectQueryManyFromOne() { Department department = departmentDao.getOne(1); System.out.println(department.getEmployees()); } }
測試 testObjectQueryOneToMany()。
測試 testObjectQueryManyFromOne()。
(1)基本概念:
兩張表之間互爲一對多的關係。
採用中間表來維護 兩表間的關係。中間表至少由兩個字段組成,且這兩個字段做爲外鍵指向兩張表的主鍵,造成聯合主鍵。
(2)sql 建表
相似於 一對多關係。
【舉例:】 員工表 與 角色表。 一個員工能夠對應多個角色,一個角色能夠對應多個員工。員工與角色之間是多對多關係。 須要創建中間表。 drop table emp_and_role; drop table emp; drop table role; CREATE TABLE role ( roleId int primary key auto_increment, roleName varchar(32) ); CREATE TABLE emp ( id int primary key auto_increment, name varchar(32), age int ); CREATE TABLE emp_and_role ( emp_id int, role_id int, primary key(emp_id, role_id), foreign key(emp_id) references emp(id), foreign key(role_id) references role(roleId) );
(3)jpa 建表
【步驟:】
Step1:明確兩表之間的關係
Step2:肯定表之間的關係,一對多(外鍵)仍是多對多(中間表)關係。
Step3:編寫實體類,在實體類中創建表關係(聲明相應的屬性)。
Step4:配置映射關係
Step一、Step2:
員工表、角色表爲多對多關係,因此需創建中間表。
【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; } 【com/lyh/demo/jpa/bean/Role.java】 package com.lyh.demo.jpa.bean; import lombok.Data; import javax.persistence.*; @Entity @Table(name = "role") @Data public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer roleId; @Column(length = 32) private String roleName; } 【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.stereotype.Component; @Component public interface EmployeeDao extends JpaRepository<Employee, Integer>, JpaSpecificationExecutor<Employee> { } 【com/lyh/demo/jpa/dao/RoleDao.java】 package com.lyh.demo.jpa.dao; import com.lyh.demo.jpa.bean.Role; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.stereotype.Component; @Component public interface RoleDao extends JpaRepository<Role, Integer>, JpaSpecificationExecutor<Role> { }
Step三、Step4:
配置映射關係。
【com/lyh/demo/jpa/bean/Employee.java】 package com.lyh.demo.jpa.bean; import lombok.Data; import javax.persistence.*; import java.util.Date; import java.util.HashSet; import java.util.Set; @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; /** * 配置多對多關係。 * @JoinTable 爲配置中間表。 * 其中: * name:中間表名。 * joinColumns:定義外鍵,並關聯於當前類的主鍵。 * inverseJoinColumns:定義外鍵,並關聯於另外一個類的主鍵。 */ @ManyToMany(targetEntity = Role.class) @JoinTable(name = "emp_and_role", joinColumns = {@JoinColumn(name = "emp_id", referencedColumnName = "id")}, inverseJoinColumns = {@JoinColumn(name = "role_id", referencedColumnName = "roleId")}) private Set<Role> roleSet = new HashSet<>(); } 【com/lyh/demo/jpa/bean/Role.java】 package com.lyh.demo.jpa.bean; import lombok.Data; import javax.persistence.*; import java.util.HashSet; import java.util.Set; @Entity @Table(name = "role") public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer roleId; @Column(length = 32) private String roleName; /** * 放棄外鍵維護權。 * 並定義級聯屬性。 */ @ManyToMany(mappedBy = "roleSet", cascade = CascadeType.ALL) private Set<Employee> employeeSet = new HashSet<>(); public Integer getRoleId() { return roleId; } public void setRoleId(Integer roleId) { this.roleId = roleId; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } public Set<Employee> getEmployeeSet() { return employeeSet; } public void setEmployeeSet(Set<Employee> employeeSet) { this.employeeSet = employeeSet; } @Override public String toString() { return "Role{" + "roleId=" + roleId + ", roleName='" + roleName + '\'' + ", employeeSet=" + employeeSet + '}'; } }
(4)測試(使用級聯賦值)
【com/lyh/demo/jpa/JpaApplicationTests.java】 package com.lyh.demo.jpa; import com.lyh.demo.jpa.bean.Employee; import com.lyh.demo.jpa.bean.Role; import com.lyh.demo.jpa.dao.EmployeeDao; import com.lyh.demo.jpa.dao.RoleDao; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class JpaApplicationTests { @Autowired private EmployeeDao employeeDao; @Autowired private RoleDao roleDao; @Test void testSave1() { Employee employee = new Employee(); employee.setName("tom"); employee.setAge(22); Role role = new Role(); role.setRoleName("經理"); // 維護外鍵 employee.getRoleSet().add(role); role.getEmployeeSet().add(employee); // 使用級聯賦值 roleDao.save(role); } }