JPA
第 1 节 JPA 简介
JPA:全称 Java Persistence API 意即 Java 持久化 API ,是 Sun 官方在 JDK5.0 后提出的 Java 持久化规范(JSR 338,这些接口所在包为 javax.persistence)。
JPA 的出现主要是为了简化持久层开发以及整合 ORM 技术,结束 Hibernate、TopLink、JDO 等 ORM 框架各自为营的局面。JPA 是在吸收现有 ORM 框架的基础上发展而来,易于使用,伸缩性强。总的来说,JPA 包括以下3方面的技术:
- ORM 映射元数据: 支持 XML 和注解两种元数据的形式,元数据描述对象和表之间的映射关系;
- API: 操作实体对象来执行 CRUD 操作;
- 查询语言: 通过面向对象而非面向数据库的查询语言(JPQL)查询数据,避免程序的 SQL 语句紧密耦合。
JPA 由创建的 model 生成对应的数据表,并自动生成大量 SQL 语句操作方法,可以适应单表和多表查询。
第 2 节 Spring Data JPA 开发
2.1 Spring Data JPA 简介
Spring Data JPA 是 Spring Data 家族的一部分,可以轻松实现基于 JPA 的存储库。 此模块处理对基于 JPA 的数据访问层的增强支持。 它使构建使用数据访问技术的 Spring 驱动应用程序变得更加容易。
在相当长的一段时间内,实现应用程序的数据访问层一直很麻烦。 必须编写太多样板代码来执行简单查询以及执行分页和审计。 Spring Data JPA 旨在通过减少实际需要的工作量来显著改善数据访问层的实现。 作为开发人员,只要编写 repository 接口,包括自定义查找器方法,Spring 将自动提供实现。
2.2 开发流程
2.2.1 导入依赖
基于 Spring Boot 启动依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
2.2.2 配置文件
JPA 配置文件:
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
password: root
username: root
url: jdbc:mysql:///jpa
jpa:
hibernate:
# update表示数据表不存在时自动创建
# create表示创建数据表,如果表存在则先删除再创建
ddl-auto: update
# 显示SQL语句
show-sql: true
2.2.3 编写 model
model 层中就是实体类,一个实体类对应一张数据表。
package com.luochen.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
// 标识一个实体类
@Entity
// 标识待创建数据表名,如果有则不创建
@Table(name = "Student")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
// @Id标识主键
@Id
// 设置自增长,默认自增长
@GeneratedValue(strategy = GenerationType.AUTO)
// @Column标识字段
@Column(name = "id", nullable = false, length = 100)
private Integer id;
// 默认长度255
@Column(name = "user_name",length = 50)
private String userName;
}
2.2.4 测试
package com.luochen;
import com.luochen.dao.StudentDao;
import com.luochen.model.Student;
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.Sort;
import java.util.List;
/**
* @author 洛尘大大
* @description: TODO
* @date 2021/11/29 13:43
*/
@SpringBootTest
class StudentDaoTest {
@Autowired
private StudentDao studentDao;
/**
* 添加数据
*/
@Test
void save(){
Student stu1 = new Student(null, "马超");
studentDao.save(stu1);
}
/**
* 查询数据
*/
@Test
void query(){
List<Student> studentList = studentDao.findAll();
studentList.forEach(System.out::println);
// 降序排序
List<Student> studentList1 = studentDao.findAll(Sort.by("id").descending());
studentList1.forEach(System.out::println);
// 分页查找,从0开始
PageRequest pageRequest = PageRequest.of(0, 2);
// 默认分页升序排序
Page<Student> studentPage = studentDao.findAll(pageRequest.withSort(Sort.by("id")));
studentPage.forEach(System.out::println);
}
/**
* 删除
*/
@Test
void delete(){
// 根据主键id删除,也可以删除多个
studentDao.deleteById(2);
}
/**
* 更新
*/
@Test
void update(){
Student student = new Student(3,"冯玉祥");
studentDao.save(student);
}
}
2.3 JPA 常用注解
2.3.1 @Entity
标注用于实体类声明语句之前,指出该 Java 类为实体类,将映射到指定的数据库表。
2.3.2 @Table
当实体类与其映射的数据库表名不同名时需要使用 @Table 标注说明,该标注与 @Entity 标注并列使用,置于实体类声明语句之前,可写于单独语句行,也可与声明语句同行。
- name 属性:指定映射到数据库的表。
2.3.3 @Basic
@Basic 表示一个简单的属性到数据库表的字段的映射,对于没有任何标注的 getXxxx() 方法,默认即为。
- 属性 fetch: 表示该属性的读取策略,有 EAGER 和 LAZY 两种,分别表示主支抓取和延迟加载,默认为 EAGER;
- 属性 optional:表示该属性是否允许为 null,默认为true。
2.3.4 @Column
当实体的属性与其映射的数据库表的列不同名时需要使用。该属性通常置于实体的属性声明语句之前,还可与 @Id 标注一起使用。
- name 属性:用于设置映射数据库表的列名;
- unique:
- nullable:设置该字段是否能为空,true/false;
- length:设置字段长度;
- columnDefinition 属性:表示该字段在数据库中的实际类型,通常 ORM 框架可以根据属性类型自动判断数据库中字段的类型,但是对于 Date 类型仍无法确定数据库中字段类型究竟是 DATE,TIME 还是 TIMESTAMP。此外,String 的默认映射类型为 VARCHAR,如果要将 String 类型映射到特定数据库的 BLOB 或 TEXT 字段类型,需要用到该属性。
@Column 标注也可置于属性的 getter 方法之前。
columnDefinition 设置字段注释实例:
@Column(columnDefinition="varchar(255) default '123' comment 'token验证用户登录是否正确'")
private String token;
2.3.5 @GeneratedValue
用于标注主键的生成策略,通过 strategy 属性指定。
- IDENTITY:采用数据库 ID 自增长的方式来自增主键字段,Oracle 不支持这种方式;
- AUTO: JPA 自动选择合适的策略,是默认选项;
- SEQUENCE:通过序列产生主键,通过 @SequenceGenerator 注解指定序列名,MySql 不支持这种方式;
- TABLE:通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植。
2.3.6 @Id
标注用于声明一个实体类的属性映射为数据库的主键列。该属性通常置于属性声明语句之前,可与声明语句同行,也可写在单独行上。
@Id 标注也可置于属性的getter方法之前。
2.3.7 @Transient
表示该属性并非一个到数据库表的字段的映射,ORM 框架将忽略该属性。
如果一个属性并非数据库表的字段映射,就务必将其标示为 @Transient,否则 ORM 框架默认其注解为 @Basic。
2.3.8 @Temporal
在核心的 Java API 中并没有定义 Date 类型的精度(temporal precision)。而在数据库中,表示 Date 类型的数据有 DATE,TIME,和 TIMESTAMP 三种精度(即单纯的日期,时间,或者两者兼备)。
在进行属性映射时可使用 @Temporal 注解来调整精度。
2.3.9 @Query (Spring Data JPA 用法)
/**
* nativeQuery = true:表示使用SQL的语法进行操作
*
* @param name 学生名字
* @return 学生模糊查询列表
*/
@Query(value = "SELECT* FROM student WHERE user_name LIKE ?", nativeQuery = true)
List<Student> getAllByUserNameLike(String name);
2.3.10 @Modifying
在 @Query 注解中编写 JPQL 实现 DELETE 和 UPDATE 操作的时候必须加上 @modifying 注解,以通知 Spring Data 这是一个 DELETE 或 UPDATE 操作。
UPDATE 或者 DELETE 操作需要使用事务,此时需要定义 Service 层,在 Service 层的方法上添加事务操作。
第 3 节 核心接口
3.1 Repository 接口
Repository 接口是 Spring Data JPA 中为我们提供的所有接口中的顶层接口。Repository 提供了两种查询方式的支持:
- 基于方法名称命名规则查询或更新(自定义 dao 方法)
- 基于 @Query 注解查询
查询关键字:
find、query、get、search
更新关键字:
update
自定义 dao 接口方法:
package com.luochen.dao;
import com.luochen.model.Student;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
public interface StudentDao extends Repository<Student, Integer> {
/**
* 方法名称必须要遵循驼峰式命名规则,findBy(关键字)+ 属性名称(首字母大写)+ 查询条件(首字母大写)
*/
List<Student> findAllByUserNameLike(String name);
/**
* HQL语法:Hibernate Query Language
*
* @param name 学生名字
* @return 学生模糊查询列表
*/
@Query("FROM Student WHERE userName LIKE :name")
List<Student> queryAllByUserNameLike(String name);
/**
* nativeQuery = true:表示使用SQL的语法进行操作
*
* @param name 学生名字
* @return 学生模糊查询列表
*/
@Query(value = "SELECT* FROM student WHERE user_name LIKE ?", nativeQuery = true)
List<Student> getAllByUserNameLike(String name);
/**
* @param name 学生名字
* @return 学生模糊查询列表
*/
List<Student> searchAllByUserNameLike(String name);
/**
* Modifying:标识当前语句是一个更新语句;
* Transactional:开启事务,否则无法执行修改或删除;
* 必须使用SQL语法
*
* @param name 学生姓名
* @param id 学生id
*/
@Query(value = "UPDATE Student SET user_name=? WHERE id=?", nativeQuery = true)
@Modifying
@Transactional
void updateStudentById(String name, Integer id);
}
测试:
package com.luochen;
import com.luochen.dao.StudentDao;
import com.luochen.model.Student;
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.Sort;
import java.util.List;
@SpringBootTest
class SpringbootJpaApplicationTests {
// 自动注入
@Autowired
private StudentDao studentDao;
@Test
void jpaTest02(){
List<Student> students1 = studentDao.findAllByUserNameLike("%张%");
students1.forEach(System.out::println);
List<Student> students2 = studentDao.getAllByUserNameLike("%冯%");
students2.forEach(System.out::println);
List<Student> students3 = studentDao.queryAllByUserNameLike("%超%");
students3.forEach(System.out::println);
List<Student> students4 = studentDao.searchAllByUserNameLike("%马%");
students4.forEach(System.out::println);
}
}
3.2 CrudRepository 接口
该接口是 Repository 的子接口,主要是提供一些增删改查的操作。
public interface StudentDao extends CrudRepository<Student, Integer> {}
package com.luochen;
import com.luochen.dao.StudentDao;
import com.luochen.model.Student;
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.Sort;
import java.util.List;
@SpringBootTest
class SpringbootJpaApplicationTests {
// 自动注入
@Autowired
private StudentDao studentDao;
@Test
void jpaTest03(){
// 查询全部数据,返回迭代器
Iterable<Student> students = studentDao.findAll();
students.forEach(System.out::println);
}
}
3.3 PagingAndSortingRepository 接口
该接口是 CrudRepository 的子接口,主要提供了分页与排序的操作。
public interface StudentDao extends PagingAndSortingRepository<Student, Integer> {}
package com.luochen;
import com.luochen.dao.StudentDao;
import com.luochen.model.Student;
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.Sort;
import java.util.List;
@SpringBootTest
class SpringbootJpaApplicationTests {
// 自动注入
@Autowired
private StudentDao studentDao;
@Test
void jpaTest01() {
// 创建实体类对象,并注入值
Student student = new Student();
student.setUserName("马超");
// 保存,相当于insert语句
studentDao.save(student);
// 查询数据
List<Student> students = (List<Student>) studentDao.findAll();
students.forEach(System.out::println);
// 降序查找
List<Student> studentList = (List<Student>) studentDao.findAll(Sort.by("id").descending());
studentList.forEach(System.out::println);
// 分页查找,从0开始
PageRequest pageRequest = PageRequest.of(0, 5);
Page<Student> studentPage = studentDao.findAll(pageRequest);
// 获取数据总条数
long totalElements = studentPage.getTotalElements();
// 获取数据总页数
int totalPages = studentPage.getTotalPages();
System.out.println(totalElements + ":" + totalPages);
studentPage.forEach(System.out::println);
}
}
3.4 JpaRepository 接口
该接口是 PagingAndSortingRepository 的子接口,对继承父接口中方法的返回值进行了适配,因为在父类接口中通常都返回迭代器,需要我们自己进行强制类型转化。而在 JpaRepository 中,直接返回了 List。
3.5 JpaSpecificationExecutor 接口
JpaSpecificationExecutor 接口是独立于以上接口之外的接口,主要用于实现复杂动态查询,并且支持分页与排序。
// JpaSpecificationExecutor接口需要结合其它接口一起使用
public interface StudentDao extends JpaRepository<Student, Integer>, JpaSpecificationExecutor<Student> {}
1、单条件查询
package com.luochen;
import com.luochen.dao.StudentDao;
import com.luochen.model.Student;
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.Sort;
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.*;
import java.util.List;
import java.util.Optional;
@SpringBootTest
class SpringbootJpaApplicationTests {
// 自动注入
@Autowired
private StudentDao studentDao;
@Test
void jpaTest04(){
Specification<Student> studentSpecification = new Specification<Student>() {
/**
*
* @param root 根对象。封装了查询条件的对象,代表查询的实体类
* @param query 定义了一个基本的查询。一般不使用,可以从中得到Root对象, 即告知JPA CriteriaBuilder查询要查询哪一个实体类, * 还可以添加查询条件, 还可以结合 EntityManager 对象得到最终查询的 TypedQuery 对象。
* @param criteriaBuilder 创建一个查询条件
* @return 定义了查询条件
*/
@Override
public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
// 获取需要匹配的条件
Path<Object> id = root.get("id");
// 获取匹配的结果
Predicate predicate = criteriaBuilder.equal(id, 1);
// 返回匹配到的结果
return predicate;
}
};
List<Student> students = studentDao.findAll(studentSpecification);
students.forEach(System.out::println);
}
}
2、多条件查询
package com.luochen;
import com.luochen.dao.StudentDao;
import com.luochen.model.Student;
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.Sort;
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.*;
import java.util.ArrayList;
import java.util.List;
@SpringBootTest
class SpringbootJpaApplicationTests {
// 自动注入
@Autowired
private StudentDao studentDao;
@Test
void jpaTest05() {
// 组装多个条件查询结果
Specification<Student> studentSpecification = new Specification<Student>() {
@Override
public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
// 将不同的匹配条件结果存入列表中
List<Predicate> predicateList = new ArrayList<>();
predicateList.add(criteriaBuilder.equal(root.get("id"), 1));
predicateList.add(criteriaBuilder.equal(root.get("userName"), "张飞"));
// 将列表转化成Predicate数组
Predicate[] predicates = new Predicate[predicateList.size()];
// 返回多条件查询的结果
// return criteriaBuilder.or(criteriaBuilder.equal(root.get("id"), 1),criteriaBuilder.equal(root.get("userName"), "张飞"));
return criteriaBuilder.or(predicateList.toArray(predicates));
}
};
List<Student> students = studentDao.findAll(studentSpecification);
students.forEach(System.out::println);
}
}
第 4 节 关联映射
4.1 一对一
JPA 使用 @OneToOne 来标注一对一的关系。
两种方式描述 JPA 的一对一关系:
- 一种是通过外键的方式(一个实体通过外键关联到另一个实体的主键);
- 另外一种是通过一张关联表来保存两个实体一对一的关系。
一对一关系维护端任意,可以互相维护。
4.1.1 外键关联
Student 实体类:
package com.luochen.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
// 标识一个实体类
@Entity
// 标识待创建数据表名,如果有则不创建
@Table(name = "Student")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
// @Id标识主键
@Id
// 设置自增长,默认自增长
@GeneratedValue(strategy = GenerationType.AUTO)
// @Column标识字段
@Column(name = "id", nullable = false, length = 100)
private Integer id;
// 默认长度255
@Column(name = "user_name", length = 50)
private String userName;
// Student时关系的维护端,如果删除,则应级联删除对应的地址
@OneToOne(cascade = CascadeType.ALL)
// Student中的address_id对应Address中的id
@JoinColumn(name = "address_id", referencedColumnName = "id")
private Address address;
}
Address 实体类:
package com.luochen.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
/**
* @author 洛尘大大
* @description: TODO
* @date 2021/11/29 15:12
*/
@Entity
@Table(name = "address")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", nullable = false)
private Integer id;
@Column(name = "address_name")
private String addressName;
// 如果不需要根据Address级联查询Student,可以注释掉
// @OneToOne(mappedBy = "address", cascade = {CascadeType.MERGE, CascadeType.REFRESH}, optional = false)
// private Student student;
}
测试:
package com.luochen;
import com.luochen.dao.StudentDao;
import com.luochen.model.Address;
import com.luochen.model.Student;
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.Sort;
import java.util.List;
/**
* @author 洛尘大大
* @description: TODO
* @date 2021/11/29 13:43
*/
@SpringBootTest
class StudentDaoTest {
@Autowired
private StudentDao studentDao;
/**
* 添加数据
*/
@Test
void save() {
Student stu1 = new Student(null, "马超", null);
studentDao.save(stu1);
}
/**
* 查询数据
*/
@Test
void query() {
List<Student> studentList = studentDao.findAll();
studentList.forEach(System.out::println);
// 降序排序
List<Student> studentList1 = studentDao.findAll(Sort.by("id").descending());
studentList1.forEach(System.out::println);
// 分页查找,从0开始
PageRequest pageRequest = PageRequest.of(0, 2);
// 默认分页升序排序
Page<Student> studentPage = studentDao.findAll(pageRequest.withSort(Sort.by("id")));
studentPage.forEach(System.out::println);
}
/**
* 删除
*/
@Test
void delete() {
// 根据主键id删除,也可以删除多个
studentDao.deleteById(2);
}
/**
* 更新
*/
@Test
void update() {
Student student = new Student(3, "冯玉祥", null);
studentDao.save(student);
}
/**
* 一对一关系映射
*/
@Test
void oneToOne(){
Address address = new Address(null,"成都");
Student student = new Student(null, "赵云", address);
Student save = studentDao.save(student);
System.out.println(save);
}
}
4.1.2 关联表关联
Student 表设计如下:
package com.luochen.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
// 标识一个实体类
@Entity
// 标识待创建数据表名,如果有则不创建
@Table(name = "Student")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
// @Id标识主键
@Id
// 设置自增长,默认自增长
@GeneratedValue(strategy = GenerationType.AUTO)
// @Column标识字段
@Column(name = "student_id", nullable = false, length = 100)
private Integer id;
// 默认长度255
@Column(name = "user_name", length = 50)
private String userName;
@OneToOne(cascade = CascadeType.ALL)
// 关联表指定一对一关系
@JoinTable(name = "student_address",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "address_id")
)
private Address address;
}
Address 实体类设计如下:
package com.luochen.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
/**
* @author 洛尘大大
* @description: TODO
* @date 2021/11/29 15:12
*/
@Entity
@Table(name = "address")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "address_id", nullable = false)
private Integer id;
@Column(name = "address_name")
private String addressName;
// 如果不需要根据Address级联查询Student,可以注释掉
// @OneToOne(mappedBy = "address", cascade = {CascadeType.MERGE, CascadeType.REFRESH}, optional = false)
// private Student student;
}
4.2 一对多和多对一
JPA 使用 @OneToMany 和 @ManyToOne 来标识一对多的双向关联。一端使用 @OneToMany,多端使用 @ManyToOne。
在 JPA 规范中,一对多的双向关系由多端来维护。就是说多端为关系维护端,负责关系的增删改查。一端则为关系被维护端,不能维护关系。
注意:在使用一对多和多对一关联时,清除、忽略或者重写任意一方的 toString() 方法,否则会相互调用,造成栈溢出。
作者实体类:
package com.luochen.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.util.List;
/**
* @author 洛尘大大
* @description: 作者,删除作者,所有文章不复存在
* @date 2021/11/29 15:57
*/
@Entity
@Table(name = "author")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "author_id", nullable = false)
private Integer authorId;
@Column(name = "author_name",length = 50)
private String authorName;
// 级联保存、更新、删除、刷新、延迟加载。当删除用户,会级联删除该用户的所有文章
// 拥有mappedBy注解的实体类为关系被维护端
// mappedBy="author"中的author是Article中的author属性
@OneToMany(mappedBy = "author",cascade = CascadeType.ALL,fetch = FetchType.LAZY)
private List<Article> articleList;
}
文章实体类:
package com.luochen.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
/**
* @author 洛尘大大
* @description: 作者的文章,删除部分文章,作者仍然存在
* @date 2021/11/29 16:01
*/
@Entity
@Table(name = "article")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "article_id", nullable = false)
private Integer articleId;
@Column(name = "article_name", length = 50)
private String articleName;
// 可选属性optional=false,表示author不能为空。删除文章,不影响用户
@ManyToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH}, optional = false)
// 对应author表中的id
@JoinColumn(name = "author_id")
private Author author;
public String toString(){
return "1";
}
}
测试:
package com.luochen;
import com.luochen.dao.ArticleDao;
import com.luochen.dao.AuthorDao;
import com.luochen.model.Article;
import com.luochen.model.Author;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.LinkedList;
import java.util.List;
/**
* @author 洛尘大大
* @description: TODO
* @date 2021/11/29 16:49
*/
@SpringBootTest
public class ArticleDaoTest {
@Autowired
private ArticleDao articleDao;
@Autowired
private AuthorDao authorDao;
@Test
void articleTest(){
Author author = new Author(null,"冯玉祥",null);
Article article1 = new Article(null, "数学", author);
Article article2 = new Article(null, "语文", author);
Article article3 = new Article(null, "英语", author);
Article article4 = new Article(null, "化学", author);
List<Article> articleList=new LinkedList<>();
articleList.add(article1);
articleList.add(article2);
articleList.add(article3);
articleList.add(article4);
author.setArticleList(articleList);
Author save = authorDao.save(author);
Article article5 = new Article(null, "物理", author);
articleDao.save(article5);
System.out.println(save);
}
}
4.3 多对多
JPA 中使用 @ManyToMany 来注解多对多的关系,由一个关联表来维护。这个关联表的表名默认是:主表名 + 下划线 + 从表名。(主表是指关系维护端对应的表,从表指关系被维护端对应的表)。这个关联表只有两个外键字段,分别指向主表 ID 和从表 ID 。字段的名称默认为:主表名 + 下划线 + 主表中的主键列名,从表名 + 下划线 + 从表中的主键列名。
注意:
- 多对多关系中一般不设置级联保存、级联删除、级联更新等操作。
- 可以随意指定一方为关系维护端。
- 多对多关系的绑定由关系维护端来完成,关系被维护端不能绑定关系。
- 多对多关系的解除由关系维护端来完成,关系被维护端不能解除关系。
用户实体类:
package com.luochen.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.util.List;
/**
* @author 洛尘大大
* @description: 用户,用户可能有多个权限
* @date 2021/11/29 17:13
*/
@Entity
@Table(name = "user")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@Id
@Column(name = "user_id", nullable = false)
private Integer userId;
@Column(name = "user_name",length = 50)
private String userName;
// 关系维护端
@ManyToMany
// @JoinTable(),可以默认
private List<Authority> authorities;
}
权限实体类:
package com.luochen.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.util.List;
/**
* @author 洛尘大大
* @description: 权限
* @date 2021/11/29 17:16
*/
@Entity
@Table(name = "authority")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Authority {
@Id
@Column(name = "authority_id", nullable = false)
private Integer authorityId;
@Column(name = "authority_name")
private String authorityName;
// 关系被维护端
@ManyToMany(mappedBy = "authorities")
private List<User> users;
public String toString(){
return "2";
}
}
测试:
package com.luochen;
import com.luochen.dao.AuthorityDao;
import com.luochen.dao.UserDao;
import com.luochen.model.Authority;
import com.luochen.model.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.LinkedList;
import java.util.List;
/**
* @author 洛尘大大
* @description: 用户权限多对多测试
* @date 2021/11/29 17:27
*/
@SpringBootTest
public class UserDaoTest {
@Autowired
private UserDao userDao;
@Autowired
private AuthorityDao authorityDao;
@Test
void test(){
// 创建权限
Authority authority1 = new Authority(2001,"admin",null);
Authority authority2 = new Authority(2002,"admin2",null);
Authority authority3 = new Authority(2003,"admin3",null);
authorityDao.save(authority1);
authorityDao.save(authority2);
authorityDao.save(authority3);
// 将权限列表存储到用户中
List<Authority> authorityList=new LinkedList<>();
authorityList.add(authority1);
authorityList.add(authority2);
authorityList.add(authority3);
User user = new User(1001, "冯玉祥", authorityList);
userDao.save(user);
// 上下代码存在一个即可,否则会创建关联表不成功
// 将用户列表储存到权限中
//List<User> userList=new LinkedList<>();
//userList.add(user);
//authority.setUsers(userList);
//authorityDao.save(authority);
}
}