这篇文章主要讲解TransactionalEventListener是怎样工作的?适合在什么场景,能解决哪些问题?以及和EventListener不同之处。
示例
这里举个业务场景,假如我们有个需求,用户创建成功后给用户发送一个邮件。这里有两个事情要做:
- 创建用户
- 给用户发送邮件
对于这种需求,我们可能会不假思索的有以下实现。
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
public User() {
}
...
//getters
//equals and hashcode
}
为User创建个Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
@Service
public class EmailService{
@Transactional
public void sendEmail(String email) {
//send email
}
}
@Service
public class UserService {
private final EmailService emailService;
private final UserRepository userRepository;
public UserService(EmailService emailService, UserRepository userRepository) {
this.emailService = emailService;
this.userRepository = userRepository;
}
@Transactional
public User createUser(User user) {
User newUser = userRepository.save(user);
emailService.sendEmail(user.getEmail());
return newUser;
}
}
对于上面的实现,是最容易实现的,但这种实现是有问题的。我们想一下,这个功能的核心是创建用户,而发送邮件是一个副作用(发送邮件不能影响用户的创建),如果把这两个操作放在一个事务中会有什么问题?其实很明显,如果创建用户时抛出异常,事务回滚,方法提前退出,那么也不会发送邮件,这是正常的。但是下面两个场景是不可接受的:
- 如果邮件发送失败,事务发生回滚,用户创建失败。
- 如果邮件发送成功后,事务提交失败,这下就尴尬了,用户收到了邮件,可是用户创建失败。
虽然这些情况出现的概率很小,但作为对自己有要求的程序猿,这是不可容忍的,我们要对自己写的业务负责。
好了,我们对上面的实现做个重构,直接将创建用户和发送邮件的业务代码拆开,使用Spring application event的方式解耦实现。
修改后的Service是这样的
@Service
public class CustomerService {
private final UserRepository userRepository;
private final ApplicationEventPublisher applicationEventPublisher;
public CustomerService(UserRepository userRepository, ApplicationEventPublisher applicationEventPublisher)