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

时间:2017-05-02 21:11:07

标签: 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 Envers中使用Spring 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的字段/列。如何表明effectiveDateUtc属于ProductHistory的属性?

更新

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

除了Naros发布的内容之外,还有我发现的其他内容,我需要做的就是使用Spring应用程序:

将所需的罐子添加到我的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());
        }

    }
} 

1 个答案:

答案 0 :(得分:2)

我意识到你可能正在寻找与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表中。

  3. 您可能希望看起来倾向于直接使用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) {
        }
      }
      

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

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

    3. 正如我最初提到的,这确实无法直接解决您的Spring数据需求,但它确实说明了如果您决定直接使用它,Envers会如何发生。