詳解基于Spring Data的領(lǐng)域事件發(fā)布
領(lǐng)域事件發(fā)布是一個領(lǐng)域?qū)ο鬄榱俗屍渌鼘ο笾雷约阂呀?jīng)處理完成某個操作時發(fā)出的一個通知,事件發(fā)布力求從代碼層面讓自身對象與外部對象解耦,并減少技術(shù)代碼入侵。
一、 手動發(fā)布事件
// 實體定義 @Entity public class Department implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer departmentId; @Enumerated(EnumType.STRING) private State state; } // 事件定義 public class DepartmentEvent { private Department department; private State state; public DepartmentEvent(Department department) { this.department = department; state = department.getState(); } } // 領(lǐng)域服務(wù) @Service public class ApplicationService { @Autowired private ApplicationEventPublisher applicationEventPublisher; @Autowired private DepartmentRepository departmentRepository; @Transactional(rollbackFor = Exception.class) public void departmentAdd(Department department) { departmentRepository.save(department); // 事件發(fā)布 applicationEventPublisher.publishEvent(new DepartmentEvent(department)); } }
使用applicationEventPublisher.publishEvent
在領(lǐng)域服務(wù)處理完成后發(fā)布領(lǐng)域事件,此方法需要在業(yè)務(wù)代碼中顯式發(fā)布事件,并在領(lǐng)域服務(wù)里引入ApplicationEventPublisher類,但對領(lǐng)域服務(wù)本身有一定的入侵性,但靈活性較高。
二、 自動發(fā)布事件
// 實體定義 @Entity public class SaleOrder implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer orderId; @Enumerated(EnumType.STRING) private State state; // 返回類型定義 @DomainEvents public List<Object> domainEvents(){ return Stream.of(new SaleOrderEvent(this)).collect(Collectors.toList()); } // 事件發(fā)布后callback @AfterDomainEventPublication void callback() { System.err.println("ok"); } } // 事件定義 public class SaleOrderEvent { private SaleOrder saleOrder; private State state; public SaleOrderEvent(SaleOrder saleOrder) { this.saleOrder = saleOrder; state = saleOrder.getState(); } } // 領(lǐng)域服務(wù) @Service public class ApplicationService { @Autowired private OrderRepository orderRepository; @Transactional(rollbackFor = Exception.class) public void saleOrderAdd(SaleOrder saleOrder) { orderRepository.save(saleOrder); } }
使用@DomainEvents
定義事件返回的類型,必須是一個集合,使用@AfterDomainEventPublication
定義事件發(fā)布后的回調(diào)。
此方法實事件類型定義在實體中,與領(lǐng)域服務(wù)完全解耦,沒有入侵。系統(tǒng)會在orderRepository.save(saleOrder)后自動調(diào)用事件發(fā)布,另delete方法不會調(diào)用事件發(fā)布。
三、 事件監(jiān)聽
@Component public class ApplicationEventProcessor { @EventListener(condition = "#departmentEvent.getState().toString() == 'SUCCEED'") public void departmentCreated(DepartmentEvent departmentEvent) { System.err.println("dept-event1:" + departmentEvent); } @Async @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, condition = "#saleOrderEvent.getState().toString() == 'SUCCEED'") public void saleOrderCreated(SaleOrderEvent saleOrderEvent) { System.err.println("sale-event succeed1:" + saleOrderEvent); } @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT, condition = "#saleOrderEvent.getState().toString() == 'SUCCEED'") public void saleOrderCreatedBefore(SaleOrderEvent saleOrderEvent) { System.err.println("sale-event succeed2:" + saleOrderEvent); } @Async @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK) public void saleOrderCreatedFailed(SaleOrderEvent saleOrderEvent) { System.out.println("sale-event failed:" + saleOrderEvent); } }
1. 使用@EventListener監(jiān)聽事件
@EventListener沒有事務(wù)支持,只要事件發(fā)出就可監(jiān)控到
@Transactional(rollbackFor = Exception.class) public void departmentAdd(Department department) { departmentRepository.save(department); applicationEventPublisher.publishEvent(new DepartmentEvent(department)); throw new RuntimeException("failed"); }
上述情況會造成事務(wù)失敗回滾,但事件監(jiān)控端已經(jīng)執(zhí)行,可能導(dǎo)致數(shù)據(jù)不一致的情況發(fā)生
2. 使用@TransactionalEventListener監(jiān)聽事件
- TransactionPhase.BEFORE_COMMIT 事務(wù)提交前
- TransactionPhase.AFTER_COMMIT 事務(wù)提交后
- TransactionPhase.AFTER_ROLLBACK 事務(wù)回滾后
- TransactionPhase.AFTER_COMPLETION 事務(wù)完成后
使用TransactionPhase.AFTER_COMMIT
可在事務(wù)完成后,再執(zhí)行事件監(jiān)聽方法,從而保證數(shù)據(jù)的一致性
3. TransactionPhase.AFTER_ROLLBACK回滾事務(wù)問題
@Async @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK, condition = "#departmentEvent.getState().toString() == 'SUCCEED'") public void departmentCreatedFailed(DepartmentEvent departmentEvent) { System.err.println("dept-event3:" + departmentEvent); }
由于@DomainEvents作用在實體上的,只有剛orderRepository.save(saleOrder)執(zhí)行成功后才會發(fā)送事件,故AFTER_ROLLBACK方法只會在同一事務(wù)中其它語句執(zhí)行失敗或顯式rollback時才會執(zhí)行,如果save方法執(zhí)行失敗,將不會監(jiān)聽到回滾事件。
4. @Async異步事件監(jiān)聽
- 沒有此注解事件監(jiān)聽方法與主方法為一個事務(wù)。
- 使用此注解將脫離原有事務(wù),BEFORE_COMMIT也無法攔截事務(wù)提交前時刻
- 此注解需要配合@EnableAsync一起使用
四、 總結(jié)
通過對 @DomainEvents、@TransactionalEventListener的使用,在有效的解決領(lǐng)域事件發(fā)布的情況下,減少了對業(yè)務(wù)代碼的入侵,同時盡一步解決了數(shù)據(jù)一致性問題。
在分布式結(jié)構(gòu)下,通過MQ發(fā)送事件通知給其它服務(wù),為解決一致性問題,防止對方服務(wù)處理失敗可先將事件保久化到數(shù)據(jù)庫后,再重試。
五、 源碼
https://gitee.com/hypier/barry-jpa/tree/master/jpa-section-5
到此這篇關(guān)于詳解基于Spring Data的領(lǐng)域事件發(fā)布的文章就介紹到這了,更多相關(guān)Spring Data 領(lǐng)域事件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 解析Spring事件發(fā)布與監(jiān)聽機(jī)制
- Springboot啟用多個監(jiān)聽端口代碼實例
- Spring Boot監(jiān)聽Redis Key失效事件實現(xiàn)定時任務(wù)的示例
- springboot+redis過期事件監(jiān)聽實現(xiàn)過程解析
- SpringBoot啟動應(yīng)用及回調(diào)監(jiān)聽原理解析
- SpringBoot實現(xiàn)攔截器、過濾器、監(jiān)聽器過程解析
- SpringMVC事件監(jiān)聽ApplicationListener實例解析
- 詳解Spring事件發(fā)布與監(jiān)聽機(jī)制
相關(guān)文章
SpringCloud?客戶端Ribbon負(fù)載均衡的實現(xiàn)方法
Ribbon 是 Netflix 提供的一個基于 Http 和 TCP 的客戶端負(fù)載均衡工具,且已集成在 Eureka 依賴中,這篇文章主要介紹了SpringCloud?客戶端Ribbon負(fù)載均衡的實現(xiàn)方法,需要的朋友可以參考下2022-06-06Java String 和StringBuffer的詳解及區(qū)別
這篇文章主要介紹了Java String 和StringBuffer的詳解及區(qū)別的相關(guān)資料,需要的朋友可以參考下2017-05-05解析Java的Jackson庫中對象的序列化與數(shù)據(jù)泛型綁定
這篇文章主要介紹了解析Java的Jackson庫中對象的序列化與數(shù)據(jù)泛型綁定,Jackson通常被用來實現(xiàn)Java對象和JSON數(shù)據(jù)的相互轉(zhuǎn)換功能,需要的朋友可以參考下2016-01-01Spring Boot中Redis數(shù)據(jù)庫的使用實例
Spring Boot中除了對常用的關(guān)系型數(shù)據(jù)庫提供了優(yōu)秀的自動化支持之外,對于很多NoSQL數(shù)據(jù)庫一樣提供了自動化配置的支持。本篇文章主要介紹了Spring Boot中Redis的使用實例代碼,有興趣的開業(yè)了解一下。2017-04-04Java實現(xiàn)簡易生產(chǎn)者消費者模型過程解析
這篇文章主要介紹了Java實現(xiàn)簡易生產(chǎn)者消費者模型過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-06-06Hibernate映射解析之關(guān)聯(lián)映射詳解
所謂關(guān)聯(lián)映射就是將關(guān)聯(lián)關(guān)系映射到數(shù)據(jù)庫里,在對象模型中就是一個或多個引用。下面這篇文章詳細(xì)的給大家介紹了Hibernate映射解析之關(guān)聯(lián)映射的相關(guān)資料,需要的朋友可以參考借鑒,下面來一起看看吧。2017-02-02