介绍
Hibernate Envers,是一个审计和历史记录框架,提供实时数据审计,它提供了一些必要的基本功能,允许您跟踪实体实例的变化,可以检索这些变化,以及由这些变化引起的变化。
Hibernate Envers是在Hibernate ORM基础上构建的一个扩展,它可以捕获实体类中属性值变形 的审计信息,并可以提供查看特定历史数据的功能,支持灵活的审计策略。例如,用户可以根据时间戳记录用户访问或数据修改时间,也可以根据某个特定用户记录数据变形信息。Hibernate Envers还可以基于审计策略设置自定义的前、后事件,以实现等级审计。 ------- from ChatGPT
环境配置
测试基于SpringBoot+JPA+H2进行,相关依赖如下
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-envers</artifactId>
<version>6.1.7.Final</version>
</dependency>
配置文件如下
server:
port: 8080
spring:
application:
name: hibernate-enver-test
datasource:
url: jdbc:h2:file:~/test;NON_KEYWORDS=USER;OLD_INFORMATION_SCHEMA=TRUE;AUTO_SERVER=TRUE
username: test
password: test
driver-class-name: org.h2.Driver
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
代码准备
首先是启动类,因为要使用自动审计功能维护时间字段所以添加了@EnableJpaAuditing注解
@EnableJpaAuditing
@SpringBootApplication
public class StartApplication {
public static void main(String[] args) {
SpringApplication.run(StartApplication.class, args);
}
}
接下来创建User实体,在类上添加@Audited代表需要让envers托管整个类,并据此生成变更记录。
@Entity
@Getter
@Setter
@Builder
@Audited
@NoArgsConstructor
@AllArgsConstructor
@EntityListeners(AuditingEntityListener.class)
public class User {
@Id
@GeneratedValue
private Long id;
private String name;
@CreatedDate
private LocalDateTime createTime;
@LastModifiedDate
private LocalDateTime updateTime;
}
我们也可以只针对某些字段做审计,例如在此例中我们只关心id和name。
@Entity
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EntityListeners(AuditingEntityListener.class)
public class User {
@Id
@Audited
@GeneratedValue
private Long id;
@Audited
private String name;
@CreatedDate
private LocalDateTime createTime;
@LastModifiedDate
private LocalDateTime updateTime;
}
最后创建相关controller、service与repo。
@RestController
@RequiredArgsConstructor
@RequestMapping("users")
public class UserController {
private final UserService userService;
@PostMapping("{name}")
public void createUser(@PathVariable String name) {
userService.createUser(name);
}
@PutMapping("{id}/{name}")
public void updateUser(@PathVariable Long id, @PathVariable String name) {
userService.updateUser(id, name);
}
@DeleteMapping("{id}")
public void updateUser(@PathVariable Long id) {
userService.deleteUser(id);
}
@GetMapping
public List<User> getAllUsers() {
return userService.getAllUsers();
}
@GetMapping("{id}")
public User getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
}
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
public void createUser(String name) {
userRepository.save(User.builder().name(name).build());
}
public void updateUser(Long id, String name) {
userRepository.findById(id).ifPresent(
user-> {
user.setName(name);
userRepository.save(user);
}
);
}
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
public List<User> getAllUsers() {
return userRepository.findAll();
}
public User getUserById(Long id) {
return userRepository.findById(id).orElseThrow(()-> new RuntimeException("user not exist."));
}
}
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
测试
启动项目后,我们先来看一下表结构,可以发现生成了以下三个表,其中USER为我们业务表,REVINFO与USER_AUD则是envers自动生成的审计记录表。
REVINFO表结构如下其中REV为主键id, REVSTAMP是创建时间
create table REVINFO
(
REV INTEGER auto_increment
primary key,
REVTSTMP BIGINT
);
接下来是我们的USER表
create table USER
(
ID BIGINT not null
primary key,
CREATE_TIME TIMESTAMP(6, 0),
NAME CHARACTER,
UPDATE_TIME TIMESTAMP(6, 0)
);
USER_AUD是USER表的审计表其中ID和NAME为我们的待审计字段,REV为REVINFO的ID也是此表的外键,REVTYPE是修改类型(0表示新增,1表示更新,2表示删除)
create table USER_AUDIT
(
ID BIGINT not null,
REV INTEGER not null,
REVISION_TYPE TINYINT,
NAME CHARACTER,
primary key (ID, REV),
constraint FK1E6UW6EK1MH3FLETHFN1VLY35
foreign key (REV) references REVINFO
);
我们调用接口添加一个用户Jack
可以看到我们的新增数据已经成功生成审计记录
当我们把调用更新接口将名称从Jack改成Rose后,也能对应新增一条变更记录
删除同理,不过可以发现删除时,审计表中没有记录名称删除的值
进阶
envers支持一些进阶的配置以适配各种场景
配置项 | 含义 |
---|---|
audit_table_prefix | 审计表名前缀 |
audit_table_suffix(默认为_AUD) | 审计表名后缀 |
revision_field_name(默认为REV) | 审计表中修订号的字段名称 |
revision_type_field_name(默认为REVTYPE) | 审计表中修改类型的字段名称 |
store_data_at_delete | 删除时是否保存对应值 |
我们对应做如下调整
jpa:
properties:
org:
hibernate:
envers:
audit_table_suffix: _audit
revision_field_name: revision_id
revision_type_field_name: revision_type
store_data_at_delete: true
重启服务后效果如下
create table USER_AUDIT
(
ID BIGINT not null,
REVISION_ID INTEGER not null,
REVISION_TYPE TINYINT,
NAME CHARACTER,
primary key (ID, REVISION_ID),
constraint FK1E6UW6EK1MH3FLETHFN1VLY35
foreign key (REVISION_ID) references REVINFO
);
删除后也能正确记录字段值