1、Auditing
一般我们针对一张表的操作需要记录下来,是谁修改的,修改时间是什么,Spring-Data为我们提供了支持。
1.1、在实体类中使用Spring-Data为我们提供的四个注解(也可以选择实现Auditable接口或继承AbstractAuditable类,推荐使用注解)
1.2、在实体上添加@EntityListeners(value = AuditingEntityListener.class)启动对当前实体的监听。
/** * 测试spring-data为我们提供的审计功能 * * @author caofanqi */ @Data @Entity @Builder @Table(name = "jpa_audit_user") @NoArgsConstructor @AllArgsConstructor @EntityListeners(value = AuditingEntityListener.class) public class AuditUser { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(unique = true) private String name; @CreatedDate private LocalDateTime createdDate; @LastModifiedDate private LocalDateTime lastModifiedDate; @CreatedBy @ManyToOne private AuditUser createdBy; @LastModifiedBy @ManyToOne private AuditUser lastModifiedBy; }
1.3、如果在实体中使用了@CreatedBy或者@LastModifiedBy需要实现AuditorAware
/** * 获取当前的审计人,实际项目中可以从Spring Security中或Token/{session}中获取,这里只是举个例子进行模拟。 * @author caofanqi */ public class AuditorAwareImpl implements AuditorAware{ private Optional currentUser = Optional.empty(); public void setCurrentUser(AuditUser currentUser){ this.currentUser = Optional.of(currentUser); } @Override public Optional getCurrentAuditor() { //要使用的当前用户 return currentUser; } }
1.4、在启动类上添加@EnableJpaAuditing启动审计功能。
1.5、如果ApplicationContext中只有一个AuditorAware类型的bean,Spring-Date会自动选择,如果又多个,需要通过@EnableJpaAuditing注解的auditorAwareRef属性进行设置。
/** * 启动类 * @author caofanqi */ @SpringBootApplication @EnableAsync @EnableJpaRepositories( /*queryLookupStrategy = QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND*/ /* ,repositoryImplementationPostfix = "MyPostfix",*/ /*repositoryBaseClass = MyRepositoryImpl.class*/) @EnableJpaAuditing public class StudySpringDataJpaApplication { public static void main(String[] args) { SpringApplication.run(StudySpringDataJpaApplication.class, args); } /** * 如果ApplicationContext中只有一个AuditorAware类型的bean,Spring-Date会自动选择, * 如果又多个,需要通过@EnableJpaAuditing注解的auditorAwareRef属性进行设置。 */ @Bean public AuditorAwareauditorProvider() { return new AuditorAwareImpl(); } }
1.6、测试用例,及生成的表
@SpringBootTest class AuditUserRepositoryTest { @Resource private AuditUserRepository auditUserRepository; @Resource private AuditorAwareImpl auditorAware; @Test void testAuditDate(){ /* *不设置创建和修改时间,由springl-data替我们完成 */ AuditUser audit = AuditUser.builder().name("张三").build(); AuditUser save = auditUserRepository.save(audit); System.out.println(save); } @Test void testAuditUser(){ /* * 模拟当前用户 */ auditorAware.setCurrentUser(auditUserRepository.findByName("张三")); /* * 这里不设置是谁保存的,看spring-data是否会为我们完成 */ AuditUser audit = AuditUser.builder().name("李四").build(); AuditUser save = auditUserRepository.save(audit); System.out.println(save); } }
testAuditDate控制台打印:
testAuditUser控制台打印:
数据库表:
2、@MappedSuperclass
指定其映射信息应用于从其继承的实体的类。映射的超类没有为其定义单独的表。与MappedSuperclass注释指定的类可以以与实体相同的方式映射,除了映射仅适用于它的子类之外,因为映射超类本身不存在表。当应用于子类时,继承的映射将应用于子类表的上下文中。(说白了,就是将各实体中相同的属性提取到一个添加该注解的父类中,父类不会生成对应的表,但是各子实体类生成的对应表不变。)
这样我们就可以将通用的ID和Auditing相关的属性提取出来。
2.1、id抽象类
/** * 抽象id父类 * * @author caofanqi */ @Getter @Setter @ToString @MappedSuperclass public abstract class AbstractID { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; }
2.2、审计功能抽象类
/** * 审计功能抽象类 * @author caofnqi */ @Getter @Setter @ToString(callSuper = true) @MappedSuperclass @EntityListeners(value = AuditingEntityListener.class) public abstract class AbstractAuditDomain extends AbstractID { @CreatedDate private LocalDateTime createdDate; @LastModifiedDate private LocalDateTime lastModifiedDate; @CreatedBy @Column(name = "create_by_user_id") private Long createdByUserId; @LastModifiedBy @Column(name = "last_modified_by_user_id") private Long lastModifiedUserBy; }
2.3、实体类,可以根据是否需要用到选择继承id抽象类,还是审计抽象类
/** * @author caofanqi */ @Getter @Setter @Entity @Builder @Table(name = "jpa_audit_person") @NoArgsConstructor @AllArgsConstructor @ToString(callSuper = true) public class AuditPerson extends AbstractAuditDomain { private String personName; }
2.4、修改对应的AuditorAware实现,并指定auditorAwareRef
/** * AuditorAware实现示例,根据自己业务进行实现 * @author caofanqi */ public class IdAuditorAwareImpl implements AuditorAware{ private Optional currentUser = Optional.empty(); public void setCurrentUser(AuditUser currentUser){ this.currentUser = Optional.of(currentUser); } @Override public Optional getCurrentAuditor() { return currentUser.map(AuditUser::getId); } }
测试类似上面,这里就不贴了。
3、自定义实体监听
Auditing是通过JPA提供的@EntityListeners和@PrePersist、@PreUpdate来完成的。
@EntityListeners,指定要用于实体或映射超类的回调侦听器类。此注释可以应用于实体类或映射的超类。
属性:value,回调侦听器类。
以下注解为相应的生命周期事件指定回调方法。此注释可以应用于实体类、映射超类或回调侦听器类的方法。都是同步机制使用时要注意,可以在使用时,可以在方法中开启异步线程或消息队列。
@PrePersist,新增之前;@PostPersist,新增之后。
@PreUpdate,更新之前;@PostUpdate,更新之后。
@PreRemove,删除之前;@PostRemove,删除之后。
@PostLoad,加载之后。
我们以订单为例:
/** * * @author caofanqi */ @Slf4j @Getter @Setter @Entity @Builder @Table(name = "jpa_order") @NoArgsConstructor @AllArgsConstructor @EntityListeners(value = OrderEntityListener.class) public class Order extends AbstractAuditDomain{ @Column(unique = true) private String orderNo; @Column(nullable = false) private OrderStatus orderStatus; @Column(nullable = false) private BigDecimal price; //其他属性.... /* * 以下方法也可以写在监听类中 */ // @PrePersist // public void prePersist(){ // this.setOrderStatus(OrderStatus.NEW); // log.info("orderNo: {},status :{},新增之前修改订单状态为NEW",this.getOrderNo(),this.getOrderStatus()); // } // // @PostPersist // public void postPersist(){ // log.info("orderNo: {},status :{},新增之后,异步通知仓库进行处理",this.getOrderNo(),this.getOrderStatus()); // } // // @PostLoad // public void postLoad(){ // log.info("orderNo: {},status :{},加载之后...",this.getOrderNo(),this.getOrderStatus()); // } // // @PreUpdate // public void preUpdate(){ // log.info("orderNo: {},status :{},修改之前.....",this.getOrderNo(),this.getOrderStatus()); // } // // @PostUpdate // public void postUpdate(){ // log.info("orderNo: {},status :{},修改之后根据订单状态进行不同的判断",this.getOrderNo(),this.getOrderStatus()); // } // // @PreRemove // public void preRemove(){ // log.info("orderNo: {},status :{},删除之前.....",this.getOrderNo(),this.getOrderStatus()); // } // // // @PostRemove // public void postRemove(){ // log.info("orderNo: {},status :{},删除之后.....",this.getOrderNo(),this.getOrderStatus()); // } }
/** * 订单实体监听类 * @author caofanqi */ @Slf4j public class OrderEntityListener { @PrePersist public void prePersist(Order order){ order.setOrderStatus(OrderStatus.NEW); log.info("orderNo: {},status :{},新增之前修改订单状态为NEW",order.getOrderNo(),order.getOrderStatus()); } @PostPersist public void postPersist(Order order){ log.info("orderNo: {},status :{},新增之后,异步通知厂库进行处理",order.getOrderNo(),order.getOrderStatus()); } @PostLoad public void postLoad(Order order){ log.info("orderNo: {},status :{},加载之后...",order.getOrderNo(),order.getOrderStatus()); } @PreUpdate public void preUpdate(Order order){ log.info("orderNo: {},status :{},修改之前.....",order.getOrderNo(),order.getOrderStatus()); } @PostUpdate public void postUpdate(Order order){ log.info("orderNo: {},status :{},修改之后根据订单状态进行不同的判断",order.getOrderNo(),order.getOrderStatus()); } @PreRemove public void preRemove(Order order){ log.info("orderNo: {},status :{},删除之前.....",order.getOrderNo(),order.getOrderStatus()); } @PostRemove public void postRemove(Order order){ log.info("orderNo: {},status :{},删除之后.....",order.getOrderNo(),order.getOrderStatus()); } }
测试新增:
测试查询和修改(图中红框中的为jpa save方法更新前自己运行的查询):
测试查询和删除(图中红框中的为jpa delete方法更新前自己运行的查询):
源码地址:https://github.com/caofanqi/study-spring-data-jpa