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);
    }

可以发现当containsPKfalse时,会尝试取获取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>