爬取页面中...

JPA框架学习


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);
    }
}

  目录