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<T>接口,告訴Spring-Data當前審計用戶是誰。(一般項目中從spring security或token中獲取)
/** * 獲取當前的審計人,實際項目中可以從Spring Security中或Token/{session}中獲取,這里只是舉個例子進行模擬。 * @author caofanqi */ public class AuditorAwareImpl implements AuditorAware<AuditUser> { private Optional<AuditUser> currentUser = Optional.empty(); public void setCurrentUser(AuditUser currentUser){ this.currentUser = Optional.of(currentUser); } @Override public Optional<AuditUser> 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 AuditorAware<AuditUser> auditorProvider() { 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<Long> { private Optional<AuditUser> currentUser = Optional.empty(); public void setCurrentUser(AuditUser currentUser){ this.currentUser = Optional.of(currentUser); } @Override public Optional<Long> 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