why?
一开始写了一段delete的sql,也用GloblaTransaction注解了,但是打断点的时候,发现在undo-log表 没找到branch_id,即并没有生成分支事务。就很好奇,所以不断的打断点找原因。
原因:
在执行delete操作时,会通过 DeleteExecutor 根据查询条件拼接查询语句,获得删除前的数据量。
获得表里的数据量-beforeImage,
然后 创建一个空的afterImage
根据beforeImage,afterImage是否为空来 准备undoLog,如果before为空(after肯定为空)就不创建 分支事务
protected T executeAutoCommitFalse(Object[] args) throws Exception {
if (!JdbcConstants.MYSQL.equalsIgnoreCase(getDbType()) && isMultiPk()) {
throw new NotSupportYetException("multi pk only support mysql!");
}
// 获得
TableRecords beforeImage = beforeImage();
T result = statementCallback.execute(statementProxy.getTargetStatement(), args);
TableRecords afterImage = afterImage(beforeImage);
prepareUndoLog(beforeImage, afterImage);
return result;
}
@Override
// 会根据查询条件拼接查询语句,获得删除前的数据量
protected TableRecords beforeImage() throws SQLException {
SQLDeleteRecognizer visitor = (SQLDeleteRecognizer) sqlRecognizer;
TableMeta tmeta = getTableMeta(visitor.getTableName());
ArrayList<List<Object>> paramAppenderList = new ArrayList<>();
String selectSQL = buildBeforeImageSQL(visitor, tmeta, paramAppenderList);
return buildTableRecords(tmeta, selectSQL, paramAppenderList);
}
//io.seata.rm.datasource.exec.BaseTransactionalExecutor#prepareUndoLog
protected void prepareUndoLog(TableRecords beforeImage, TableRecords afterImage) throws SQLException {
if (beforeImage.getRows().isEmpty() && afterImage.getRows().isEmpty()) {
return;
}
ConnectionProxy connectionProxy = statementProxy.getConnectionProxy();
TableRecords lockKeyRecords = sqlRecognizer.getSQLType() == SQLType.DELETE ? beforeImage : afterImage;
String lockKeys = buildLockKey(lockKeyRecords);
connectionProxy.appendLockKey(lockKeys);//操作1
SQLUndoLog sqlUndoLog = buildUndoItem(beforeImage, afterImage);
connectionProxy.appendUndoLog(sqlUndoLog);//操作2
}
//操作1,2执行后,ConnectionContext内对应参数集合size>0
//进而影响 到 io.seata.rm.datasource.ConnectionProxy#register
private void register() throws TransactionException {
if (!context.hasUndoLog() || context.getLockKeysBuffer().isEmpty()) {//由于上面都append了,就会执行下面的 分支事务id的注册
return;
}
//向服务端注册 分支事务,并返回branchId
Long branchId = DefaultResourceManager.get().branchRegister(
BranchType.AT,getDataSourceProxy().getResourceId(),null,
context.getXid(), null, context.buildLockKeys());
context.setBranchId(branchId);
}
//io.seata.rm.AbstractResourceManager#branchRegister
@Override
public Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid, String applicationData, String lockKeys) throws TransactionException {
try {
BranchRegisterRequest request = new BranchRegisterRequest();
request.setXid(xid);
request.setLockKey(lockKeys);
request.setResourceId(resourceId);
request.setBranchType(branchType);
request.setApplicationData(applicationData);
//向seata服务端发送请求,获得branchId
BranchRegisterResponse response = (BranchRegisterResponse) RmNettyRemotingClient.getInstance().sendSyncRequest(request);
if (response.getResultCode() == ResultCode.Failed) {
throw new RmTransactionException(response.getTransactionExceptionCode(), String.format("Response[ %s ]", response.getMsg()));
}
return response.getBranchId();
} catch (TimeoutException toe) {
throw new RmTransactionException(TransactionExceptionCode.IO, "RPC Timeout", toe);
} catch (RuntimeException rex) {
throw new RmTransactionException(TransactionExceptionCode.BranchRegisterFailed, "Runtime", rex);
}
}
与mybatis-plus结合时,失效的解决方法
当seata+mybatis-plus结合时,由于mp是通过 MybatisSqlSessionFactoryBean创建SqlSessionFactory的,所以需要将 DataSourceProxy 作为参数 传进去
另外 当:手动由spring实例化时,主键的类型 将是 IdType.ID_WORKER,如果主键是int类型会超出长度,所以需要 设置DbConfig.IdType = IdType.AUTO
@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
// 这里用 MybatisSqlSessionFactoryBean 代替了 SqlSessionFactoryBean,否则 MyBatisPlus 不会生效
MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
mybatisSqlSessionFactoryBean.setDataSource(dataSourceProxy);
mybatisSqlSessionFactoryBean.setTypeAliasesPackage("com.*.pojo.model");
mybatisSqlSessionFactoryBean.setTypeHandlersPackage("com.*.common.mybatisplus.handler");
mybatisSqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:/mapper/*.xml"));
GlobalConfig globalConfig = new GlobalConfig();
GlobalConfig.DbConfig dbConfig = new GlobalConfig.DbConfig();
dbConfig.setIdType(IdType.AUTO);
globalConfig.setDbConfig(dbConfig);
mybatisSqlSessionFactoryBean.setGlobalConfig(globalConfig);
return mybatisSqlSessionFactoryBean.getObject();
}