Hibernate Envers项目旨在实现对持久类的简单审计。它完全消除了审计实体的麻烦。

以下部分概述了使用自定义修订实体通过 Spring 引导配置 Envers 的高级步骤。它演示了在涉及多个数据源时如何配置 Envers。

Envers 入门

如果您使用的是 Maven,请在 pom 中添加以下 Envers 配置.xml

org.hibernate
hibernate-envers

创建自定义修订实体

在许多情况下,您需要自定义修订实体,因为默认修订实体字段可能不够。下面是创建自定义修订实体的示例。

该示例对 id 使用序列,但可以使用不同的策略来生成 id。

请注意 Envers 用于创建修订实体并将值保留在数据库中的@RevisionNumber和@RevisionEntity。

package com.example.service.audit.entity

@Table(name = "app_user_rev_entity", schema = "application")
@Entity
@RevisionEntity(UserRevisionListener.class)
public class UserRevEntity implements Serializable {

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_rev_generator")
@SequenceGenerator(name = "user_rev_generator", allocationSize = 10,sequenceName = "app_userrev_seq")
@RevisionNumber
private int id;

@RevisionTimestamp
@Temporal(TemporalType.TIMESTAMP)
private Date date;

@Column(name = "user_name")
private String userName;

@Column(name = "user_id")
private Long userId;

// Getters, setters, equals, hashcode ….

UserRevisionListener 是填充 UserRevEntity 的所有自定义属性的类。

下面的示例使用 Spring Boot 主体用户来获取用户名。同样,也可以填充其他属性。该类应从 Hibernate Envers 实现 RevisionListener 接口。

package com.example.service.audit.entity

public class UserRevisionListener implements RevisionListener {

/**
* @see org.hibernate.envers.RevisionListener#newRevision(java.lang.Object)
*/
@Override
public void newRevision(Object userRevision) {

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
User authenticatedUser = (User) authentication.getPrincipal();

UserRevEntity userRevEntity = (UserRevEntity) userRevision;
userRevEntity.setUserName(authenticatedUser.getUsername());
userRevEntity.setDate(Calendar.getInstance(TimeZone.getTimeZone("UTC")).getTime());
}

配置应用程序属性

以下是一些特定于Hibernate Envers的配置。可在此处找到有关配置属性的更多详细信息。

spring.jpa.properties.org.hibernate.envers.revision_type_field_name=revision_type
spring.jpa.properties.org.hibernate.envers.revision_field_name=revision_id
spring.jpa.properties.org.hibernate.envers.modified_flag_suffix=_mod

spring.jpa.properties.org.hibernate.envers.audit_strategy=org.hibernate.envers.strategy.ValidityAuditStrategy

如果您希望Hibernate为您创建自定义修订实体和其他审计表,则可以使用spring.jpa.hibernate.ddl-auto=update,但这不是我建议用于生产环境的内容。最好在生产环境中自行创建表。

下一节是关于审计策略的。默认值很好,但有效性审计策略是一种更高级的策略。

配置要审核的实体

下面是如何审核实体类的示例。我们需要做的就是在类级别添加@Audited注释(如果必须审核所有属性)或将注释添加为单个属性级别。

@Entity
@Table(name = "app_user", schema = "application")
public class UserEntity implements Serializable {

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_generator")
@SequenceGenerator(name = "user_generator", allocationSize = 1, sequenceName = "application.app_userid_seq")
@Column(name = "user_id")
private Long userId;

@NotNull
@Column(name = "first_name")
@Audited
private String firstName;

使用多个数据源配置环境

在许多应用程序中,您将拥有多个数据源,因为您可能希望在不同的架构甚至不同的数据库中存储不同的数据。以下部分概述了使用多个数据源配置 Envers 所涉及的步骤。

在应用程序属性文件中配置多个数据源。

application.spring.datasource.url = jdbc:postgresql://xxxx
application.spring.datasource.username = xxx
application.spring.datasource.password=xxx application.spring.datasource.testWhileIdle = true
application.spring.datasource.validationQuery = SELECT 1
application.spring.datasource.schema=application
application.spring.datasource.driver-class-name=org.postgresql.Driver

example.spring.datasource.url = jdbc:postgresql://xxx
example.spring.datasource.username = xxx
example.spring.datasource.password=xxx
example.spring.datasource.testWhileIdle = true
example.spring.datasource.validationQuery = SELECT 1
example.spring.datasource.schema=public
example.spring.datasource.driver-class-name=org.postgresql.Driver

以下是 Spring 引导识别和加载要使用的数据源所需的配置。

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "exampleEntityManagerFactory", transactionManagerRef = "exampleTransactionManager", basePackages = {
"com.example.service.example.entity" })
public class ExampleDatabaseConfig {

@Bean(name = "exampleDataSource")
@ConfigurationProperties(prefix = "example.spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}

@Bean(name = "exampleEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean exampleEntityManagerFactory(EntityManagerFactoryBuilder builder,
@Qualifier("exampleDataSource") DataSource dataSource) {
return builder.dataSource(dataSource).packages("com.example.service.audit.entity").persistenceUnit("example").build();
}

@Bean(name = "exampleTransactionManager")
public PlatformTransactionManager exampleTransactionManager(
@Qualifier("exampleEntityManagerFactory") EntityManagerFactory exampleEntityManagerFactory) {
return new JpaTransactionManager(exampleEntityManagerFactory);
}

}

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "applicationEntityManagerFactory", transactionManagerRef = "applicationTransactionManager", basePackages = {
"com.application.service.example.entity "
})
public class ApplicationDatabaseConfig {

@Primary
@Bean(name = "applicationDataSource")
@ConfigurationProperties(prefix = "application.spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}

@Primary
@Bean(name = "applicationEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder,
@Qualifier("applicationDataSource") DataSource dataSource) {
return builder.dataSource(dataSource)
.packages("com.example.service.audit.entity")
.persistenceUnit("application").build();
}

@Primary
@Bean(name = "applicationTransactionManager")
public PlatformTransactionManager transactionManager(
@Qualifier("applicationEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}

在上述两种配置中,要记住的重要部分是添加实现 Envers 自定义修订实体类的包 — 在本例中,将“com.example.service.audit.entity”添加到两个数据源配置文件中,以便 Entity Scan by Envers。

如果您错过在其中一个配置文件中添加包,则在尝试保留正在审核的实体时,您可能会突然开始看到以下错误。代码可能未更改,但您可能会开始看到失败。

这取决于在引导时首先加载数据源的类加载器。如果最后加载缺少实体扫描包的数据源,您将看到以下错误。

这可能会导致在调试问题时浪费大量时间,并且您可能会被误导创建缺少的hibernate_sequence,这是Hibernate Envers使用的通用序列。

Caused by: org.hibernate.exception.SQLGrammarException: could not extract ResultSet
at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:106)
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:109)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:95)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:79)
at org.hibernate.id.enhanced.SequenceStructure$1.getNextValue(SequenceStructure.java:96)
at org.hibernate.id.enhanced.NoopOptimizer.generate(NoopOptimizer.java:40)
at org.hibernate.id.enhanced.SequenceStyleGenerator.generate(SequenceStyleGenerator.java:412)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:101)
at org.hibernate.jpa.event.internal.core.JpaSaveEventListener.saveWithGeneratedId(JpaSaveEventListener.java:56)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:192)
at org.hibernate.event.internal.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:38)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:177)
at org.hibernate.event.internal.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:32)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
at org.hibernate.internal.SessionImpl.fireSave(SessionImpl.java:679)
at org.hibernate.internal.SessionImpl.save(SessionImpl.java:671)
at org.hibernate.envers.internal.revisioninfo.DefaultRevisionInfoGenerator.saveRevisionData(DefaultRevisionInfoGenerator.java:75)
at org.hibernate.envers.internal.synchronization.AuditProcess.getCurrentRevisionData(AuditProcess.java:119)
at org.hibernate.envers.internal.synchronization.AuditProcess.executeInSession(AuditProcess.java:96)

... 141 common frames omitted
Caused by: org.postgresql.util.PSQLException: ERROR: relation "hibernate_sequence" does not exist
Position: 17

结论

Hibernate Envers是Hibernate提供的成熟审计模块。它是高度可配置的,节省了构建审计框架的工作量。

但是,使用多个数据源时,请记住在两个数据源中配置实体扫描包,否则会误导您并占用您调试问题的大量时间,尤其是当工作代码突然开始失败时。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐