Spring Data之@DomainEvents注解

背景

在对一个Entity进行save操作时,往往需要触发后续的业务流程,通常采用如下做法

public void saveUser(){
	User user = ...
	user = repository.save(user);

	doSomething(user);
}

public void action(){
	User user = ...
	saveUser(user);
	doSomething(user);
}

其中有一些注意事项,例如

  1. doSomething与saveUser在同一个事务中,需要考虑doSomething中的异常对repository.save(user)的影响
  2. doSomething与saveUser不在同一个事务中,那么在doSomething中查询user时将查询不到,因为saveUser的事务还未提交。
    这种情况则需要将doSomething上移到调用saveUser同级的地方调用这种情况则需要将doSomething上移到调用saveUser同级的地方调用

DomainEvents

近日在Spring Data的官方手册中看到@DomainEvents的介绍。官方解释是由Repositoty管理的Entity是源于聚合根( aggregate roots)的,在领域驱动设计系统中,可以通过聚合根发出领域事件。在Spring Data中可以通过@DomainEvents注解在聚合根的方法上,从而可以简单快捷的发出事件。下面就来看一下,DomainEvents的具体使用效果。
首先定义一个普通的Entity

@Data
@Entity
@Table(name = "t_user")
@AllArgsConstructor
@NoArgsConstructor
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String firstName;

    private String lastName;

    private Integer age;

	//该方法会在userRepository.save()调用时被触发调用
    @DomainEvents
    Collection<UserSaveEvent> domainEvents() {
        return Arrays.asList(new UserSaveEvent(this.id));
    }

}

其中UserSaveEvent的定义如下

@Data
@AllArgsConstructor
public class UserSaveEvent {

    private Long id;

}

再定义一个UserService消费发出的事件

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

	//接受User发出的类型为UserSaveEvent的DomainEvents事件
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void event(UserSaveEvent event){
        System.out.println(userRepository.getOne(event.getId()));
    }

}

其中@TransactionalEventListener注解的phase有多个选项

  • BEFORE_COMMIT
  • AFTER_COMMIT
  • AFTER_ROLLBACK
  • AFTER_COMPLETION

看名字就知道它们的作用和区别了,因为事件是repository.save发出的,这里就涉及到了事务。通过phase的不同选项,就能选择是在事务提交前获取事件,还是提交后,或者混滚的时候。

运行一下单元测试

@Before
public void before(){

    userRepository.saveAll(Arrays.asList(
            new User(null,"刘","一", 20),
            new User(null,"陈","二", 20),
            new User(null,"张","三", 20),
            new User(null,"李","四", 20),
            new User(null,"王","五", 20),
            new User(null,"赵","六", 20),
            new User(null,"孙","七", 20),
            new User(null,"周","八", 20)
    ));
}

控制台输出

Hibernate: insert into t_user (id, age, first_name, last_name) values (null, ?, ?, ?)
Hibernate: insert into t_user (id, age, first_name, last_name) values (null, ?, ?, ?)
Hibernate: insert into t_user (id, age, first_name, last_name) values (null, ?, ?, ?)
Hibernate: insert into t_user (id, age, first_name, last_name) values (null, ?, ?, ?)
User(id=1, firstName=, lastName=, age=20)
User(id=2, firstName=, lastName=, age=20)
User(id=3, firstName=, lastName=, age=20)
User(id=4, firstName=, lastName=, age=20)

上面是使用的phase = TransactionPhase.AFTER_COMMIT,即事务提交后响应事件,所以userRepository.getOne(event.getId())能查询到user对象。如果改成TransactionPhase.BEFORE_COMMIT呢

@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void event(UserSaveEvent event){
    System.out.println(userRepository.getOne(event.getId()));
}

其实效果是一样的也能查询到user,难道BEFORE_COMMIT没起作用?没提交事务前按理是查询不到的才对。
其实是因为session的缓存,因为event方法并没有添加@Async注解异步,也没有@Transactional(value = Transactional.TxType.REQUIRES_NEW)开启新事务,所以这时与发送事件的repository.save还在一个事务内。

扫描二维码关注公众号,回复: 4295760 查看本文章

如果给event方法开启新事务

@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
@Transactional(value = Transactional.TxType.REQUIRES_NEW)
public void event(UserSaveEvent event){
    System.out.println(userRepository.getOne(event.getId()));
}

这样查询就会报错,因为查不到了

org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find com.learn.data.entity.User with id 1; nested exception is javax.persistence.EntityNotFoundException: Unable to find com.learn.data.entity.User with id 1

再将phase改成TransactionPhase.AFTER_COMMIT试试

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Transactional(value = Transactional.TxType.REQUIRES_NEW)
public void event(UserSaveEvent event){
    System.out.println(userRepository.getOne(event.getId()));
}

控制输出

Hibernate: insert into t_user (id, age, first_name, last_name) values (null, ?, ?, ?)
Hibernate: insert into t_user (id, age, first_name, last_name) values (null, ?, ?, ?)
Hibernate: insert into t_user (id, age, first_name, last_name) values (null, ?, ?, ?)
Hibernate: insert into t_user (id, age, first_name, last_name) values (null, ?, ?, ?)
Hibernate: select user0_.id as id1_0_0_, user0_.age as age2_0_0_, user0_.first_name as first_na3_0_0_, user0_.last_name as last_nam4_0_0_ from t_user user0_ where user0_.id=?
User(id=1, firstName=, lastName=, age=20)
Hibernate: select user0_.id as id1_0_0_, user0_.age as age2_0_0_, user0_.first_name as first_na3_0_0_, user0_.last_name as last_nam4_0_0_ from t_user user0_ where user0_.id=?
User(id=2, firstName=, lastName=, age=20)
Hibernate: select user0_.id as id1_0_0_, user0_.age as age2_0_0_, user0_.first_name as first_na3_0_0_, user0_.last_name as last_nam4_0_0_ from t_user user0_ where user0_.id=?
User(id=3, firstName=, lastName=, age=20)
Hibernate: select user0_.id as id1_0_0_, user0_.age as age2_0_0_, user0_.first_name as first_na3_0_0_, user0_.last_name as last_nam4_0_0_ from t_user user0_ where user0_.id=?
User(id=4, firstName=, lastName=, age=20)

现在能查询到了,但控制台里面的查询打出了select语句,与没有添加@Transactional时是不一样了,没有@Transactional注解时是没有select语句的,说明JPA查询的是seesion缓存并没有真正执行查询。

结束

@DomainEvents和@TransactionalEventListener的组合使用,给我们处理实体保存后触发事件。特别是异步事件(给event方法加上@Async,同时开启@EnableAsync)是非常简便的,它是一种领域驱动的思想,让代码显得更加的内聚。

猜你喜欢

转载自blog.csdn.net/f4761/article/details/84622317