从零开始 Spring Boot 50:Entity Lifecyle Event
图源:简书 (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