Spring Boot 2.x实战 - Spring Data12 - Spring Data JPA领域事件(Domain Events)
领域事件
由于在DDD中采用了“设计小聚合”的原则,因此避免了领域模型的相互关联,从而避免了在应用演进中形成“大泥球”(Big Ball of Mud),也因为上述的原因,本书将不讲解@OneToMany、@ManyToMany等关联注解。聚合之间在没有了关联关系后,聚合之间的数据通讯通过领域事件来完成,领域事件是由聚合根发出的。
Spring Data对领域事件做了专门的支持,使用@DomainEvents
注解注册领域事件或者继承AbstractAggregateRoot
使用它的registerEvent
方法注册事件。
@Data @AllArgsConstructor @NoArgsConstructor @Entity public class Person { @DomainEvents // 使用集合类注册事件列表 Collection<Object> domainEvents(){ List<Object> events= new ArrayList<Object>(); events.add(new PersonSaved(this.id, this.name, this.age)); return events; } @AfterDomainEventPublication //所有事件发布完成后调用,一般用来清空事件列表 void callbackMethod() { domainEvents().clear(); } }
当Repository每一次调用save
方法时,领域事件都会被发布。
我们的领域事件定义:
import lombok.Value; @Value public class PersonSaved { private Long id; private String name; private Integer age; }
我们现在定义另外一个聚合根,为雇员(Employee
),它和Person
是一一对应的关系,但是多了公司的信息。基于设计小聚合的原则,我们没有给他们配置@OneToOne
,而是当Person
保存成功后发布领域事件PersonSaved
,在事件监听的位置我们在另外一个事务中新建对应的Employee
。小聚合的另外一个好处就是将事务边界变小从而有更快的速度和更好的性能。
新的雇员聚合:
@Data @AllArgsConstructor @NoArgsConstructor @Entity public class Employee { @Id private Long id; private String name; private Integer age; @Embedded private Company company; }
@Data @AllArgsConstructor @NoArgsConstructor @Embeddable public class Company { private String name; private String city; }
聚合的Repository:
public interface EmployeeRepository extends JpaRepository<Employee, Long> { List<Employee> findByName(String name); }
领域发送的事件是Spring事件,我们可以使用@EventListener
来接受,Spring Data给我们提供了专门的事务监听注解@TransactionalEventListener
,它组合了@EventListener
。
@Component public class DomainEventListener { private EmployeeRepository employeeRepository; public DomainEventListener(EmployeeRepository employeeRepository) { this.employeeRepository = employeeRepository; } @Async //1 @TransactionalEventListener public void handlePersonSavedEvent(PersonSaved personSaved){ Company company = new Company("某某公司", "hefei"); Optional<Employee> employeeOptional = employeeRepository.findById(personSaved.getId()); employeeOptional.ifPresent(employee -> { //2 employee.setName(personSaved.getName()); employee.setAge(personSaved.getAge()); employeeRepository.save(employee); return; }); employeeRepository.save(new Employee(personSaved.getId(), personSaved.getName(), personSaved.getAge(), company));//3 } }
使用@Async
注解让处理在另外一个线程处理;需要在配置类启用异步支持@EnableAsync
@SpringBootApplication @EnableAsync public class LearningSpringDataJpaApplication {}
-
若存在则更新雇员;
-
若不存在则保存新的雇员。
- 执行检验代码:
-
@Bean CommandLineRunner domainEvents(PersonRepository personRepository, EmployeeRepository employeeRepository){ return args -> { Address address = new Address("nanjing","Jiangsu"); Collection<Child> children = Arrays.asList(new Child("xxxx", Gender.FEMALE)); Person savedPerson = personRepository.save(new Person("wwww", 33, address, children)); Thread.sleep(100); //监听是在异步线程执行的,所以需等待 List<Employee> employees1 = employeeRepository.findByName("wwww"); employees1.forEach(System.out::println); savedPerson.setName("wwwww"); personRepository.save(savedPerson); Thread.sleep(100); List<Employee> employees2 = employeeRepository.findByName("wwwww"); employees2.forEach(System.out::println); }; }
- 转自:https://blog.csdn.net/wiselyman/article/details/106563016
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架