一、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.bootgroupId> <artifactId>spring-boot-starter-data-jpaartifactId> 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 Listemployee; } 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】xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.springframework.boot spring-boot-starter-parent 2.2.2.RELEASE com.lyh.demo jpa 0.0.1-SNAPSHOT jpa JPA Demo project for Spring Boot 1.8 org.springframework.boot spring-boot-starter-data-jpa org.springframework.boot spring-boot-devtools runtime true mysql mysql-connector-java 8.0.18 org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.junit.vintage junit-vintage-engine org.springframework.boot spring-boot-maven-plugin
(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, JpaSpecificationExecutor { }
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, JpaSpecificationExecutor { public List getEmployeeByAge(Integer age); @Query("from Employee where age = ?1") public List getEmployeeByAge1(Integer age); public List getEmployeeByAgeAndName(Integer age, String name); @Query("from Employee where name = ?2 and age = ?1") public List 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 ListgetEmployeeByAge1(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, JpaSpecificationExecutor { @Query(value = "select * from emp", nativeQuery = true) public List 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() { ListemployeeList = 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, JpaSpecificationExecutor { @Query(value = "select * from emp", nativeQuery = true) public List getEmployee(); public List findEmployeesByAgeAndName(Integer age, String name); public List 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 employeeList = employeeDao.getEmployee(); for (Employee employee: employeeList) { System.out.println(employee); } } @Test void testFindEmployeesByAgeAndName() { List employeeList = employeeDao.findEmployeesByAgeAndName(22, "tom"); for (Employee employee: employeeList) { System.out.println(employee); } } @Test void testFindEmployeesByNameLike() { List 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{ // 查询单个对象 Optional findOne(@Nullable Specification var1); // 查询对象列表 List findAll(@Nullable Specification var1); // 查询对象列表,并返回分页数据 Page findAll(@Nullable Specification var1, Pageable var2); // 查询对象列表,并排序 List findAll(@Nullable Specification var1, Sort var2); // 统计查询的结果 long count(@Nullable Specification var1); }
2、Specification
定义 sql 语句。同样是一个接口,需要自定义实现类。需要重写 toPredicate() 方法。
// Root 指查询的根对象,可以获取任何属性。 // CriteriaQuery 标准查询,可以自定义查询方式(一般不用) // CriteriaBuilder 指查询的构造器,封装了很多查询条件 Predicate toPredicate(Rootvar1, 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 Specificationextends Serializable { long serialVersionUID = 1L; static Specification not(@Nullable Specification spec) { return spec == null ? (root, query, builder) -> { return null; } : (root, query, builder) -> { return builder.not(spec.toPredicate(root, query, builder)); }; } @Nullable static Specification where(@Nullable Specification spec) { return spec == null ? (root, query, builder) -> { return null; } : spec; } @Nullable default Specification and(@Nullable Specification other) { return SpecificationComposition.composed(this, other, (builder, left, rhs) -> { return builder.and(left, rhs); }); } @Nullable default Specification or(@Nullable Specification other) { return SpecificationComposition.composed(this, other, (builder, left, rhs) -> { return builder.or(left, rhs); }); } @Nullable Predicate toPredicate(Root 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() { // 定义内部类,泛型为 查询的对象 Specificationspecification = new Specification () { @Override public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) { // 获取比较的属性 Path
(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() { // 定义内部类,泛型为 查询的对象 Specificationspecification = new Specification () { @Override public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) { // 获取比较的属性 Path name = root.get("name"); Path 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 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() { // 定义内部类,泛型为 查询的对象 Specificationspecification = new Specification () { @Override public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) { // 获取比较的属性 Path name = root.get("name"); Path 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 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() { // 定义内部类,泛型为 查询的对象 Specificationspecification = new Specification () { @Override public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) { // 获取比较的属性 Path name = root.get("name"); Path 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 page = employeeDao.findAll(specification, pageable); // 获取当前查询数据的集合 System.out.println(page.getContent()); // 获取总条数 System.out.println(page.getTotalElements()); // 获取总页数 System.out.println(page.getTotalPages()); } }
七、多表操作
1、一对一(@OneToOne)
表的某条数据,对应另外一张表的某条数据。
2、一对多(@OneToMany,@ManyToOne)
(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:配置映射关系
Step1、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; }
Step3、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 Setemployees = new HashSet (); }
(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 Setemployees = new HashSet (); } 【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 , JpaSpecificationExecutor { } 【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 , JpaSpecificationExecutor { } 【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 Setemployees = new HashSet (); 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 getEmployees() { return employees; } public void setEmployees(Set 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 Setemployees = new HashSet (); 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 getEmployees() { return employees; } public void setEmployees(Set 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()。
3、多对多(@ManyToMany)
(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:配置映射关系
Step1、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, JpaSpecificationExecutor { } 【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 , JpaSpecificationExecutor { }
Step3、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 SetroleSet = 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 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 getEmployeeSet() { return employeeSet; } public void setEmployeeSet(Set 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); } }