从零开始 Spring Boot 50:Entity Lifecyle Event

news2024/11/20 14:29:20

从零开始 Spring Boot 50:Entity Lifecyle Event

spring boot

图源:简书 (jianshu.com)

在上篇文章,我介绍了 Hibernate 中的实体生命周期以及可以转换实体状态的 Session API。就像 Spring Bean 的生命周期拥有一些事件,通过监听这些事件我们可以在其不同时期用回调执行一些代码。在 Hibernate 实体的生命周期中同样有一些事件可以监听和回调,接下来我会介绍这些事件以及其用途。

实体生命周期事件

Hibernate (JPA)的实体生命周期(Entity Lifecycle)有如下事件(Event):

  • @PrePersist,保存新的实体到数据库前调用。
  • @PostPersist,新的实体被保存到数据库后被调用。
  • @PreRemove,实体被从数据库中删除前被调用。
  • @PostRemove,实体被从数据库中删除后被调用。
  • @PreUpdate,实体发生改变,更新数据库中数据前被调用。
  • @PostUpdate,实体发生改变,更新数据库中数据后被调用。
  • @PostLoad,实体从数据库中加载后被调用。

就像展示的那样,这些事件都对应一个注解,可以使用注解来定义事件监听。

在实体中定义事件

监听实体生命周期事件的最简单方式是在实体中定义方法,并使用相应的事件注解:

// ...
public class Student {
	// ...
    @PrePersist
    public void prePersist() {
        log.info("New student %s will be add.".formatted(this));
    }

    @PostPersist
    public void postPersist() {
        log.info("New student %s is already added.".formatted(this));
    }

    @PreRemove
    public void preRemove() {
        log.info("Student %s will be removed.".formatted(this));
    }

    @PostRemove
    public void postRemove() {
        log.info("Student %s is already removed.".formatted(this));
    }

    @PreUpdate
    public void preUpdate() {
        log.info("Student %s is will be updated.".formatted(this));
    }

    @PostUpdate
    public void postUpdate() {
        log.info("Student %s is already updated.".formatted(this));
    }

    @PostLoad
    public void postLoad() {
        log.info("Student %s is loaded.".formatted(this));
    }
}

Student是一个实体类,这里只展示关键代码,完整代码可以阅读这里。

在这个简单示例中,事件监听只打印日志。

下面测试这些事件监听:

测试 Persist 事件

@Test
void testPersistEvent() {
    Student student = new Student("lalala", LocalDate.of(2002, 1, 1), Gender.MALE);
    studentRepository.save(student);
}

输出:

... New student Student(id=null, name=lalala, birthDay=2002-01-01, age=21, gender=MALE) will be add.
Hibernate: insert into user_student (birth_day,gender,name,id) values (?,?,?,?)
... New student Student(id=3905, name=lalala, birthDay=2002-01-01, age=21, gender=MALE) is already added.

可以看到,通过存储库(Repository)保存新实体的时候,@PrePersist@AfterPersist依次被执行,并且中间夹杂着 INSERT SQL 语句的执行日志,并且在@AfterPersist输出的日志中我们可以看到,实体的 ID 已经被分配。

测试 Remove 事件

@Test
void testRemoveEvent() {
    Student student = studentRepository
        .findOne(Example.of(new Student("icexmoon", null, null)))
        .get();
    studentRepository.delete(student);
}

输出:

Hibernate: select s1_0.id,s1_0.birth_day,s1_0.gender,s1_0.name from user_student s1_0 where s1_0.name=? limit ?
... -- Student Student(id=3952, name=icexmoon, birthDay=1989-10-01, age=34, gender=MALE) is loaded.
Hibernate: select s1_0.id,s1_0.birth_day,s1_0.gender,s1_0.name from user_student s1_0 where s1_0.id=?
... -- Student Student(id=3952, name=icexmoon, birthDay=1989-10-01, age=34, gender=MALE) is loaded.
... -- Student Student(id=3952, name=icexmoon, birthDay=1989-10-01, age=34, gender=MALE) will be removed.
Hibernate: delete from user_student where id=?
... -- Student Student(id=3952, name=icexmoon, birthDay=1989-10-01, age=34, gender=MALE) is already removed.

可以看到,使用JPARepository.findOne方法查找实体触发了两次@PostLoad事件,第一次是通过 SELECT SQL 按照Example指定的name属性查找,第二次通过 SELECT SQL 通过第一次查到的实体的id属性查找。之后调用delete方法删除实体,触发了@PreRemove事件,并在数据库中执行 DELETE SQL,之后触发@PostRemove事件。

测试 Update 事件

Student student = studentRepository
.findOne(Example.of(new Student("icexmoon", null, null)))
.get();
student.setBirthDay(LocalDate.of(1990,1,1));
student.setName("moyuhongcha");
studentRepository.save(student);

输出:

... -- Student Student(id=4002, name=moyuhongcha, birthDay=1990-01-01, age=34, gender=MALE) is will be updated.
Hibernate: update user_student set birth_day=?,gender=?,name=? where id=?
... -- Student Student(id=4002, name=moyuhongcha, birthDay=1990-01-01, age=34, gender=MALE) is already updated.

这里省略@PostLoad事件的输出。

同样使用存储库的save方法,但这里处理的实体是一个从数据库中读取的持久实体,并且修改了其属性。可以看到调用save方法后,先触发了@PrePersist事件,之后执行了 UPDATE SQL,最后触发了@PostPersist事件。

定义单独的事件监听

除了在实体类中直接定义事件监听,还可以定义单独的事件监听,并在实体类中“注册”。这样做的好处是可以重复使用这些事件监听(用于多个实体)。

定义事件监听类:

@Log4j2
public class EntityEventListener {
    @PrePersist
    public void prePersist(Object entity) {
        log.info("New Entity %s will be add.".formatted(entity));
    }

    @PostPersist
    public void postPersist(Object entity) {
        log.info("New Entity %s is already added.".formatted(entity));
    }

    @PreRemove
    public void preRemove(Object entity) {
        log.info("Entity %s will be removed.".formatted(entity));
    }

    @PostRemove
    public void postRemove(Object entity) {
        log.info("Entity %s is already removed.".formatted(entity));
    }

    @PreUpdate
    public void preUpdate(Object entity) {
        log.info("Entity %s is will be updated.".formatted(entity));
    }

    @PostUpdate
    public void postUpdate(Object entity) {
        log.info("Entity %s is already updated.".formatted(entity));
    }

    @PostLoad
    public void postLoad(Object entity) {
        log.info("Entity %s is loaded.".formatted(entity));
    }
}

这个类并没有什么特殊的,不用继承或实现任何已有的类/接口,直接定义监听相关的处理方法并用相关事件注解标记即可。

在实体类中注册监听类也很容易:

@EntityListeners(EntityEventListener.class)
// ...
public class Student {
	// ...
}

以上的另种方式可以同时(混合)使用。

最后,要说明的是这些事件相关注解可以在一个方法上同时使用多个,比如:

@PrePersist
@PreUpdate
@PreRemove
private void beforeAnyUpdate(User user) {
    if (user.getId() == 0) {
        log.info("[USER AUDIT] About to add a user");
    } else {
        log.info("[USER AUDIT] About to update/delete user: " + user.getId());
    }
}

这个示例来自JPA Entity Lifecycle Events | Baeldung。

测试用例可以重用,且不需要做任何修改,所以这里不再重复展示。

The End,谢谢阅读。

可以从这里获取本文的完整示例代码。

参考资料

  • JPA Entity Lifecycle Events | Baeldung

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/699231.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C# 中【委托】的简单理解

先不说 C# 中的委托编程,先从生活中的例子入手。 场景一: 防疫期间,外卖人员不能进入花园小区。外卖小哥到了花园小区门口,只好【委托】花园的保安人员,把东西送上楼去。 场景二: 有资格的人,都…

慢谈漫威--来龙去脉

慢谈漫威 背景介绍一、抛开电影看漫威相关公司背景、关系二、漫威宇宙和索尼漫威宇宙三、 漫威宇宙四、话外--美国6大影视公司 背景介绍 最近在看一部漫威电影,可能是年纪大了,看美国英雄大片以及系列电影提不起兴趣,各种人物背景关系也不清…

途乐证券|光伏概念发力上扬,好利科技涨停,隆基绿能等拉升

光伏概念28日盘中发力上扬,截至发稿,露笑科技、好利科技、宁波能源等涨停,中国动力、欧晶科技、聚合材料、鹿山新材等涨超5%,帝科股份、隆基绿能、福斯特等涨超3%。 露笑科技昨日晚间披露的半年度业绩预告,预计上半年归…

ST - 如何下载带有.ioc的官方工程

文章目录 ST - 如何下载带有.ioc的官方工程概述下载带有.ioc文件的官方工程的方法备注END ST - 如何下载带有.ioc的官方工程 概述 有些CubeMX的官方工程, 自己复现不出来. 但是官方工程是好使的. 这是, 可能差别就在工程配置上. 如果仅仅靠BC4比对官方工程和自己重建的工程, …

简单指令实现Docker构建镜像启动运行保存导出后再导入新环境完整全流程

项目场景及问题描述 本文做一个简单Docker使用指令指南,可快速实现Docker构建镜像、启动、运行、保存、导出后再导入新环境完整全流程。具体每一个指令又有很多参数和学问,大家可自行查询更详细的解释,本文可用于小白快速构建镜像并使用。 使…

【doxygen】doxygen 支持 markdown 公式

文章目录 配置 doxygenmarkdown 文件中插入公式在段落中插入公式公式居中显示公式分行显示效果展示 参考链接 配置 doxygen 在 Doxygen 配置文件(通常是 Doxyfile )中,确保以下选项已启用或设置: MARKDOWN_SUPPORT :设…

100种思维模型之心流理论思维模型-86

亚里士多德说, 人类的终极目标是获得幸福 ! 那么到底什么是幸福?如何才能获得幸福? 米哈里契克森米哈赖, 积极心理学奠基人之一, “心流” 理论的提出者,他在其著作《心流》一书中这样写道…

操作系统4——存储器管理

本系列博客重点在深圳大学操作系统课程的核心内容梳理,参考书目《计算机操作系统》(有问题欢迎在评论区讨论指出,或直接私信联系我)。 梗概 本篇博客主要介绍操作系统第四章存储器管理与第五章虚拟存储器的相关知识。 目录 一、…

辛苦写的文章在自媒体上难以盈利的原因分析!

自媒体是指个人或组织通过互联网自主开设自己的媒体平台,并在其平台上发布原创内容,获取流量并从中获得收益的一种模式。自媒体的发展可以让更多的人有机会成为内容创作者,并能够通过自己的努力获得收益,但实际情况是,…

Linux ps命令常见实战用法

文章目录 一、基本介绍1.1 基本介绍1.2 常用参数1.3 字段含义 二、常见用法2.1 查看所有进程2.2 查看特定进程信息 参考资料 Linux中的ps命令是Process Status的缩写。当程序运行在系统上时,我们称之为进程。想监测这些进程,需要熟悉ps命令的用法。ps能输…

2023一造土建安装案例《十年真题▪九套模拟》

造价工程师,是指通过全国统一考试取得中华人民共和国造价工程师执业资格证书,并经注册后从事建设工程造价工作的专业人员。国家对造价工程师实行准入类职业资格制度,纳入国家职业资格目录。凡从事工程建设活动的建设、设计、施工、造价咨询等…

合合信息智能文字识别产品通过中国信通院“可信AI—OCR智能化服务”评估

近年来,我国对数据的重视程度不断加强。2022年1月,国务院印发的《“十四五”数字经济发展规划》进一步提出,到2025年要初步建立数据要素市场体系,并对充分发挥数据要素价值作出重要部署。然而,现阶段有大量的数据信息以…

Java ~ Executor ~ AbstractExecutorService【总结】

前言 文章 相关系列:《Java ~ Executor【目录】》(持续更新)相关系列:《Java ~ Executor ~ AbstractExecutorService【源码】》(学习过程/多有漏误/仅作参考/不再更新)相关系列:《Java ~ Execu…

《痞子衡嵌入式半月刊》 第 78 期

痞子衡嵌入式半月刊: 第 78 期 这里分享嵌入式领域有用有趣的项目/工具以及一些热点新闻,农历年分二十四节气,希望在每个交节之日准时发布一期。 本期刊是开源项目(GitHub: JayHeng/pzh-mcu-bi-weekly),欢迎提交 issue&#xff0c…

2023双态IT北京用户大会回顾(三) | 银行分布式核心智能运维体系思考

​文末附有本场专题演讲视频 2023第五届双态IT北京用户大会,擎创科技专场演讲回顾之三:《银行分布式核心智能运维体系思考》,演讲嘉宾:擎创科技生态合作部总经理 冯陈湧 一、前言:分布式云原生快速发展催生更高的业务要…

解密Docker容器网络

一个Linux容器能看见的“网络栈”,被隔离在它自己的Network Namespace中。 1 “网络栈”的内容 网卡(Network Interface)回环设备(Loopback Device)路由表(Routing Table)iptables规则 对于一…

【PCB专题】Allegro如何设置电源电压属性,将飞线隐藏?

在PCB设计过程中,布局完成之后的布线的顺序一般是先走信号线,然后进行电源的处理、分割。因为电源往往在整个板子上是都有的,所以电源的飞线是非常多,在布线时特别影响其他信号线的布线,界面看着比较杂乱。 如下所示GND和1.8V都存在各种飞线,比较杂乱。 Allegro中有设置…

随想012:断言

C 标准库提供了名为 assert 的断言宏; C# 语言提供了名为 Debug.Assert 的断言方法; Java 语言提供名为 assert 的断言关键字。 主流编程语言不约而同的在语言层面上提供了 断言 机制。 David R. Jamson,编译器 Icc 的开发者之一&#xf…

掌握IO流这一篇就够了

IO流(几种常见的流) IO流概述IO的分类顶级父类 字节流、字符流字节输出流OutputStream字节输入流InputStreamFileOutputStream类FileOutputStream**写出字节数据** FileInputStream类FileInputStream读取字节数据复制图片 字符流字符输入流Reader字符输出…

主动式和被动式电容笔哪个好用?苹果平板平替笔排行

被动式电容笔与主动式电容笔最大的不同在于主动式电容笔具有更广泛的应用领域,可以与不同种类的电容屏幕进行匹配。随着人们对其了解的不断深入,其应用也日益广泛。除此之外,平替电容笔的技术,也在不断的改进和提高,逐…