Spring Data JPA Auditing(审核审计)和@Version(乐观锁)

我们在实际的业务系统中,针对一张表的操作大部分是需要记录谁什么时间创建,谁什么时间修改的,并且能让我们方便地记录操作日志

Spring Data JPA为我们提供了审计功能的架构实现,提供4个注解专门解决这件事情:

@CreatedBy: 哪个用户创建的

@CreatedDate: 创建时间

@LastModifiedBy: 修改实体的用户

@LastModifiedDate: 最后一次修改时间

 

Auditing配置: 

1. 配置实体类的注解

@Data
@Entity(name = "t_user")
@EntityListeners(AuditingEntityListener.class)
public class SystemUser implements Serializable {

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

    private String uname;

    private String email;

    private String address;

    @CreatedBy
    private Integer createUserId;

    @LastModifiedBy
    private Integer lastModifiedUserId;

    @CreatedDate
    private Date createTime;

    @LastModifiedDate
    private Date lastModifiedTime;
    
}

添加 @CreatedBy、@CreatedDate、@LastModifiedBy、@LastModifiedDate 和 @EntityListener(AuditingEntityListener.class) 注解

 

2. 实现 AuditorAware接口,高速JPA当前用户是谁

public class MyAuditorAware implements AuditorAware<Integer> {

    @Override
    public Optional<Integer> getCurrentAuditor() {
        // 获取用户id
        return Optional.of(999);
    }
}

3. 启用Auditing配置

@EnableJpaRepositories(repositoryBaseClass = MyRepoImpl.class)
@EnableJpaAuditing // 1. 开启审计
@SpringBootApplication
public class StudySpringDataJpaApplication {

    public static void main(String[] args) {
        SpringApplication.run(StudySpringDataJpaApplication.class, args);
    }


    // 2. 将自定义的用户获取注册为Bean
    @Bean
    public AuditorAware<Long> auditorProvider(){
        return new MyAuditorAware();
    }
}

 

在实际业务情况中,我们会将 @Id、@CreatedBy、@CreatedDate、@LastModifiedBy、@LastModifiedDate  放在一个公共的基类中

// 抽象公共基类
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class AbstractAuditable {

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

    @CreatedBy
    private Long createUserId;

    @CreatedDate
    private Date createTime;

    @LastModifiedBy
    private Long lastModifiedUserId;

    @LastModifiedDate
    private Date lastModifiedTime;
}


// 修改后的实体类
@Data
@Entity(name = "t_user")
public class SystemUser extends AbstractAuditable implements Serializable {

    private String uname;

    private String email;

    private String address;

}

 

Auditing原理解析

  • AuditingEntityListener通过委托设计模式,委托AuditingHandler进行处理,AuditingHandler里面是根据ID和Version来判断我们的对象是新增还是更新,从而来更改时间字段和User字段而User字段是通过AuditorAware的实现类来取的,并且AuditorAware没有默认实现类,实现类必须我们自己来定义,否则报错
  • AuditingEntityListener支持自定义
类型 描述
@PrePersist 新增之前
@PreRemove 删除之前
@PostPersist 新增之后
@PostRemove 删除之后
@PreUpdate 更新之前
@PostUpdate 更新之后
@PostLoad 加载之后

 

这些方法都是同步机制,一旦报错将会影响所有底层代码执行,在处理日志的时候方法体里面开启异步线程或者消息队列来异步处理日志,或更繁重的工作

public class MyAuditingListeners {
    
    @PostPersist
    private void postPersist(SystemUser user){
        System.out.println("新增");
    }

    @PostRemove
    private void postRemove(SystemUser user){
        System.out.println("删除");
    }

    @PostUpdate
    private void postUpdate(SystemUser user){
        System.out.println("修改");
    }
}
@MappedSuperclass
@EntityListeners(MyAuditingListeners.class) // 使用自定义Listener
public abstract class AbstractAuditable {

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

    @CreatedBy
    private Long createUserId;

    @CreatedDate
    private Date createTime;

    @LastModifiedBy
    private Long lastModifiedUserId;

    @LastModifiedDate
    private Date lastModifiedTime;
}

 

@Version 处理乐观锁的问题

public @interface Version {}

在数据库并发操作时,为了保证数据库的正确性,我们会做一些并发处理,主要就是加锁,在加锁的选择上,常见有两种方式: 乐观锁和悲观锁

悲观锁: 

吧需要的数据全部加锁,在事务提交之前,这些数据全部不可读取和修改

乐观锁:

使用对单挑数据进行版本校验和比较,来保证本次的更新是最新的,否则就失败,效率要高很多 

在分布式系统中,为了实现分布式系统数据一致性,分布式事务的一种做法就是乐观锁

 

数据库操作例子:

悲观锁做法:

select * from t_user where id = 1 for update;
update t_user set name='wang' where id = 1;

通过使用 for update 给这条语句加锁,如果事务没有提交,其他任何读取和修改都得排队等待,我们加事务的方法就会自然形成一个锁

 

乐观锁做法:

select id,uname,version from t_user where id =1;
update t_user set name='wang',version=version+1 where id=1 and version=1;

假设本次查询version=1,在更新操作时,带上这次查出来的Version,这样只有和我们上次版本一样的时候才会更新,就不会出现相互覆盖的问题

 

@Version用法

@Version通过AOP机制,帮我们动态维护这个Version,从而更优雅地实现乐观锁

@Data
@Entity(name = "t_user")
public class SystemUser extends AbstractAuditable implements Serializable {

    private String uname;

    private String email;

    private String address;

    @Version // 标记为控制乐观锁字段
    private Long version;
}

当新增和更新的时候都会带上version操作,当乐观锁更新失败的时候,会抛出异常: org.springframework.orm.ObjectOptimisticLockingFailureException

 

posted @ 2020-11-22 11:33  半雨微凉  阅读(1134)  评论(0)    收藏  举报