Seata0.9 + mybatis在INSERT语句中手动指定主键(非数据库生成),然而seata依然尝试获取Auto主键,导致null报错的问题分析以及解决办法
<insert id="insert" parameterType="ChatRecord">
INSERT INTO `chat_record` (`chat_record_id`, `member_id`, `request_timestamp`, `response_timestamp`, `consumed_points`)
VALUES (#{chatRecordId}, #{memberId}, #{requestTimestamp}, #{responseTimestamp}, #{consumedPoints})
</insert>
上面是Mybatis配置的INSERT语句,这里的 chat_record_id 就是主键,在尝试执行这个语句后会抛出如下错误:
2025-05-22 17:48:02.582 ERROR [chat-service-provider,efe1d7cf4704c7c7,efe1d7cf4704c7c7,true] 18884 --- [io-22100-exec-2] i.s.r.d.exec.AbstractDMLBaseExecutor : execute executeAutoCommitTrue error:nullio.seata.common.exception.ShouldNeverHappenException: null
at io.seata.rm.datasource.exec.InsertExecutor.defaultByAuto(InsertExecutor.java:287) ~[seata-all-0.9.0.jar:0.9.0]
at io.seata.rm.datasource.exec.InsertExecutor.getPkValuesByAuto(InsertExecutor.java:210) ~[seata-all-0.9.0.jar:0.9.0]
at io.seata.rm.datasource.exec.InsertExecutor.afterImage(InsertExecutor.java:79) ~[seata-all-0.9.0.jar:0.9.0]
可以看到这里出现了一个defaultByAuto的东西,这不应该啊,按理来说我们手动添加了主键这个方法不应该被调用才对,一定是哪里出现了问题,经过DEBUG我注意到了这两段代码
protected TableRecords afterImage(TableRecords beforeImage) throws SQLException {
//Pk column exists or PK is just auto generated
List<Object> pkValues = containsPK() ? getPkValuesByColumn() :
(containsColumns() ? getPkValuesByAuto() : getPkValuesByColumn());
TableRecords afterImage = buildTableRecords(pkValues);
if (afterImage == null) {
throw new SQLException("Failed to build after-image for insert");
}
return afterImage;
}
protected boolean containsPK() {
SQLInsertRecognizer recognizer = (SQLInsertRecognizer) sqlRecognizer;
List<String> insertColumns = recognizer.getInsertColumns();
TableMeta tmeta = getTableMeta();
return tmeta.containsPK(insertColumns);
}
可以发现当containsPK为false时,会尝试取获取auto主键,那么这里到底是怎么检测的呢?
我们来看看这个insertColums到底是个什么东西
看上去不就是我们的插入的字段吗,和MyBatis的确实对应啊?
不对,注意,比如索引0的位置是" ` chat_record_id ` ",没错,Seata并没有去除INSERT语句里的反引号!让我们继续往下看
public boolean containsPK(List<String> cols) {
if (cols == null) {
return false;
}
List<String> pk = getPrimaryKeyOnlyName();
if (pk.isEmpty()) {
return false;
}
if (cols.containsAll(pk)) {
return true;
} else {
return CollectionUtils.toUpperList(cols).containsAll(CollectionUtils.toUpperList(pk));
}
}
我们发现判断是否去获取auto主键的核心在于clos.containsAll(pk)为false,那么这个pk是个什么呢?我们再看看
"chat_record_id",那没有问题啊?我们回顾一下cols存放的是什么
" ` chat_record_id ` ",没错是这个玩意儿,区别在于这对多出来的反引号导致"chat_record_id".equls("`chat_record_id`")为false,即没有在INSERT语句里找到主键,让Seata去尝试自动获取
既然已经知道问题的根源那么解决就很简单了只需要在xml文件里去掉`chat_record_id`的这对反引号就行了
<insert id="insert" parameterType="ChatRecord">
INSERT INTO `chat_record` (chat_record_id, `member_id`, `request_timestamp`, `response_timestamp`, `consumed_points`)
VALUES (#{chatRecordId}, #{memberId}, #{requestTimestamp}, #{responseTimestamp}, #{consumedPoints})
</insert>