You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MyBatis用HashMap动态插入后获取Oracle自增主键的问题

解决方案:Oracle下动态获取多表插入的自增主键

针对Oracle数据库中多表动态插入时无法配置动态keyColumn的问题,以下是三种可行方案:

方案一:使用Oracle RETURNING 子句(推荐)

Oracle支持通过RETURNING子句直接返回插入生成的主键值,无需依赖MyBatis的useGeneratedKeys,可完美适配动态主键列名。

修改XML映射文件如下:

<insert id="insert" parameterType="de.gwvs.btix.database.model.GenericRecordMap">
  INSERT INTO ${genericRecordMap.tableName}
  <foreach open="(" close=")" collection="genericRecordMap.tableMap" index="col" separator=",">
    ${col}
  </foreach>
  VALUES
  <foreach open="(" close=")" collection="genericRecordMap.tableMap" item="val" separator=",">
    #{val}
  </foreach>
  -- 动态指定主键列,将生成的值赋值给GenericRecordMap的id属性
  RETURNING ${genericRecordMap.pk} INTO #{id,jdbcType=BIGINT,mode=OUT}
</insert>

注意事项:

  • 确保传入的GenericRecordMap对象的pk字段正确填写目标表的主键列名(如USER_ID、ORDER_ID)
  • 根据主键实际数据类型调整jdbcType参数(例如字符串类型用VARCHAR)

执行插入后,主键值会自动填充到传入的GenericRecordMap对象的id字段中。

方案二:使用序列CURRVAL(适用于序列生成主键的场景)

如果表主键是通过序列生成的(插入时使用序列名.NEXTVAL赋值),可通过<selectKey>动态查询序列当前值。

步骤1:构造插入参数时指定序列值

GenericRecordMap record = GenericRecordMap.builder()
    .tableName("USER")
    .pk("USER_ID")
    .tableMap(new HashMap<String, Object>() {{
        put("USER_ID", "USER_ID_SEQ.NEXTVAL"); // 直接使用序列生成主键
        put("USER_NAME", "test_user");
    }})
    .build();
recordMapper.insert(record);

步骤2:修改XML映射文件

<insert id="insert" parameterType="de.gwvs.btix.database.model.GenericRecordMap">
    <!-- 插入后查询序列当前值,赋值给id属性 -->
    <selectKey keyProperty="id" resultType="long" order="AFTER">
        SELECT ${genericRecordMap.pk}_SEQ.CURRVAL FROM DUAL
    </selectKey>
    INSERT INTO ${genericRecordMap.tableName}
    <foreach open="(" close=")" collection="genericRecordMap.tableMap" index="col" separator=",">
        ${col}
    </foreach>
    VALUES
    <foreach open="(" close=")" collection="genericRecordMap.tableMap" item="val" separator=",">
        ${val}
    </foreach>
</insert>

说明:如果序列命名无固定规则,可在GenericRecordMap中新增seqName字段存储序列名,将<selectKey>中的SQL改为SELECT ${genericRecordMap.seqName}.CURRVAL FROM DUAL。

方案三:自定义MyBatis插件(进阶)

若必须使用useGeneratedKeys,可通过自定义插件拦截插入操作,动态设置keyColumn的值。

1. 编写插件类

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

import java.sql.Connection;
import java.util.Properties;

@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class DynamicKeyColumnPlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        
        // 仅处理目标insert方法
        if ("insert".equals(mappedStatement.getId()) && mappedStatement.getKeyProperties() != null) {
            Object parameterObject = metaObject.getValue("delegate.parameterHandler.parameterObject");
            if (parameterObject instanceof GenericRecordMap) {
                GenericRecordMap recordMap = (GenericRecordMap) parameterObject;
                // 动态设置keyColumn为传入的主键列名
                metaObject.setValue("delegate.mappedStatement.keyColumns", recordMap.getPk());
            }
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {}
}

2. 注册插件

在MyBatis配置文件中添加插件:

<plugins>
    <plugin interceptor="de.gwvs.btix.database.plugin.DynamicKeyColumnPlugin"/>
</plugins>

3. 修改insert标签

保留useGeneratedKeys配置,移除keyColumn:

<insert id="insert" parameterType="de.gwvs.btix.database.model.GenericRecordMap" 
        useGeneratedKeys="true" keyProperty="id">
    INSERT INTO ${genericRecordMap.tableName}
    <foreach open="(" close=")" collection="genericRecordMap.tableMap" index="col" separator=",">
        ${col}
    </foreach>
    VALUES
    <foreach open="(" close=")" collection="genericRecordMap.tableMap" item="val" separator=",">
        #{val}
    </foreach>
</insert>

内容的提问来源于stack exchange,提问作者GMarjano

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 16:22:52