Hibernate Envers项目旨在实现对持久类的简单审计。它完全消除了审计实体的麻烦。
以下部分概述了使用自定义修订实体通过 Spring 引导配置 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提供的成熟审计模块。它是高度可配置的,节省了构建审计框架的工作量。
但是,使用多个数据源时,请记住在两个数据源中配置实体扫描包,否则会误导您并占用您调试问题的大量时间,尤其是当工作代码突然开始失败时。