Spring Data JPA
常用注解汇总
一、Spring Data JPA
1、简介
Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范封装的一套 JPA 框架。使开发者通过极简的代码实现对数据库的访问和操作。
ORM 框架:指的是 Object Relational Mapping,即对象关系映射。采用元数据来描述对象和关系映射的细节。常见框架如:mybatis、Hibernate。
元数据:一般采用 XML 文件的形式。
JPA:指的是 Java Persistence API,即 Java 持久层 API。通过 xml 或注解的映射关系将运行期的实体对象持久化到数据库中。
基于注解的形式,根据java中的实体类生成数据库中的数据库表。
2、sping boot 项目中使用
step1:在 【pom.xml】文件中引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
step2:在 【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
注:若 @Entity 与 @Table 同时定义了 name 属性,那以 @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 {
}
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, // 映射name字段在数据库中的约束条件
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为例)
- 【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>
- 【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)。
JpaRepository<操作的实体类型, 实体类中主键的类型>
, 封装了 CRUD 基本操作。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;
@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);
}
}
-
增
-
改
-
-
查
-
-
删
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 提供的查询语法,用于操作实体类以及实体类的属性。
2、使用
- 在 Dao 接口中定义相关方法
- 通过
@Query
定义 sql 语句 - 更新数据时,需要使用
@Modifying
- 测试的时候,需要使用
@Transactional
- 若方法参数为实体类对象,则
@Query
的sql语句通过:#{#实体类名.实体类属性名}
取值。且方法参数需要使用@Param
声明。
JpaRepository<操作的实体类型, 实体类中主键的类型>, 封装了 CRUD 基本操作。
JpaSpecificationExecutor<操作的实体类型>,封装了复杂的操作,比如 分页。
【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;
@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);
}
- 测试【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);
}
}
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 、DAO定义方法需要 @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);
}
}
}
2、方法命名规则查询
方法命名规则查询是对 jpql 的进一步封装。只需要根据 SpringDataJPA 提供的方法名规则去定义方法名,从而不需要配置 jpql 语句,会自动根据方法名去解析成 sql 语句。
(1)关键字定义
(2)举例
根据属性名称进行查询
findEmployeesByAgeAndName 等价于 select * from emp where age = ? and name = ?
根据属性进行模糊查询
findEmployeesByNameLike 等价于 select * from emp where name like ?
(3)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();
public List<Employee> findEmployeesByAgeAndName(Integer age, String name);
public List<Employee> findEmployeesByNameLike(String name);
}
(4)测试:【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
中。
【JpaSpecificationExecutor】
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);
【Specification】
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 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 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 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)分页
在上例 多条件拼接 代码的基础上增加分页。如下例,按每页1条数据分页,取第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 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、一对一(@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 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 的顺序。先执行departmentDao.save(department);
在执行employeeDao.save(employee);
解决方法二:
采用级联属性(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()
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<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> {
}
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 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);
}
}
关闭JPA持久化框架
中修改实体后自动同步更新到数据库
参考:https://www.jianshu.com/p/7bcaf3f3dc41
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.hibernate.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import java.util.Iterator;
import java.util.Optional;
/**
* @author JHL
* @version 1.0
* @since : JDK 11
*/
@Slf4j
@Aspect
@Component
public class RepositoryAspect {
@Autowired
private EntityManager entityManager;
// 此处环绕的是你项目工程下 repository 文件的包路径
@Pointcut("execution(public * com.ailun.modules.fwq.repository..*.*(..))*")
public void repositoryEvict() {
}
// @After("repositoryEvict()")
// public void doAfter() {
//// entityManager.flush();
// entityManager.clear();
// log.info("Aspect repositoryEvict() doAfter ... ...");
// }
@Around("repositoryEvict()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = joinPoint.proceed();
clear(result);
log.info("Aspect repositoryEvict(),{}", joinPoint.getSignature());
return result;
}
@SuppressWarnings({"unchecked", "rawtypes"})
private void clear(Object result) {
if (result.getClass().getAnnotation(Entity.class) != null) {
evict(result);
return;
}
if (result instanceof Optional) {
Object entity = ((Optional) result).orElse(null);
if (entity != null && entity.getClass().getAnnotation(Entity.class) != null) {
evict(entity);
}
return;
}
if ((result instanceof Iterable)) {
Iterator iterator = ((Iterable) result).iterator();
boolean init = false;
boolean toClear = false;
while (iterator.hasNext()) {
Object object = (Object) iterator.next();
if (!init) {
toClear = object.getClass().getAnnotation(Entity.class) != null;
}
if (!toClear) {
return;
}
evict(object);
}
return;
}
}
private void evict(Object entity) {
Session session = (Session) entityManager.getDelegate();
if (session.isOpen()) {
session.evict(entity);
}
}
}
JPA忽略实体类中的某个属性,不持久化此属性
https://blog.csdn.net/weixin_42830314/article/details/108828285
在实体上加注解 @Transient 注解
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix