详解基于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 领域事件内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 微服务领域Spring Boot自动伸缩的实现方法

    前言 自动伸缩是每个人都想要的,尤其是在微服务领域.让我们看看如何在基于Spring Boot的应用程序中实现. 我们决定使用Kubernetes.Pivotal Cloud Foundry或HashiCorp's Nomad等工具的一个更重要的原因是为了让系统可以自动伸缩.当然,这些工具也提供了许多其他有用的功能,在这里,我们只是用它们来实现系统的自动伸缩.乍一看,这似乎很困难,但是,如果我们使用Spring Boot来构建应用程序,并使用Jenkins来实现CI,那么就用不了太多工作. 今天

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

    领域事件发布是一个领域对象为了让其它对象知道自己已经处理完成某个操作时发出的一个通知,事件发布力求从代码层面让自身对象与外部对象解耦,并减少技术代码入侵. 一. 手动发布事件 // 实体定义 @Entity public class Department implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer departmentId; @Enumerate

  • 详解基于Spring Boot与Spring Data JPA的多数据源配置

    由于项目需要,最近研究了一下基于spring Boot与Spring Data JPA的多数据源配置问题.以下是传统的单数据源配置代码.这里使用的是Spring的Annotation在代码内部直接配置的方式,没有使用任何XML文件. @Configuration @EnableJpaRepositories(basePackages = "org.lyndon.repository") @EnableTransactionManagement @PropertySource("

  • 详解基于Spring Boot/Spring Session/Redis的分布式Session共享解决方案

    分布式Web网站一般都会碰到集群session共享问题,之前也做过一些Spring3的项目,当时解决这个问题做过两种方案,一是利用nginx,session交给nginx控制,但是这个需要额外工作较多:还有一种是利用一些tomcat上的插件,修改tomcat配置文件,让tomcat自己去把Session放到Redis/Memcached/DB中去.这两种各有优缺,也都能解决问题. 但是现在项目全线Spring Boot,并不自己维护Tomcat,而是由Spring去启动Tomcat.这样就会有一

  • 详解基于Spring Cloud几行配置完成单点登录开发

    单点登录概念 单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一.SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统.登录逻辑如上图 基于Spring 全家桶的实现 技术选型: Spring Boot Spring Cloud Spring Security oAuth2 客户端: maven依赖 <dependency> <groupId>org.springframework.boot</g

  • 详解使用Spring Data repository进行数据层的访问问题

    目录 使用Spring Data repository进行数据层的访问 核心概念 查询方法 定义查询方法 CREATE USE_DECLARED_QUERY CREATE_IF_NOT_FOUND WEB支持 使用Spring Data repository进行数据层的访问 抽象出Spring Data repository是因为在开发过程中,常常会为了实现不同持久化存储的数据访问层而写大量的大同小异的代码. Spring Data repository的目的就是要大幅减少这些重复的代码. Sp

  • 详解基于spring多数据源动态调用及其事务处理

    需求: 有些时候,我们需要连接多个数据库,但是,在方法调用前并不知道到底是调用哪个.即同时保持多个数据库的连接,在方法中根据传入的参数来确定. 下图的单数据源的调用和多数据源动态调用的流程,可以看出在Dao层中需要有一个DataSource选择器,来确定到底是调用哪个数据源. 实现方式 对Dao层提供一个公共父类,保持有多个数据源的连接(本人是基于iBatis,即保持多个SQLSessionTemplate) /** * Created by hzlizhou on 2017/2/6. */ p

  • 详解JAVA Spring 中的事件机制

    说到事件机制,可能脑海中最先浮现的就是日常使用的各种 listener,listener去监听事件源,如果被监听的事件有变化就会通知listener,从而针对变化做相应的动作.这些listener是怎么实现的呢?说listener之前,我们先从设计模式开始讲起. 观察者模式 观察者模式一般包含以下几个对象: Subject:被观察的对象.它提供一系列方法来增加和删除观察者对象,同时它定义了通知方法notify().目标类可以是接口,也可以是抽象类或具体类. ConcreteSubject:具体的

  • 详解基于JWT的springboot权限验证技术实现

    JWT简介 Json Web Token(JWT):JSON网络令牌,是为了在网络应用环境间传递声明而制定的一种基于JSON的开放标准((RFC 7519).JWT是一个轻便的安全跨平台传输格式,定义了一个紧凑的自包含的方式用于通信双方之间以 JSON 对象行使安全的传递信息.因为数字签名的存在,这些信息是可信的. 实现步骤: 环境spring boot 1.添加jwt依赖 <dependency> <groupId>com.auth0</groupId> <ar

  • SpringBoot详解整合Spring Boot Admin实现监控功能

    目录 监控 监控的意义 可视化监控平台 监控原理 自定义监控指标 监控 ​ 在说监控之前,需要回顾一下软件业的发展史.最早的软件完成一些非常简单的功能,代码不多,错误也少.随着软件功能的逐步完善,软件的功能变得越来越复杂,功能不能得到有效的保障,这个阶段出现了针对软件功能的检测,也就是软件测试.伴随着计算机操作系统的逐步升级,软件的运行状态也变得开始让人捉摸不透,出现了不稳定的状况.伴随着计算机网络的发展,程序也从单机状态切换成基于计算机网络的程序,应用于网络的程序开始出现,由于网络的不稳定性,

  • 详解基于django实现的webssh简单例子

    本文介绍了详解基于django实现的webssh简单例子,分享给大家,具体如下: 说明 新建一个 django 程序,本文为 chain. 以下仅为简单例子,实际应用 可根据自己平台情况 进行修改. 打开首页后,需要输入1,后台去登录主机,然后返回登录结果. 正常项目 可以post 主机和登录账户,进行权限判断,然后去后台读取账户密码,进行登录. djang后台 需要安装以下模块 安装后会有一个版本号报错,不影响 channels==2.0.2 channels-redis==2.1.0 amq

随机推荐