Spring Boot 61:JPA 中的级联类型

这篇具有很好参考价值的文章主要介绍了Spring Boot 61:JPA 中的级联类型。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Spring Boot 61:JPA 中的级联类型

Spring Boot 61:JPA 中的级联类型,JAVA,spring boot,hibernate,cascadetype

图源:简书 (jianshu.com)

关系型数据库的增删改查操作会因为有关联关系而存在“级联操作”的需要,体现在 JPA 中,就是实体中会定义的级联类型(Cascade Type)。

JPA 中的级联类型由枚举jakarta.persistence.CascadeType表示,包括:

  • ALL
  • PERSIST
  • MERGE
  • REMOVE
  • REFRESH
  • DETACH

这些级联类型对应实体对象的状态转换操作,具体可以参考这篇文章。

ALL包含其他所有的操作。

下面详细说明这些级联类型的用途和影响。

示例

本文将使用以下的示例说明级联操作的影响:

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "student")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotNull
    @NotBlank
    @Length(max = 45)
    @Column(unique = true)
    private String name;

    @OneToMany(mappedBy = "student",
            fetch = FetchType.LAZY)
    @Builder.Default
    private List<Email> emails = new ArrayList<>();

    public Student addEmail(Email email){
        if (this.emails.contains(email)){
            return this;
        }
        this.emails.add(email);
        email.setStudent(this);
        return this;
    }
}

@Accessors(chain = true)
@Setter
@Getter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "email", uniqueConstraints = @UniqueConstraint(columnNames = {"name", "domain"}))
public class Email {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotNull
    @NotBlank
    @Length(max = 45)
    @EqualsAndHashCode.Include
    private String name;
    @NotBlank
    @NotNull
    @Length(max = 45)
    @EqualsAndHashCode.Include
    private String domain;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "student_id")
    private Student student;
}

这里包含两个实体,一个学生实体实例对应多个电子邮件实体实例。

关于一对多关系的更多介绍可以阅读这篇文章。

PERSIST

如果实体之间的关系不包含任何级联类型,添加一个实体时不会对另一个实体产生任何影响,换言之,添加学生实体实例后,只会插入学生相关表数据,电子邮件表不会有任何数据添加。

如果希望进行“级联添加”,需要使用级联类型CascadeType.PERSIST

public class Student {
    // ...
    @OneToMany(mappedBy = "student",
               cascade = CascadeType.PERSIST,
               fetch = FetchType.LAZY)
    private List<Email> emails = new ArrayList<>();	
}

现在,添加新的Student实例时,就会一同添加相关的Email实例到数据库。

测试用例:

students.forEach(s -> {
    session.persist(s);
});

SQL 日志:

insert into student (name) values (?)
binding parameter [1] as [VARCHAR] - [icexmoon]
insert into email (domain,name,student_id) values (?,?,?)
binding parameter [1] as [VARCHAR] - [qq.com]
binding parameter [2] as [VARCHAR] - [icexmoon]
binding parameter [3] as [BIGINT] - [1]
insert into email (domain,name,student_id) values (?,?,?)
binding parameter [1] as [VARCHAR] - [qq.com]
binding parameter [2] as [VARCHAR] - [123]
binding parameter [3] as [BIGINT] - [1]
...

当然,JPA 的 persist API 还包含对持久实体的更新操作,此时同样适用CascadeType.PERSIST级联类型:

var icexmoon = students.stream().filter(s -> 		s.getName().equals("icexmoon")).findFirst().get();
long id = icexmoon.getId();
var savedIcexmoon = session.find(Student.class, id);
var icexmoonEmail = savedIcexmoon.getEmails().get(0);
icexmoonEmail.setName("111")
    .setDomain("gmail.com");
session.persist(icexmoonEmail);

SQL 日志:

update email set domain=?,name=?,student_id=? where id=?
binding parameter [1] as [VARCHAR] - [gmail.com]
binding parameter [2] as [VARCHAR] - [111]
binding parameter [3] as [BIGINT] - [1]
binding parameter [4] as [BIGINT] - [1]

当然,使用JPARepository先关的 API 同样是可以的:

studentRepository.saveAndFlush(student);
student.getEmails().get(0)
    .setName("111")
    .setDomain("gmail.com");
studentRepository.saveAndFlush(student);

同样会进行级联插入/更新。

MERGE

CascadeType.MERGE对应 JPA 持久化上下文的merge操作。

示例:

public class Student {
    // ...
    @OneToMany(mappedBy = "student",
            cascade = CascadeType.MERGE,
            fetch = FetchType.LAZY)
    private List<Email> emails = new ArrayList<>();
}

调用示例:

var savedIcexmoon = session.find(Student.class, icexmoon.getId());
session.evict(savedIcexmoon);
savedIcexmoon.getEmails().get(0).setName("111").setDomain("gmail.com");
session.merge(savedIcexmoon);

SQL 日志:

update email set domain=?,name=?,student_id=? where id=?
binding parameter [1] as [VARCHAR] - [gmail.com]
binding parameter [2] as [VARCHAR] - [111]
binding parameter [3] as [BIGINT] - [1]
binding parameter [4] as [BIGINT] - [1]

REMOVE

CascadeType.REMOVE对应持久化上下文的remove操作。

示例:

public class Student {
    // ...
    @OneToMany(mappedBy = "student",
            cascade = {CascadeType.MERGE,
                    CascadeType.PERSIST,
                    CascadeType.REMOVE},
            fetch = FetchType.EAGER)
    private List<Email> emails = new ArrayList<>();
}

调用示例:

var savedIcexmoon = session.find(Student.class, icexmoon.getId());
session.remove(savedIcexmoon);

SQL 日志:

select s1_0.id,s1_0.name,e1_0.student_id,e1_0.id,e1_0.domain,e1_0.name from student s1_0 left join email e1_0 on s1_0.id=e1_0.student_id where s1_0.id=?
binding parameter [1] as [BIGINT] - [1]
delete from email where id=?
binding parameter [1] as [BIGINT] - [1]
delete from email where id=?
binding parameter [1] as [BIGINT] - [2]
delete from student where id=?
binding parameter [1] as [BIGINT] - [1]
...

DETACH

CascadeType.DETACH对应持久化上下文的detachevict操作,这些操作可以将持久实体从持久上下文中移除,变成分离实体。

evictdetach操作的别名,两者没有什么区别。

示例:

public class Student {
	// ...
    @OneToMany(mappedBy = "student",
               cascade = {CascadeType.MERGE,
                          CascadeType.PERSIST,
                          CascadeType.REMOVE,
                          CascadeType.DETACH},
               fetch = FetchType.EAGER)
    private List<Email> emails = new ArrayList<>();
}

调用示例:

var savedIcexmoon = session.find(Student.class, icexmoon.getId());
Assertions.assertTrue(session.contains(savedIcexmoon));
savedIcexmoon.getEmails().forEach(e->{
    Assertions.assertTrue(session.contains(e));
});
session.detach(savedIcexmoon);
Assertions.assertFalse(session.contains(savedIcexmoon));
savedIcexmoon.getEmails().forEach(e->{
    Assertions.assertFalse(session.contains(e));
});

REFRESH

CascadeType.REFRESH对应持久化上下文从数据库中重新加载数据的操作,比如Session.refresh(...)

示例:

public class Student {
    // ...
    @OneToMany(mappedBy = "student",
               cascade = {CascadeType.MERGE,
                          CascadeType.PERSIST,
                          CascadeType.REMOVE,
                          CascadeType.DETACH},
               fetch = FetchType.EAGER)
    @Builder.Default
    private List<Email> emails = new ArrayList<>();
}

调用示例:

var savedIcexmoon = session.find(Student.class, icexmoon.getId());
savedIcexmoon.setName("lalala");
var savedEmail = savedIcexmoon.getEmails().get(0);
savedEmail.setName("666").setDomain("gmail.com");
var oldEmailName = savedEmail.getName();
var oldEmailDomain = savedEmail.getDomain();
Assertions.assertEquals("lalala", savedIcexmoon.getName());
Assertions.assertEquals("666", savedEmail.getName());
Assertions.assertEquals("gmail.com", savedEmail.getDomain());
session.refresh(savedIcexmoon);
Assertions.assertEquals("icexmoon", savedIcexmoon.getName());
Assertions.assertEquals(oldEmailName, savedEmail.getName());
Assertions.assertEquals(oldEmailDomain, savedEmail.getDomain());

可以看到,调用Session.refresh后,关联到Student上的Email实例也被重新加载。

此外,Hibernate 提供了一些独特于 JPA 的级联类型,这些类型由枚举类型org.hibernate.annotations.CascadeType表示,大部分不同的级联类型已经作废,剩余的与 JPA 不同的级联类型有CascadeType.LOCK,要使用这些类型可以参考这篇文章。

The End,谢谢阅读。

本文的完整示例代码可以从这里获取。文章来源地址https://www.toymoban.com/news/detail-525051.html

参考资料

  • Overview of JPA/Hibernate Cascade Types. | Baeldung
  • 从零开始 Spring Boot 49:Hibernate Entity Lifecycle - 红茶的个人站点 (icexmoon.cn)

到了这里,关于Spring Boot 61:JPA 中的级联类型的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Spring Boot 实战 | Spring Boot整合JPA常见问题解决方案

    专栏集锦,大佬们可以收藏以备不时之需: Spring Cloud 专栏: Python 专栏: Redis 专栏: TensorFlow 专栏: Logback 专栏: 量子计算: 量子计算 | 解密著名量子算法Shor算法和Grover算法 AI机器学习实战: AI机器学习实战 | 使用 Python 和 scikit-learn 库进行情感分析 AI机器学习 | 基于lib

    2024年02月04日
    浏览(58)
  • Jpa与Druid线程池及Spring Boot整合(一): spring-boot-starter-data-jpa 搭建持久层

                          Jpa与Druid线程池及Spring Boot整合(一) Jpa与Druid线程池及Spring Boot整合(二):几个坑 附录官网文档:core.domain-events域事件 docker实战(一):centos7 yum安装docker docker实战(二):基础命令篇 docker实战(三):docker网络模式(超详细) docker实战(四):docker架构原理 docker实战(五

    2024年02月13日
    浏览(52)
  • Jpa与Druid线程池及Spring Boot整合(二): spring-boot-starter-data-jpa 踏坑异常处理方案

                         docker实战(一):centos7 yum安装docker docker实战(二):基础命令篇 docker实战(三):docker网络模式(超详细) docker实战(四):docker架构原理 docker实战(五):docker镜像及仓库配置 docker实战(六):docker 网络及数据卷设置 docker实战(七):docker 性质及版本选择 认知升维: 道、法、

    2024年02月13日
    浏览(57)
  • 在Spring Boot项目中使用JPA

    Spring Boot提供了启动器spring-boot-starter-data-jpa,只需要添加启动器(Starters)就能实现在项目中使用JPA。下面一步一步演示集成Spring Data JPA所需的配置。 步骤01 添加JPA依赖。 首先创建新的Spring Boot项目,在项目的pom.xml中增加JPA相关依赖,具体代码如下:

    2024年02月09日
    浏览(57)
  • Spring Boot 篇四: Spring Data JPA使用SQL Server

    本篇介绍篇一至篇三中用到的JPA链接SQL Server的具体情况以及实战过程中可能遇到的问题。 具体的下载和安装教程,请参阅微软SQL Server官网; SQL Server Express 是免费的,并且配套的SQL Server Management Studio也是可以用的。 呃,当然,使用Docker来运行SQL Server是另外一条路径。具体

    2024年02月05日
    浏览(95)
  • 【Spring Boot】SpringBoot和数据库交互: 使用Spring Data JPA

    在现代应用程序的开发中,数据是核心部分。为了能够持久化、检索、更新和删除数据,应用程序需要与数据库进行交互。 1.1 为什么需要数据库交互 数据持久化 :当你关闭应用程序或者服务器时,你仍希望数据能够保存。数据库提供了一个持久的存储方案,使得数据在关闭

    2024年02月12日
    浏览(50)
  • 国庆中秋特辑(八)Spring Boot项目如何使用JPA

    国庆中秋特辑系列文章: 国庆中秋特辑(八)Spring Boot项目如何使用JPA 国庆中秋特辑(七)Java软件工程师常见20道编程面试题 国庆中秋特辑(六)大学生常见30道宝藏编程面试题 国庆中秋特辑(五)MySQL如何性能调优?下篇 国庆中秋特辑(四)MySQL如何性能调优?上篇 国庆

    2024年02月08日
    浏览(54)
  • Spring Boot集成JPA和ClickHouse数据库

    Spring Boot是一个用于创建独立的、基于Spring的应用程序的框架。它具有快速开发特性,可以大大减少开发人员的工作量。JPA(Java Persistence API)是Java中处理关系型数据库持久化的标准规范,而ClickHouse是一个高性能、分布式的列式数据库。 本文将介绍如何在Spring Boot项目中集成

    2024年02月09日
    浏览(52)
  • Spring Boot整合JPA 与 JpaRepository 基础方法介绍

    1. 什么是JPA    JPA(Java Persistence API, Java 持久化API)是SUN公司提出的Java持久化规范,它提供了一种对象/关系映射的管理工具来管理Java中的关系型数据库。JPA的主要目的是简化现有的持久化开发工作并且整合ORM框架,JPA本身并不是ORM框架,它是一种规范,这种规范可以私下

    2024年02月09日
    浏览(45)
  • Spring Boot:利用JPA进行数据库的查删

    DAO 层负责数据库访问,它 封装了对数据库的访问操作 ,例如查询、插入、更新和删除等。 Service 层负责业务逻辑, Service 层位于 DAO 层之上 ,Service 层可以 调用多个 DAO 层的接口 来完成复杂的业务操作,也可以将多个 DAO 层的接口组合成一个新的接口,并将其返回给客户端

    2024年02月07日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包