JPA(三)-审计功能
参考文献
- 拉钩教育–Spring Data JPA原理与实战
JAP审计功能
-
Auditing 是帮我们做审计用的,当我们操作一条记录的时候,需要知道这是谁创建的、什么时间创建的、最后修改人是谁、最后修改时间是什么时候,甚至需要修改记录……这些都是 Spring Data JPA 里面的 Auditing 支持的,它为我们提供了四个注解来完成上面说的一系列事情,如下:
-
@CreatedBy
是哪个用户创建的。 -
@CreatedDate
创建的时间。 -
@LastModifiedBy
最后修改实体的用户。 -
@LastModifiedDate
最后一次修改的时间。
-
具体实现步骤
1 | 第一种方式:直接在实例里面添加上述四个注解 |
1 | 第二种方式:实体里面实现Auditable 接口 |
1 | 第三种方式:利用 注解 |
Java Persistence API 里面规定的回调方法
@PrePersist
@PostPersist
@PreRemove
@PostRemove
@PreUpdate
@PostUpdate
@PostLoad
语法注意事项
- 回调函数都是和 EntityManager.flush 或 EntityManager.commit 在同一个线程里面执行的,只不过调用方法有先后之分,都是同步调用,所以当任何一个回调方法里面发生异常,都会触发事务进行回滚,而不会触发事务提交。
- Callbacks 注解可以放在实体里面,可以放在 super-class 里面,也可以定义在 entity 的 listener 里面,但需要注意的是:放在实体(或者 super-class)里面的方法,签名格式为“void ()”,即没有参数,方法里面操作的是 this 对象自己;放在实体的 EntityListener 里面的方法签名格式为“void (Object)”,也就是方法可以有参数,参数是代表用来接收回调方法的实体。
- 使上述注解生效的回调方法可以是 public、private、protected、friendly 类型的,但是不能是 static 和 finnal 类型的方法。
示例
1 | 第一种用法:在实体和 super-class 中使用 |
1 | 第二种用法:自定义 EntityListener |
-
关于 @EntityListeners 加载顺序的说明
-
默认如果子类和父类都有 EntityListeners,那么 listeners 会按照加载的顺序执行所有 EntityListeners;
-
EntityListeners 和实体里面的回调函数注解可以同时使用,但需要注意顺序问题;
-
如果我们不想加载super-class里面的EntityListeners,那么我们可以通过注解 @ExcludeSuperclassListeners,排除所有父类里面的实体监听者,需要用到的时候,我们再在子类实体里面重新引入即可,代码如下:
1
2
3
4
public class User extends BaseEntity {
......
}
-
JPA Callbacks 的最佳实践
-
注意回调函数方法要在同一个事务中进行,异常要可预期,非可预期的异常要进行捕获,以免出现意想不到的线上 Bug;
-
回调函数方法是同步的,如果一些计算量大的和一些耗时的操作,可以通过发消息等机制异步处理,以免阻塞主流程,影响接口的性能。比如上面说的日志,如果我们要将其记录到数据库里面,可以在回调方法里面发个消息,改进之后将变成如下格式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30public class AuditLoggingListener {
private void postLoad(Object entity) {
this.notice(entity, OperateType.load);
}
private void postPersist(Object entity) {
this.notice(entity, OperateType.create);
}
private void PostRemove(Object entity) {
this.notice(entity, OperateType.remove);
}
private void PostUpdate(Object entity) {
this.notice(entity, OperateType.update);
}
private void notice(Object entity, OperateType type) {
//我们通过active mq 异步发出消息处理事件
ActiveMqEventManager.notice(new ActiveMqEvent(type, entity));
}
enum OperateType {
create("创建"), remove("删除"),update("修改"),load("查询");
private final String description;
OperateType(String description) {
this.description=description;
}
}
}-
在回调函数里面,尽量不要直接在操作 EntityManager 后再做 session 的整个生命周期的其他持久化操作,以免破坏事务的处理流程;也不要进行其他额外的关联关系更新动作,业务性的代码一定要放在 service 层面,否则太过复杂,时间长了代码很难维护;
-
回调函数里面比较适合用一些计算型的transient方法,如下面这个操作:
1
2
3
4
5
6
7public class UserListener {
public void prePersist(User user) {
//通过一些逻辑计算年龄;
user.calculationAge();
}
}
-