之前说了用消息队列实现了事件中心(不知道算不算),现在虽然换成rabbitmq了但是原理是一样的,这时候就出现了一个问题,就是消息消费的时候,之前的事务还没提交就会有错误。比如编辑机器名称,发送机器编辑事件,在消费消息时把用到机器名称的地方修改机器名称(我们称为冗余字段,这样就减少了关联查询)。但是机器编辑的事务还没有提交。
@Resource
MachineDao machineDao;
@Resource
IMessageSenderService messageSenderService;
@Transactional
public void update(MachineModel machine){
machineDao.save(machine);
//因为消息发送包含在事务下面,所以发送消息的时候事务还没提交
messageSenderService.sendMsg(MessageType.MACHINEEDIT, machine.getId());
}
这时候有两种解决办法,一是把业务方法和消息发送的方法分开,让业务方法的事务不要包含消息发送,但是这样很不合适,消息发送应该和业务代码在同一个方法中,而且如果有一个复合业务包含了这个方法,需要多个方法的事务合并,那么就很麻烦了。
二是使用编程事务,不使用注解的方式,但是在复合业务中同样有问题。
刚开始为此烦恼了很多,并且报了很多错误,使用了很多编程式事务,把产生事件的方法放在复合业务的最后执行,并且事务分开等等等等,有时候不得不破坏事务的一致性。我就想有没有存在一个事务监听的机制,只有监听到事务提交才执行该方法,结果还真被我找到了
创建一个事务监听器
@Component
public class TransactionalMessageListener {
Logger logger = LoggerFactory.getLogger(this.getClass());
@Resource
IMessageSenderService messageSenderService;
@TransactionalEventListener(fallbackExecution = true,phase=TransactionPhase.AFTER_COMMIT)//AFTER_COMMIT是事务提交后执行,默认就是这个
//fallbackExecution=true则会在没有事务时也执行该方法,不然只有加上事务才会监听,切记。默认是false
public void handleMessageSend(MessageDTO message) throws Exception{
logger.info("***********************messageListener************************");
logger.info("messageType:"+message.getMessageType());
logger.info("messageContent"+message.getMessageContent());
String uuid = UUID.randomUUID().toString();
messageSenderService.sendMessage(message.getMessageType(), uuid, message.getMessageContent());
}
}
@Service
public class MessageSenderServiceImpl implements IMessageSenderService{
Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
ApplicationEventPublisher publisher;
/**
* 发送消息到mq
* @param type 消息类型
* @param message 消息内容
* @throws Exception
*/
@Override
public void sendMessage(String type, String message) throws Exception {
//触发事件
publisher.publishEvent(new MessageDTO(type, message));
// String uuid = UUID.randomUUID().toString();
// sendMessage(type, uuid, message);
}
/**
* 发送消息到mq
* @param type 消息类型
* @param key 消息主键
* @param message 消息内容
* @throws Exception
*/
@Override
public void sendMessage(String type, String key, String message) throws Exception {
//发送消息到mq
}
}
把sendMessage(String type, String message)方法改成触发事件,然后监听器会在这个事件所在的事务提交后执行。这样就不用改任何代码,而且即使事务被包含在了其它事务中也没关系。完美。