1.概述
在ORM的框架中,数据库审计一般是指跟踪和记录与持久化实体相关的事件,或者仅仅是实体版本跟踪。受SQL触发器的启发,事件是对实体的插入,更新和删除操作,类似于源码版本控制。
我们将演示三种不同的审计方法。首先,我们将使用标准JPA实现它,接下来,我们将看两个提供自己的审计功能的JPA扩展:一个由Hibernate提供,另一个由Spring Data提供。
以下是将在此示例中使用的示例相关实体Bar和Foo:
2. 使用JPA进行审计
JPA没有明确包含审计API,但可以使用实体生命周期事件来实现功能。
2.1 @PrePersist, @PreUpdate 和 @PreRemove
在JPA Entity类中,实体对象生命周期在发生变化时会有相应的事件回调,比如相应的DML操作之前执行的回调,有@PrePersist,@PreUpdate和@PreRemove 分别对于 持久化前,更新前以及删除前:
1 |
|
这些内部回调方法没有返回值,且没有参数,它们可以具有任何名称和任何访问级别,但不应该是静态的。
请注意,JPA 中的 @Version 注解与我们的主题不一样 - 它与乐观锁定相关,而不是与审计数据相关。
2.2 实现回调方法
上一节提到的方法有一个限制,JPA 2 specification (JSR 317) 的相关描述如下:
In general, the lifecycle method of a portable application should not invoke EntityManager or Query operations, access other entity instances, or modify relationships within the same persistence context. A lifecycle callback method may modify the non-relationship state of the entity on which it is invoked.
意思就是生命周期回调方法不应该调用 EntityManager 或者 Query 的操作去修改其它的实例或者实例的关联关系。
在没有审计框架的情况下,我们必须手动维护数据库架构和域模型。对于我们的简单用例,让我们向实体添加两个新属性,因为我们只能管理“实体的非关系状态”。operation 属性将存储执行的操作的名称,timestamp属性是操作的时间戳:
1 |
|
如果需要将此类审计添加到多个类,则可以使用 @EntityListeners 来集中代码。例如:
1 | .class) (AuditListener |
3. Hibernate Envers
如果使用 Hibernate 框架,我们可以使用Interceptor 和 EventListeners 以及数据库触发器来完成审计。但是ORM框架提供了Envers,这是一个实现持久化类的审计和版本控制的模块。
3.1 Envers入门
要启用Envers,需要添加hibernate-envers 依赖:
1 | <dependency> |
然后在@Entity或特定的@Column上添加@Audited注释(如果您只需要审计特定属性):
1 |
|
请注意,Bar与Foo有一对多的关系。在这种情况下,我们要么通过在Foo上添加@Audited或在Bar的关系属性上设置@NotAudited来审计Foo:
1 | "bar") (mappedBy = |
3.2 创建审计日志表
有几种方法可以创建审计表:
- 设置hibernate.hbm2ddl.auto=create, create-drop or update,这样Envers就可以自动创建它们
- 使用o rg.hibernate.tool.EnversSchemaGenerator以编程方式导出完整的数据库Schema
- 使用Ant任务生成适当的DDL语句
- 使用Maven插件从映射(例如Juplo)生成数据库模式以导出Envers模式(与Hibernate 4及更高版本一起使用)
注意使用hibernate.hbm2ddl.auto在生产中是不安全的。
在我们的例子中,bar_AUD和foo_AUD(如果你将Foo设置为@Audited)表应该自动生成。审计表从实体表中复制所有审计字段,其中包含两个字段REVTYPE(值为:“0”表示添加,“1”表示更新,“2”表示删除实体)和REV。
除此之外,默认会生成一个名为REVINFO的表,它包括两个重要的字段REV和REVTSTMP,记录每次修改的时间戳。正如你猜测的那样,bar_AUD.REV和foo_AUD.REV实际上是REVINFO.REV的外键。
3.3 配置Envers
您可以像任何其他Hibernate属性一样配置Envers属性。例如,让我们将审计表后缀(默认为“ _AUD ”)更改为“ _AUDIT_LOG ”。比如设置相应属性org.hibernate.envers.audit_table_suffix的值:
1 | Properties hibernateProperties = new Properties(); |
在Envers文档中找到可用属性的完整列表。
3.4 访问实体历史记录
您可以通过类似于通过Hibernate条件API查询数据的方式查询历史数据。实体的审计历史记录可以使用AuditReader 接口访问,这个接口可以通过 的EntityManager 或Session 获取,如下所示:
1 | AuditReader reader = AuditReaderFactory.get(session); |
Envers提供AuditQueryCreator(由AuditReader.createQuery()返回)创建特定于审计的查询,如以所示将返回在修订版#2中修改的所有Bar实例(其中bar_AUDIT_LOG.REV = 2):
1 | AuditQuery query = reader.createQuery().forEntitiesAtRevision(Bar.class, 2) |
以下是如何查询Bar的修订版,即它将导致获取所有审计状态中所有Bar实例的列表:
1 |
|
如果第二个参数为false,则结果还会 join REVINFO表,否则,仅返回实体实例。最后一个参数指定是否返回已删除的Bar实例。
还可以使用 AuditEntity 工厂类指定约束:1
query.addOrder(AuditEntity.revisionNumber().desc());
4. Spring Data JPA
Spring Data JPA 是一个通过在JPA提供程序的顶部添加额外的抽象层来扩展JPA的框架。我们可以扩展CrudRepository <T,ID extends Serializable>,用于通用CRUD操作的接口。一旦您创建了 Repository 并将其注入另一个组件,Spring Data就会自动提供实现,您就可以添加审计功能了。
4.1 启用JPA审计
只需在 @Configuration 类上添加 @EnableJpaAuditing 即可启用 JPA 审计,如下所示:
1 |
|
4.2 添加Spring的实体回调监听器
我们已经知道,JPA提供@EntityListeners 注释来指定回调监听类。Spring Data 也提供了自己的JPA实体监听器类:AuditingEntityListener。所以让我们为Bar 实体指定监听器,如下所示:
1 |
|
现在,监听器将在持久化和更新Bar实体时捕获审计信息。
4.3 跟踪创建的和上次修改的日期
接下来,我们将添加两个新属性,用于将创建和最后修改日期存储到Bar实体。这些属性由@CreatedDate和@LastModifiedDate 进行注解:
1 |
|
通常,可以将这几个属性移动到基类(由@MappedSuperClass注释),该基类将由所有审计实体继承。在我们的示例中,为简单起见,我们将它们直接添加到Bar。
4.4 审计更改的作者
您不仅可以跟踪何时进行更改,还可以跟踪谁进行了修改,同样的,添加两个属性,并由 @CreatedBy 和 @LastModifiedBy 进行注解:
1 |
|
使用@CreatedBy和@LastModifiedBy注释的列将自动填充创建或上次修改实体的作者。当然,该作者信息需要开发者自己提供。实际上框架是会查找 Spring 上下文中的 AuditorAware
1 | public class AuditorAwareImpl implements AuditorAware<String> { |
根据不同的情况,可以从 Session 或者 SpringSecurity 框架的 Principal 中获取当前登录用户信息
为了配置该应用使用AuditorAwareImpl查找当前主体,@EnableJpaAuditing 中可以设置 auditorAwareRef属性为我们前面提到的 AuditorAware 实现类。
1 | "auditorProvider") (auditorAwareRef= |