详解基于Spring Data的领域事件发布
领域事件发布是一个领域对象为了让其它对象知道自己已经处理完成某个操作时发出的一个通知,事件发布力求从代码层面让自身对象与外部对象解耦,并减少技术代码入侵。
一、手动发布事件
//实体定义 @Entity publicclassDepartmentimplementsSerializable{ @Id @GeneratedValue(strategy=GenerationType.IDENTITY) privateIntegerdepartmentId; @Enumerated(EnumType.STRING) privateStatestate; } //事件定义 publicclassDepartmentEvent{ privateDepartmentdepartment; privateStatestate; publicDepartmentEvent(Departmentdepartment){ this.department=department; state=department.getState(); } } //领域服务 @Service publicclassApplicationService{ @Autowired privateApplicationEventPublisherapplicationEventPublisher; @Autowired privateDepartmentRepositorydepartmentRepository; @Transactional(rollbackFor=Exception.class) publicvoiddepartmentAdd(Departmentdepartment){ departmentRepository.save(department); //事件发布 applicationEventPublisher.publishEvent(newDepartmentEvent(department)); } }
使用applicationEventPublisher.publishEvent在领域服务处理完成后发布领域事件,此方法需要在业务代码中显式发布事件,并在领域服务里引入ApplicationEventPublisher类,但对领域服务本身有一定的入侵性,但灵活性较高。
二、自动发布事件
//实体定义 @Entity publicclassSaleOrderimplementsSerializable{ @Id @GeneratedValue(strategy=GenerationType.IDENTITY) privateIntegerorderId; @Enumerated(EnumType.STRING) privateStatestate; //返回类型定义 @DomainEvents publicList
使用@DomainEvents定义事件返回的类型,必须是一个集合,使用@AfterDomainEventPublication定义事件发布后的回调。
此方法实事件类型定义在实体中,与领域服务完全解耦,没有入侵。系统会在orderRepository.save(saleOrder)后自动调用事件发布,另delete方法不会调用事件发布。
三、事件监听
@Component publicclassApplicationEventProcessor{ @EventListener(condition="#departmentEvent.getState().toString()=='SUCCEED'") publicvoiddepartmentCreated(DepartmentEventdepartmentEvent){ System.err.println("dept-event1:"+departmentEvent); } @Async @TransactionalEventListener(phase=TransactionPhase.AFTER_COMMIT,condition="#saleOrderEvent.getState().toString()=='SUCCEED'") publicvoidsaleOrderCreated(SaleOrderEventsaleOrderEvent){ System.err.println("sale-eventsucceed1:"+saleOrderEvent); } @TransactionalEventListener(phase=TransactionPhase.BEFORE_COMMIT,condition="#saleOrderEvent.getState().toString()=='SUCCEED'") publicvoidsaleOrderCreatedBefore(SaleOrderEventsaleOrderEvent){ System.err.println("sale-eventsucceed2:"+saleOrderEvent); } @Async @TransactionalEventListener(phase=TransactionPhase.AFTER_ROLLBACK) publicvoidsaleOrderCreatedFailed(SaleOrderEventsaleOrderEvent){ System.out.println("sale-eventfailed:"+saleOrderEvent); } }
1.使用@EventListener监听事件
@EventListener没有事务支持,只要事件发出就可监控到
@Transactional(rollbackFor=Exception.class) publicvoiddepartmentAdd(Departmentdepartment){ departmentRepository.save(department); applicationEventPublisher.publishEvent(newDepartmentEvent(department)); thrownewRuntimeException("failed"); }
上述情况会造成事务失败回滚,但事件监控端已经执行,可能导致数据不一致的情况发生
2.使用@TransactionalEventListener监听事件
- TransactionPhase.BEFORE_COMMIT事务提交前
- TransactionPhase.AFTER_COMMIT事务提交后
- TransactionPhase.AFTER_ROLLBACK事务回滚后
- TransactionPhase.AFTER_COMPLETION事务完成后
使用TransactionPhase.AFTER_COMMIT可在事务完成后,再执行事件监听方法,从而保证数据的一致性
3.TransactionPhase.AFTER_ROLLBACK回滚事务问题
@Async @TransactionalEventListener(phase=TransactionPhase.AFTER_ROLLBACK,condition="#departmentEvent.getState().toString()=='SUCCEED'") publicvoiddepartmentCreatedFailed(DepartmentEventdepartmentEvent){ System.err.println("dept-event3:"+departmentEvent); }
由于@DomainEvents作用在实体上的,只有刚orderRepository.save(saleOrder)执行成功后才会发送事件,故AFTER_ROLLBACK方法只会在同一事务中其它语句执行失败或显式rollback时才会执行,如果save方法执行失败,将不会监听到回滚事件。
4.@Async异步事件监听
- 没有此注解事件监听方法与主方法为一个事务。
- 使用此注解将脱离原有事务,BEFORE_COMMIT也无法拦截事务提交前时刻
- 此注解需要配合@EnableAsync一起使用
四、总结
通过对@DomainEvents、@TransactionalEventListener的使用,在有效的解决领域事件发布的情况下,减少了对业务代码的入侵,同时尽一步解决了数据一致性问题。
在分布式结构下,通过MQ发送事件通知给其它服务,为解决一致性问题,防止对方服务处理失败可先将事件保久化到数据库后,再重试。
五、源码
https://gitee.com/hypier/barry-jpa/tree/master/jpa-section-5
到此这篇关于详解基于SpringData的领域事件发布的文章就介绍到这了,更多相关SpringData领域事件内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!