详解基于Spring Data的领域事件发布

 更新时间:2020年04月02日 09:22:04   作者:barry的异想世界  
这篇文章主要介绍了详解基于Spring Data的领域事件发布,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

领域事件发布是一个领域对象为了让其它对象知道自己已经处理完成某个操作时发出的一个通知,事件发布力求从代码层面让自身对象与外部对象解耦,并减少技术代码入侵。

一、 手动发布事件

// 实体定义
@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();
  }
}

// 领域服务
@Service
public class ApplicationService {

  @Autowired
  private ApplicationEventPublisher applicationEventPublisher;

  @Autowired
  private DepartmentRepository departmentRepository;

  @Transactional(rollbackFor = Exception.class)
  public void departmentAdd(Department department) {
    departmentRepository.save(department);
    // 事件发布
    applicationEventPublisher.publishEvent(new DepartmentEvent(department));
  }
}

使用applicationEventPublisher.publishEvent在领域服务处理完成后发布领域事件,此方法需要在业务代码中显式发布事件,并在领域服务里引入ApplicationEventPublisher类,但对领域服务本身有一定的入侵性,但灵活性较高。

二、 自动发布事件

// 实体定义
@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());
  }

  // 事件发布后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();
  }
}

// 领域服务
@Service
public class ApplicationService {
  @Autowired
  private OrderRepository orderRepository;
  
  @Transactional(rollbackFor = Exception.class)
  public void saleOrderAdd(SaleOrder saleOrder) {
    orderRepository.save(saleOrder);
  }
}

使用@DomainEvents定义事件返回的类型,必须是一个集合,使用@AfterDomainEventPublication定义事件发布后的回调。

此方法实事件类型定义在实体中,与领域服务完全解耦,没有入侵。系统会在orderRepository.save(saleOrder)后自动调用事件发布,另delete方法不会调用事件发布。

三、 事件监听

@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监听事件

@EventListener没有事务支持,只要事件发出就可监控到

@Transactional(rollbackFor = Exception.class)
public void departmentAdd(Department department) {
  departmentRepository.save(department);
  applicationEventPublisher.publishEvent(new DepartmentEvent(department));
  throw new RuntimeException("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'")
public void departmentCreatedFailed(DepartmentEvent departmentEvent) {
  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

到此这篇关于详解基于Spring Data的领域事件发布的文章就介绍到这了,更多相关Spring Data 领域事件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java对接支付宝支付接口简单步骤记录

    java对接支付宝支付接口简单步骤记录

    最近项目APP需要接入微信、支付宝支付功能,在分配开发任务时,听说微信支付接口比支付宝支付接口要难实现,这篇文章主要给大家介绍了关于java对接支付宝支付接口的简单步骤,需要的朋友可以参考下
    2024-05-05
  • Java 泛型总结(三):通配符的使用

    Java 泛型总结(三):通配符的使用

    在泛型的使用中,还有个重要的东西叫通配符,本文介绍通配符的使用。具有很好的参考价值。下面跟着小编一起来看下吧
    2017-03-03
  • Netty分布式NioSocketChannel注册到selector方法解析

    Netty分布式NioSocketChannel注册到selector方法解析

    这篇文章主要为大家介绍了Netty分布式源码分析NioSocketChannel注册到selector方法的解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-03-03
  • spring security与corsFilter冲突的解决方案

    spring security与corsFilter冲突的解决方案

    这篇文章主要介绍了spring security与corsFilter冲突的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • Java图片转字符图片的生成方法

    Java图片转字符图片的生成方法

    本文主要实现了将一张图片转成字符图片,同样可以遍历每个像素点,然后将像素点由具体的字符来替换,从而实现字符化处理,感兴趣的可以了解一下
    2021-11-11
  • SpringBoot之那些注入不了的Spring占位符(${}表达式)问题

    SpringBoot之那些注入不了的Spring占位符(${}表达式)问题

    这篇文章主要介绍了SpringBoot之那些注入不了的Spring占位符(${}表达式)问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04
  • JAVA找不到符号的三种解决方案

    JAVA找不到符号的三种解决方案

    这篇文章主要给大家介绍了关于JAVA找不到符号的三种解决方案, 找不到符号错误主要发生在我们试图引用一个未在我们正在编译的程序中声明的变量时,这意味着编译器不知道我们所引用的Java变量,需要的朋友可以参考下
    2024-03-03
  • SpringBoot异步Async使用Future与CompletableFuture区别小结

    SpringBoot异步Async使用Future与CompletableFuture区别小结

    本文主要介绍了SpringBoot异步Async使用Future与CompletableFuture区别小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • Java排列组合字符串的方法

    Java排列组合字符串的方法

    这篇文章主要介绍了Java排列组合字符串的方法
    2018-02-02
  • 全面了解JAVA_BaseDAO数据处理类

    全面了解JAVA_BaseDAO数据处理类

    下面小编就为大家带来一篇全面了解JAVA_BaseDAO数据处理类。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-07-07

最新评论