javascript
Spring Data JPA 从入门到精通~Auditing及其事件详解
?Auditing 及其事件詳解
Auditing 翻譯過來是審計(jì)和審核,Spring 的優(yōu)秀之處在于幫我們想到了很多繁瑣事情的解決方案,我們?cè)趯?shí)際的業(yè)務(wù)系統(tǒng)中,針對(duì)一張表的操作大部分是需要記錄誰什么時(shí)間創(chuàng)建的,誰什么時(shí)間修改的,并且能讓我們方便的記錄操作日志。Spring Data JPA 為我們提供了審計(jì)功能的架構(gòu)實(shí)現(xiàn),提供了四個(gè)注解專門解決這件事情:
- @CreatedBy 哪個(gè)用戶創(chuàng)建的。
- @CreatedDate 創(chuàng)建的時(shí)間。
- @LastModifiedBy 修改實(shí)體的用戶。
- @LastModifiedDate 最后一次修改時(shí)間。
Auditing 如何配置
我們以一個(gè)快速的例子,看看它是怎么配置生效的。
(1)先新建一個(gè) @Entity:UserCustomerEntity 里面的寫法如下。
@Entity @Table(name = "user_customer", schema = "test", catalog = "") @EntityListeners(AuditingEntityListener.class) public class UserCustomerEntity {@Id@Column(name = "id", nullable = false) @GeneratedValue(strategy = GenerationType.IDENTITY)private Integer id;@CreatedDate@Column(name = "create_time", nullable = true)private Date createTime;@CreatedBy@Column(name = "create_user_id", nullable = true)private Integer createUserId;@LastModifiedBy@Column(name = "last_modified_user_id", nullable = true)private Integer lastModifiedUserId;@LastModifiedDate@Column(name = "last_modified_time", nullable = true)private Date lastModifiedTime;@Column(name = "customer_name", nullable = true, length = 50)private String customerName;@Column(name = "customer_email", nullable = true, length = 50)private String customerEmail; ...... }@Entity 實(shí)體中我們需要做兩點(diǎn):
- 相應(yīng)的字段添加 @CreatedBy、@CreatedDate、@LastModifiedBy and @LastModifiedDate注解。
- 增加 @EntityListeners(AuditingEntityListener.class)。
(2)實(shí)現(xiàn) AuditorAware 接口告訴 JPA 當(dāng)前的用戶是誰。
實(shí)現(xiàn) AuditorAware 接口,實(shí)現(xiàn) getCurrentAuditor 方法,返回一個(gè) Integer 的 user ID。以下代碼介紹了兩種做法:
public class MyAuditorAware implements AuditorAware<Integer> {/*** Returns the current auditor of the application.* @return the current auditor*/@Overridepublic Integer getCurrentAuditor() { // 第一種方式:如果我們集成了spring的Security,我們直接通過如下方法即可獲得當(dāng)前請(qǐng)求的用戶ID. // Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // if (authentication == null || !authentication.isAuthenticated()) { // return null; // } // return ((LoginUserInfo) authentication.getPrincipal()).getUser().getId();//第二種方式通過request里面取或者session里面取ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();return (Integer) servletRequestAttributes.getRequest().getSession().getAttribute("userId");} }而 AuditorAware 的源碼如下:
public interface AuditorAware<T> {T getCurrentAuditor(); }通過實(shí)現(xiàn) AuditorAware 接口的 getCurrentAuditor() 方法告訴 JPA 當(dāng)前的用戶是誰,里面實(shí)現(xiàn)方法千差萬別,作者舉例了兩種最常見的:
- 通過 Security 取。
- 通過 Request 取。
(3)通過 @EnableJpaAuditing 注解開啟 JPA 的 Auditing 功能。
并且告訴應(yīng)用 AuditorAware 的實(shí)現(xiàn)類是誰,也就是我們通過 @Bean 注解把上面的實(shí)現(xiàn)類放到 Spring 的 Bean 管理里面,當(dāng)然了也可以上面的類加上 @Component。具體配置方式如下:
@SpringBootApplication @EnableJpaAuditing public class QuickStartApplication {public static void main(String[] args) {SpringApplication.run(QuickStartApplication.class, args);}@Beanpublic AuditorAware<Integer> auditorProvider() {return new MyAuditorAwareImpl();} }驗(yàn)證結(jié)果如下。
通過以上的三步,我們已經(jīng)完成了 auting 的配置,通過 userCustomerRepository.save(new UserCustomerEntity("1","Jack")); 的執(zhí)行,我們看數(shù)據(jù)庫(kù)里面的 4 個(gè)字段已經(jīng)給填上去了。
@MappedSuperclass
實(shí)際工作中我們還會(huì)對(duì)上面的實(shí)體部分進(jìn)行改進(jìn),引入 @MappedSuperclass 注解,我們將 @Id、@CreatedBy、@CreatedDate、@LastModifiedBy and @LastModifiedDate 抽象到一個(gè)公用的基類里面,方便公用和形成每個(gè)表的字段約束。可以將其放到我們公司的框架代碼上,對(duì)表設(shè)計(jì)形成統(tǒng)一的強(qiáng)約束。
步驟如下:
(1)改進(jìn)后我們新增一個(gè) AbstractAuditable 的抽象類:
@MappedSuperclass @EntityListeners(AuditingEntityListener.class) public abstract class AbstractAuditable {@Id@Column(name = "id", nullable = false)@GeneratedValue(strategy = GenerationType.IDENTITY)private Integer id;@CreatedDate@Column(name = "create_time", nullable = true)private Date createTime;@CreatedBy@Column(name = "create_user_id", nullable = true)private Integer createUserId;@LastModifiedBy@Column(name = "last_modified_user_id", nullable = true)private Integer lastModifiedUserId;@LastModifiedDate@Column(name = "last_modified_time", nullable = true)private Date lastModifiedTime; ...... }(2)而我們每個(gè)需要 Auditing 的實(shí)體只需要繼承 AbstractAuditable 即可。
內(nèi)容如下:
@Entity @Table(name = "user_customer", schema = "test", catalog = "") public class UserCustomerEntity extends AbstractAuditable {@Column(name = "customer_name", nullable = true, length = 50)private String customerName;@Column(name = "customer_email", nullable = true, length = 50)private String customerEmail; ......}Auditing 原理解析
(1)我們先看一下關(guān)鍵的幾個(gè)源碼的關(guān)系圖:
(2)AuditingEntityListener 的源碼如下:
@Configurable public class AuditingEntityListener {private ObjectFactory<AuditingHandler> handler;public void setAuditingHandler(ObjectFactory<AuditingHandler> auditingHandler) {Assert.notNull(auditingHandler, "AuditingHandler must not be null!");this.handler = auditingHandler;}//在新增之前通過handler來往我們的@Entity里面的auditor的那些字段塞值。@PrePersistpublic void touchForCreate(Object target) {if (handler != null) {handler.getObject().markCreated(target);}}//在更新之前通過handler來往我們的@Entity里面的auditor的那些字段塞值。@PreUpdatepublic void touchForUpdate(Object target) {if (handler != null) {handler.getObject().markModified(target);}} }(3)通過調(diào)用關(guān)系圖和 AuditingEntityListener,我們其實(shí)可以發(fā)現(xiàn)以下兩點(diǎn)情況:
- AuditingEntityListener 通過委托設(shè)計(jì)模式,委托 AuditingHandler 進(jìn)行處理,而我們看 AuditingHandler 的源碼會(huì)發(fā)現(xiàn),里面就是根據(jù) ID 和 Version(后面介紹)來判斷我們的對(duì)象是新增還是更新,從而來更改時(shí)間字段和 User 字段。而 User 字段是通過 AuditorAware 的實(shí)現(xiàn)類來取的,并且 AuditorAware 沒有默認(rèn)實(shí)現(xiàn)類,只有我們自己的實(shí)現(xiàn)類,也就是 AuditorAware 的實(shí)現(xiàn)類必須我們自己來定義,否則啟動(dòng)會(huì)報(bào)錯(cuò)。
- AuditingEntityListener 的代碼如此簡(jiǎn)單,我們能不能自定義呢?答案是肯定的,通過 @PrePersist、@PreUpdate 查看源碼得出,Java Persistence API 底層又幫我們提供的 Callbacks,而這些回調(diào)方法,用于偵聽保存、查詢(抓取)、更新和刪除數(shù)據(jù)庫(kù)中的數(shù)據(jù)。注解方式如下:
| Type | 描述 |
| @PrePersist | 新增之前 |
| @PreRemove | 刪除之前 |
| @PostPersist | 新增之后 |
| @PostRemove | 刪除之后 |
| @PreUpdate | 更新之前 |
| @PostUpdate | 更新之后 |
| @PostLoad | 加載后 |
注意:這個(gè)方法都是同步機(jī)制,一但報(bào)錯(cuò)將會(huì)影響所有底層代碼執(zhí)行。在實(shí)際工作中實(shí)現(xiàn)這些方法的時(shí)候,方法體里面開啟異步線程,或者消息隊(duì)列,來異步處理日志,或者更繁重的工作。
Listener 事件的擴(kuò)展
自定義 EntityListener
隨著 DDD 的設(shè)計(jì)模式逐漸被大家認(rèn)可和熱捧,JPA 通過這種 Listener 這種機(jī)制可以很好的實(shí)現(xiàn)事件分離、狀體分離。假如,訂單的狀態(tài)變化可能對(duì)我們來說比較重要,我們需要定一個(gè)類去監(jiān)聽訂單狀態(tài)變更,通知相應(yīng)的邏輯代碼各自去干各自的活。
(1)新增一個(gè) OrderStatusAuditListener 類,在相應(yīng)的操作上添加 Callbacks 注解。
public class OrderStatusAuditListener {@PostPersistprivate void postPersist(OrderEntiy entity) {//當(dāng)更新的時(shí)候做一些邏輯判斷,及其事件通知。}@PostRemoveprivate void PostRemove(OrderEntiy entity) {//當(dāng)刪除的時(shí)候做一些邏輯判斷。}@PostUpdate private void PostUpdate(OrderEntiy entity) {//當(dāng)更新的時(shí)候// entity.getOrderStatus(),做一些邏輯判斷} }(2)我們的訂單實(shí)體變化如下:
@Entity @Table("orders") @EntityListeners({AuditingEntityListener.class, OrderStatusAuditListener.class}) public class OrderEntity extends AbstractAuditable{@Enumerated(EnumType.STRING)@Column("order_status")private OrderStatusEnum orderStatus;...... }即可完成自定義 EntityListener。
實(shí)際工作記錄操作日志的實(shí)例
public class ActionsLogsAuditListener {private static final Logger logger = LoggerFactory.getLogger(ActionsLogsAuditListener.class);@PostLoadprivate void postLoad(Object entity) {this.notice(entity, OperateType.load);}@PostPersistprivate void postPersist(Object entity) {this.notice(entity, OperateType.create);}@PostRemoveprivate void PostRemove(Object entity) {this.notice(entity, OperateType.remove);}@PostUpdateprivate void PostUpdate(Object entity) {this.notice(entity, OperateType.update);}private void notice(Object entity, OperateType type) {logger.info("{} 執(zhí)行了 {} 操作", entity, type.getDescription());//我們通過active mq 異步發(fā)出消息處理事件ActiveMqEventManager.notice(new ActiveMqEvent(type, entity));}enum OperateType {create("創(chuàng)建"), remove("刪除"),update("修改"),load("查詢");private final String description;OperateType(String description) {this.description=description;}public String getDescription() {return description;}} }我們通過自定義的 ActionsLogsAuditListener 來監(jiān)聽我們要處理日志的實(shí)體,然后將事件變更,通過消息隊(duì)列進(jìn)行異步處理,這樣就可以完全解耦了。當(dāng)然了,這里我們解耦的方式也可以通過 Spring 的事件機(jī)制進(jìn)行解決。通過工作中的此示例,來幫助大家更好的理解 Audit 的機(jī)制,順便說一下處理操作的日志的正確思路,記錄當(dāng)前真實(shí)發(fā)生的數(shù)據(jù)和狀態(tài),及其時(shí)間即可,具體變化了什么那是在業(yè)務(wù)展示層面上要做的事情,這里沒有必要做比對(duì)的事情,記住這一點(diǎn)之后就會(huì)讓你的日志處理實(shí)現(xiàn)機(jī)制豁然明朗,變得容易許多。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的Spring Data JPA 从入门到精通~Auditing及其事件详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux离线安装docker,cent
- 下一篇: Inline Method(内联函数)