• JPA Audit and Envers


    Background

    在一些数据敏感的项目, 特别是配置表, 我们需要记录每一次对表的甚至每个值的改动, 并把改动的数据存放到另一张表中。

    以前我们可能会用数据库trigger来实现, 但trigger实现方式也有如下限制

    1. 需要用sql编写trigger, 一但表结构更改, 需要找人修改trigger
    2. 手动创建Audit 表
    3. 对删除(hard delete)难以detect是谁删除的。

    当今世界, 更加偏向用java框架去实现,例如JPA Audit 和 Hiberate Envers



    JPA Audit

    如果对Audit 的要求不高, 只需要在表中记录是谁创建的, 谁最后修改,创建时间和最后修改时间。
    这种情况下我们使用JPA Audit就足够了。

    下面来看是怎么实现的, 我们会用user service 作为例子.



    step 1, 对Entity类添加注解 @EntityListeners(AuditingEntityListener.class)
    @Entity // to be pojo class of Hibernate
    @Data
    @Table(name = "tb_user")
    @ToString
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    @EntityListeners(AuditingEntityListener.class)
    public class User {
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9



    step 2, 对Entity类添加 下面4个关于audit的属性
        @CreatedDate
        @JsonIgnore
        private Date createDate;
    
        @LastModifiedDate
        @JsonIgnore
        private Date lastModifiedDate;
    
        @CreatedBy
        @JsonIgnore
        private String createdBy;
    
        @LastModifiedBy
        @JsonIgnore
        private String lastModifiedBy;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15



    step 3, 添加1个bean,实现AuditorAware接口告诉JPA当前的用户是谁

    正常呢来讲, username should be get from Sprint security API, 为了测试方便这边我就hardcode了

    import org.springframework.data.domain.AuditorAware;
    import org.springframework.stereotype.Component;
    
    import java.util.Optional;
    
    
    @Component("myAuditorAware")
    public class MyAuditorAware implements AuditorAware<String> {
    
        @Override
        public Optional<String> getCurrentAuditor() {
            return Optional.of("JasonPoon");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14



    step 4, 在启动类添加注解 @EnableJpaAuditing(auditorAwareRef = “myAuditorAware”)
    @EnableJpaRepositories
    @SpringBootApplication
    @EnableJpaAuditing(auditorAwareRef = "myAuditorAware")
    @ComponentScan({"cn.home.user.config","cn.home.user.web","cn.home.user.service","cn.home.user.pojo","cn.home.user.dao"})
    public class UserApplication {
        public static void main(String[] args) {
            SpringApplication.run(UserApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9



    step 5, 接下来我们就可以测试了
    @Slf4j
    @DataJpaTest //default will auto rollback
    @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
    public class UserDaoTest {
    
        @Autowired
        private UserDao userDao; //springboot will create an instance of the interface while starting
    
        @Test
        @DisplayName("test add User")
        @Rollback(value = false)
        public void testAddUser(){
            User user = new User();
            user.setUsername("Alice13");
            user.setAddress("The 5th School");
            userDao.save(user);
            log.info("User id: {}",user.getId());
            assertTrue(0 < user.getId(),"id should be generated");
            userDao.delete(user);
    
        }
    
        @Test
        @DisplayName("test add User")
        @Rollback(value = false)
        public void updateUser(){
            User user = userDao.findByUsername("Alice12");
            log.info("User id: {}",user.getId());
            user.setAddress("The 5th School3");
    
            userDao.save(user);
            assertTrue( "The 5th School3".equals(user.getAddress()),"address should be updated");
        }
    
        @Test
        @DisplayName("find one User")
        @Rollback(value = false)
        public void findUserTest1(){
            User user = userDao.findByUsername("Alice12");
            log.info(String.valueOf(user));
            assertNotNull(user);
        }
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    如果enable了 spring.jpa.hibernate.ddl-auto=true, 启动时, 会自动为tb_user表添加create_date等4个列。

    测试pass时检查下record
    可以看见audit 信息已经被加入!
    在这里插入图片描述




    Hibernate Envers

    如果对某张表的要求比较高, 要记录所有的change History在另1 张table, 这时我们需要用到Hibernate Envers
    下面也是对user service 的 user table作一个例子.

    首先我们Enver需要一张公共表 revinfo

    默认情况下,它包含两列
    REV- 主键列 - 修订版ID/修订版号
    REVTSTMP- 修订版创建的时间戳。

    而且Envers 会建立hard 外键 在其他Audit表上, 所以db 账号必须具有创建外键 REFERENCES 的权限。
    Mysql 中, grant语句是:

    grant select,insert,update,delete,create,alter,drop,REFERENCES on demo_cloud_user.* to cloud_user;
    
    • 1



    step 1, 添加hibernate-envers 依赖
         	<dependency>
                <groupId>org.hibernategroupId>
                <artifactId>hibernate-enversartifactId>
            dependency>
    
    • 1
    • 2
    • 3
    • 4



    step 2, 在springboot 配置文件中, 添加关于envers的属性
    spring:
      datasource:
        url: jdbc:mysql://43.138.222.61:3306/demo_cloud_user?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true
        username: cloud_user
        password: '{cipher}323e2265acd321eaec76a88bfa710f5f3673c58f8e6e1bbe2944f08b9518ac0c'
        driver-class-name: com.mysql.cj.jdbc.Driver
        hikari:
          maxLifeTime: 30000
      jpa:
        show-sql: true
        generate-ddl: true
        hibernate:
          ddl-auto: update
        properties:
          hibernate:
            dialect: org.hibernate.dialect.MySQL8Dialect
          org:
            hibernate:
              envers:
                audit_strategy: org.hibernate.envers.strategy.internal.DefaultAuditStrategy
                audit_strategy_validity_store_revend_timestamp: true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    其中audit_strategy, org.hibernate.envers.strategy.internal.DefaultAuditStrategy 的意思为
    hibernate提供了两种审计策略,分别是

    org.hibernate.envers.strategy.internal.DefaultAuditStrategy
    org.hibernate.envers.strategy.internal.ValidityAuditStrategy
    如果使用DefaultAuditStrategy,USER_AUD表中不会有REVEND,REVEND_TSTMP两个字段,只会单纯的记录变更与版本

    而使用ValidityAuditStrategy,在新增一条变更记录时,会更新上一条变更记录的REVEND,REVEND_TSTMP为当前的版本号以及变更时间

    因为ValidityAuditStrategy除了插入新纪录还要更新旧的记录,所以插入速度会慢一点,但是因为提供了额外的信息,对于数据查询,速度则较DefaultAuditStrategy更快一些

    具体的Envers 设置, 请参考
    https://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/Hibernate_User_Guide.html#envers



    step 3, 在springboot 配置文件中, 添加关于envers的属性

    在Entity类上加上@Audited 注解

    
    @Entity // to be pojo class of Hibernate
    @Data
    @Table(name = "tb_user")
    @ToString
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    @EntityListeners(AuditingEntityListener.class)
    @Audited
    public class User {
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    接下来就可以测试了

    执行上面的都测试用例, 可以见到1个新的AUD表被创建
    在这里插入图片描述

  • 相关阅读:
    linux(软硬链接)
    C++设计模式_04_Strategy 策略模式
    linux的常用命令及常用工具安装
    迭代器失效问题
    探索arkui(2)--- 布局(列表)--- 1(列表数据的展示)
    PAT 1036 Boys and Girls
    透视星环科技上市:基础工具、技术融合、场景应用三维击穿
    使用 Postman 的 Environments 和 Tests 简化在不同环境中的切换步骤
    【学习心得】爬虫JS逆向通解思路
    Python读取hbase数据库
  • 原文地址:https://blog.csdn.net/nvd11/article/details/126693034