JPA Audit and Envers

2 篇文章 0 订阅

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 {



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;



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");
    }
}



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);
    }
}



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);
    }


}

如果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;



step 1, 添加hibernate-envers 依赖
     	<dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-envers</artifactId>
        </dependency>



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

其中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个新的AUD表被创建
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nvd11

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值