红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 专栏
  3. Spring Boot 学习笔记
  4. 正文

从零开始 Spring Boot 50:Entity Lifecyle Event

2023年6月28日 907点热度 0人点赞 0条评论

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

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: entity event spring
最后更新:2023年6月28日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇
下一篇 >

文章评论

取消回复

*

code

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号