Справочная информация. В бизнесе часто необходимо отправлять сообщения или события для асинхронного вызова других компонентов для выполнения соответствующих бизнес-операций после выполнения операций с базой данных (завершение отправки транзакции).
Например: после успешной регистрации пользователя отправляется код активации или электронное письмо с активацией. Если пользователь сохраняет данные, выполняется асинхронная операция для отправки кода активации или электронного письма с активацией. Однако после сохранения предыдущим пользователем происходит откат базы данных, и происходит откат базы данных. На самом деле пользователь не прошел успешную регистрацию, но получил код активации или электронное письмо с активацией. На этом этапе нам срочно требуется завершить транзакцию базы данных перед выполнением асинхронных операций.
@Autowired
private UserDao userDao;
@Autowired
private JmsProducer jmsProducer;
public User saveUser(User user) {
// 保存用户
userDao.save(user);
// 发送激活码或激活邮件
jmsProducer.sendEmail(user.getId());
}
// -------------------------------------
public void sendEmail(int userId) {
/*
* 获取待接收邮件的用户。(如果上面的保存用户方法还未提交事务,则实际数据还未插入到数据库中,此时会返回null)
*/
User user = userDao.get(userId);
// 可能抛NullPointException
String email = user.getEmail();
mailService.send(email);
}
Решение
1. После Spring 4.2 используйте аннотацию @TransactionalEventListener
/**
* 业务Service
*/
@Service
@Transactional
public class FooService {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
public User saveUser(User user) {
userDao.save(user);
// 注册事件
applicationEventPublisher.publishEvent(new SavedUserEvent(user.getId()));
}
}
// -------------------------------------
/**
* 保存用户事件
*/
public class SavedUserEvent {
private int userId;
public SavedUserEvent(int userId) {
this.userId = userId;
}
// getter and setter
}
// ---------------------------------
/**
* 事件侦听,处理对应事件
*/
@Component
public class FooEventListener() {
@Autowired
private UserDao userDao;
@Autowired
private MailService mailService;
@TransactionalEventListener
public sendEmail(SavedUserEvent savedUserEvent) {
User user = userDao.get(userId);
String email = user.getEmail();
mailService.send(email);
}
}
Использование TransactionSynchronizationManager и TransactionSynchronizationAdapter
@Autowired
private UserDao userDao;
@Autowired
private JmsProducer jmsProducer;
public User saveUser(User user) {
// 保存用户
userDao.save(user);
final int userId = user.getId();
// 事务提交后调用
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
jmsProducer.sendEmail(userId);
}
});
}
Примечание. Приведенный выше код будет выполнен после фиксации транзакции.Если он находится в нетранзакционном контексте, будет выдано исключение java.lang.IllegalStateException: синхронизация транзакции не активна.
Улучшенный код:
@Autowired
private UserDao userDao;
@Autowired
private JmsProducer jmsProducer;
public User saveUser(User user) {
// 保存用户
userDao.save(user);
final int userId = user.getId();
// 兼容无论是否有事务
if(TransactionSynchronizationManager.isActualTransactionActive()) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
jmsProducer.sendEmail(userId);
}
});
} else {
jmsProducer.sendEmail(userId);
}
}