• Spring Boot集成Hibernate Envers自定义修订实体使用多个数据源


    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提供的成熟审计模块。它是高度可配置的,节省了构建审计框架的工作量。

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

  • 相关阅读:
    MySQL 迁移 Oracle 场景中自增主键的实践
    windows中python版本冲突问题之二
    python学习15--数据分析(matplotlib、numpy)
    XCC批量更新固件微码
    110.firefly-overlayroot
    【基站维修工程师】python实现-附ChatGPT解析
    2022年京东双11食品饮料品类数据回顾
    sharepoint2016-2019升级到sharepoint订阅版
    金融工作怎么做?低代码如何助力金融行业
    农村科学实验杂志农村科学实验杂志社农村科学实验编辑部2022年第12期目录
  • 原文地址:https://blog.csdn.net/allway2/article/details/127975478