spring - 如何使用 Spring Data JPA 的审计功能在历史表上指示生效日期?

标签 spring jpa spring-data-jpa hibernate-envers

我有一个包含名称列的 Product 表,我想在 ProductHistory 表中维护名称更改的历史记录。 (Product 表还有一些其他列。我不需要跟踪这些列的数据更改。)

enter image description here

当用户请求运行给定日期的报告时,我的应用程序将使用 ProductHistory 表。报告需要显示截至报告日期的产品名称。

我正在使用 Spring Boot 1.5.2 并包括 Spring Boot 的 Starter Data JPA 依赖项。我打算使用 Hibernate EnversSpring Data JPA 内帮助实现我的目标。我已通读 this doc但不确定如何在 ProductHistory 上指定生效日期。

我已经启用了 JPA 审计:

@Configuration
@EnableJpaRepositories("com.example.repository")
@EnableJpaAuditing
public class DatabaseConfig {
    //config items
}

使用 the doc作为引用,我应该将实体设置为:

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @LastModifiedDate
    private Date effectiveDateUtc;

    private String name;

    //other fields corresponding to othercols
 }

但是effectiveDateUtc 不是Product 的字段/列。如何指示 effectiveDateUtcProductHistory 的属性?

更新

我一直无法找到一种方法来使用 Spring Data JPA 的审计功能来完成上述任务。不过,使用 Naros/Hibernate envers 发布的答案,我能够实现我的目标。

除了 Naros 发布的内容之外,以下是我发现要使它与我的 Spring 应用程序一起工作还需要做的其他事情:

将所需的 jar 添加到我的 gradle 构建中:

compile('org.hibernate:hibernate-envers:5.2.10.Final')
compile('org.hibernate:hibernate-core:5.2.10.Final')
compile('joda-time:joda-time:2.1')

在数据库中创建了必要的模式。

我还设置了一个集成测试用例来验证我是否可以对修订数据进行报告类型查询。为此,我需要获取实体管理器并将其传递给审核读取器工厂:

@RunWith(SpringRunner.class)
@SpringBootTest(classes=ProductDataApplication.class)
public class ProductRepositoryTest {

    @PersistenceContext
    private EntityManager entityManager;

    @Test
    @Transactional
    public void testEnversQuerying() {
        AuditReader ar = AuditReaderFactory.get(entityManager);
        Product revisions = ar.find(Product.class, 1L, new Date());
        // assert code
    }
}

正如 Naros 推测的那样,我还需要存储与修订相关联的用户 ID。我是这样做的:

public class UserRevisionListener implements RevisionListener {

    @Override
    public void newRevision(Object revisionEntity) {
        UserRevision revision = (UserRevision) revisionEntity;

        if (SecurityContextHolder.getContext().getAuthentication() == null ||
                SecurityContextHolder.getContext().getAuthentication().getName() == null) {
            revision.setModifiedBy("system");
        } else {
            revision.setModifiedBy(SecurityContextHolder.getContext().getAuthentication().getName());
        }

    }
} 

最佳答案

我知道您可能正在寻找与 Spring 相关的响应;但是,如果您直接使用 Hibernate Envers,我至少可以向您提供有关其工作原理的详细信息。

  1. 让我们进行实体设置:

    @Entity
    public class Product {
      @Id
      private Integer id;
      @Audited
      private String name;
      // other attributes
    }
    

    如您所见,我们基本上只是想审核产品名称。您在实体中映射的任何其他属性将被简单地忽略,因为该类未使用 @Audited 进行注释,并且我们专门针对属性 name

  2. 与您说明的ProductHistory 模型相反,Envers 实际上会为您创建两个表。第一个是 Product_AUD 表,我们将在其中存储有关对您的 Product 实体所做更改的审计日志。

    Product_AUD 表将包含与此非常相似的列:

    REV INT NOT NULL         <- Foreign Key to the Revision Entity Table (REVINFO)
    PRODUCT_ID INT NOT NULL  <- Your PK from your Product Table
    NAME VARCHAR(255)        <- The name column annotated with @Audited
    REVTYPE INT              <- Revision type (0=INSERT,1=UPDATE,2=DELETE)
    

    下一个将要构建的表是 REVINFO。默认情况下,该表仅由两列构成,如下所示:

    REV INT NOT NULL         <- The revision number (PK)
    REVTSTMP BIGINT          <- The time in milliseconds
    

    Envers 以这种方式构建模型的原因是因为在自事务中修改多个实体并不少见,而不是在所有实体审计表中复制大量特定于修订的属性,我们将其合并到一种规范化的方式,并将该数据存储在 REVINFO 表中。

您可能希望似乎倾向于直接使用 Spring Data 的一件事是能够捕获用户以及可能在修订发生时与环境相关的其他属性。您很幸运,因为 Envers 也提供了一种快速而轻松的方法来做到这一点。

  1. 定义自定义修订实体。这可以通过简单地扩展我们为您提供的默认实现之一来完成,或者您可以简单地创建一个功能齐全的实体。我个人的偏好和建议是创建您自己的完整实现。该实体基本上使用自定义列扩展了默认的 REVINFO 表。下面是一个用一个额外的列来镜像默认表设置的示例:

    @Entity
    @Table(name = "REVINFO")
    @RevisionEntity(MyRevisionEntityListener.class)
    public class MyRevisionEntity {
      @Id
      @RevisionNumber
      @Column(name = "REV", nullable = false, updatable = false)
      private Integer id;
      @RevisionTimestamp
      @Column(name = "REVTSTMP", nullable = false, updatable = false)
      private Long timestamp;
      @Column(name = "MODIFIED_BY", length = 100)
      private String modifiedBy;
      // getter/setters and perhaps other attributes
    }
    
  2. 定义用于在修订实体实例上填充自定义属性的修订实体监听器:

    class MyRevisionEntityListener implements RevisionListener {
      @Override
      public void newRevision(Object revisionEntity) {
      }
    }
    

    在此监听器中,您要做的是获取要注入(inject)到 Envers 修订实体中的任何上下文信息,并将其设置在修订实体类中。

    将信息获取到此监听器中的一种方法是将其存储在 ThreadLocal 变量中并在监听器回调中访问该变量。如果您使用 Spring Security 作为示例,它已经在其 SecurityContextHolder 线程本地容器中为您完成了此操作。如果您使用其他方式,您可以简单地以它们的实现为例。

正如我最初提到的,这实际上并没有直接解决您的 Spring Data 需求,但它确实说明了如果您决定直接使用 Envers,它会如何发生。

关于spring - 如何使用 Spring Data JPA 的审计功能在历史表上指示生效日期?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43747306/

相关文章:

java - Spring @Around > 参数的行为

java - Spring @Transactional : Rollback information reported but not actually performed

java - JQuery DataTables服务端分页、排序、搜索用Spring MVC + JPA

java - 内存数据库H2中的Spring Boot不会在初始化时从文件加载数据

spring - 在测试文件夹Spring中定义一个JPA Repository

spring - 如何使用@WebfluxTest测试异常

java - @Resource(名称 ="beanName") 与 ApplicationContext#getBean ("beanName")

java - 为什么 FetchType.EAGER 在单元测试中不起作用?

java - 如何使用 jpa 和 hibernate 将 native 查询映射到 POJO 类

java - 使用 spring Data JPA 将 sql 查询的结果映射到 pojo