书接上文:不使用binlog,canal,kafka等,只用java+mybatis拦截器来实现项目中的异步双写主从数据库,代码逻辑全整理
我目前在双写的项目发现中还差一个缺陷,就是如果有事务注解的情况下,发生了回滚,我的异步双写成功写入,但是主库插入失败,这里就用到了TransactionalEventListener作为参考来实现异步双写的事务回滚
参考如下:TransactionalEventListener使用场景以及实现原理,最后要躲个大坑
然后再我的拦截器中应用,新加package如下
TrxEvent继承ApplicationEvent类,作为传参我自定义了几个字段
public class TrxEvent extends ApplicationEvent {
private String tableName;
private Object obj;
private Map<String,Object> param;
private String sqlCommandType;
private String isObjOrMap;
public TrxEvent(Object source) {
super(source);
}
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
public Map<String, Object> getParam() {
return param;
}
public void setParam(Map<String, Object> param) {
this.param = param;
}
public String getSqlCommandType() {
return sqlCommandType;
}
public void setSqlCommandType(String sqlCommandType) {
this.sqlCommandType = sqlCommandType;
}
public String getIsObjOrMap() {
return isObjOrMap;
}
public void setIsObjOrMap(String isObjOrMap) {
this.isObjOrMap = isObjOrMap;
}
}
其中构造需要实现如下
public TrxEvent(Object source) {
super(source);
}
然后是监听器UserCreatedEventListener,注入我的双写service和ApplicationEventPublisher做发布事件操作
@Component
public class UserCreatedEventListener {
@Autowired
private DoubleWriteService doubleWriteService;
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
public UserCreatedEventListener(DoubleWriteService doubleWriteService) {
this.doubleWriteService = doubleWriteService;
}
@TransactionalEventListener(fallbackExecution = true, phase = TransactionPhase.AFTER_COMMIT)
public void processUserCreatedEvent(TrxEvent event) {
if (event.getIsObjOrMap().equals("obj")){
if (DoubleWriteConstants.UPDATE.equals(event.getSqlCommandType())){
doubleWriteService.update(event.getTableName(),event.getObj());
}else if (DoubleWriteConstants.INSERT.equals(event.getSqlCommandType())){
doubleWriteService.insert(event.getTableName(), event.getObj());
}
}else if (event.getIsObjOrMap().equals("map")){
if (DoubleWriteConstants.UPDATE.equals(event.getSqlCommandType())){
doubleWriteService.update(event.getTableName(),event.getParam());
}else if (DoubleWriteConstants.INSERT.equals(event.getSqlCommandType())){
doubleWriteService.insert(event.getTableName(), event.getParam());
}
}
}
public void publish(String isObjOrMap, String sqlCommandType, Map<String,Object> param,Object obj,String tableName){
TrxEvent event = new TrxEvent(this);
event.setIsObjOrMap(isObjOrMap);
event.setTableName(tableName);
event.setObj(obj);
event.setParam(param);
event.setSqlCommandType(sqlCommandType);
applicationEventPublisher.publishEvent(event);
}
}
关键注解如下,一定要设置事务阶段是AFTER_COMMIT提交后才执行
最后去mybatis拦截器里,调用发布事件
最后调试截图就不贴了,基本都能保证事务成功提交后,从库开始写入。否则失败回滚后,直接从库不执行写入操作