Mybatis获取自增主键的两种实现原理小结

 更新时间:2025年08月29日 10:39:38   作者:灵魂猎手  
MyBatis通过useGeneratedKeys和两种方式适配不同数据库的自增主键生成,利用JDBC接口和KeyGenerator实现高效获取与回写,下面就来介绍一下

一、自增主键的获取方式

自增主键是数据库常见的主键生成策略(如MySQL的AUTO_INCREMENT、Oracle 的序列等)。MyBatis针对不同数据库的特性,提供了灵活的自增主键获取方案,核心分为两类:依赖数据库原生自增机制的 useGeneratedKeys方式,以及通过显式SQL查询获取的 <selectKey> 方式。

1. MySQL 的两种获取方式

MySQL支持AUTO_INCREMENT自增列,MyBatis 提供两种方式获取其生成的主键:

(1)useGeneratedKeys方式(推荐)

这是最简单的方式,利用JDBC的 Statement.getGeneratedKeys() 接口直接获取数据库生成的主键,无需手动编写查询逻辑。

配置示例(XML 映射)

<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
  INSERT INTO user (username, age) VALUES (#{username}, #{age})
</insert>
  • useGeneratedKeys="true":开启自增主键获取功能。
  • keyProperty="id":指定 Java 实体类中接收主键的属性(如 User 类的 id 字段)。

(2)<selectKey>方式

通过显式执行查询SQL来获取主键,MySql也支持插入后通过LAST_INSERT_ID函数获取刚生成的主键:

配置示例

<insert id="insertUser">
  <!-- order="AFTER" 表示插入后执行查询 -->
  <selectKey keyProperty="id" resultType="java.lang.Long" order="AFTER">
    SELECT LAST_INSERT_ID AS id -- MySQL 专用函数,返回当前会话最后生成的自增主键
  </selectKey>
  INSERT INTO user (username, age) VALUES (#{username}, #{age})
</insert>
  • order="AFTER":由于 MySQL 自增主键在插入后生成,因此需设置为 AFTER
  • SELECT LAST_INSERT_ID:MySQL 提供的函数,用于获取当前会话中最近一次插入生成的自增主键。

PS:LAST_INSERT_ID是个函数,后面要有()的,但是似乎触发了掘金的什么feature,加上括号后会被转成0

2. Oracle 的获取方式

Oracle 没有内置的自增列机制,通常通过序列(Sequence) 生成主键。MyBatis 需通过 <selectKey>标签先查询序列值,再将其作为主键插入。

配置示例

    <insert id="insertUser">
      <!-- order="BEFORE" 表示插入前先查询序列 -->
      <selectKey keyProperty="id" resultType="java.lang.Long" order="BEFORE">
        SELECT USER_SEQ.NEXTVAL FROM DUAL -- 查询序列的下一个值
      </selectKey>
      INSERT INTO user (id, username, age) VALUES (#{id}, #{username}, #{age})
    </insert>
  • order="BEFORE":由于 Oracle 需先获取序列值作为主键,再执行插入,因此需设置为 BEFORE
  • USER_SEQ.NEXTVAL:Oracle 序列的下一个值,作为主键传入 INSERT 语句。

二、源码解析:自增主键的实现原理

MyBatis自增主键的核心逻辑由 KeyGenerator 接口及其实现类完成,结合MappedStatementStatementHandler等组件,实现主键的生成、获取与回写。

1. 核心接口:KeyGenerator

KeyGenerator 是自增主键处理的核心接口,定义了主键生成的两个关键时机(插入前 / 后):

public interface KeyGenerator {
  // 插入操作执行前调用(如 Oracle 序列查询)
  void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
  
  // 插入操作执行后调用(如 MySQL 自增主键获取)
  void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
}

MyBatis 提供两个核心实现类:Jdbc3KeyGenerator(处理 useGeneratedKeys 方式)和 SelectKeyGenerator(处理 <selectKey> 方式)。

2.useGeneratedKeys方式的实现(Jdbc3KeyGenerator)

当解析 Mapper 配置时,useGeneratedKeys 等属性会被封装到 MappedStatement 中,并初始化 Jdbc3KeyGenerator

(1)创建PreparedStatement时设置主键返回标志

执行插入时,PreparedStatementHandler 会根据 MappedStatement 的配置,创建带有 RETURN_GENERATED_KEYS 标志的 PreparedStatement,告知数据库返回自增主键:

// PreparedStatementHandler.java
@Override
protected Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
  PreparedStatement ps;
  String sql = boundSql.getSql();
  
  // 若使用 Jdbc3KeyGenerator,设置 RETURN_GENERATED_KEYS 标志
  if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
    ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
  } else {
    ps = connection.prepareStatement(sql);
  }
  // 设置超时时间等
  return ps;
}

(2)插入后获取并回写主键(processAfter方法)

插入执行后,Jdbc3KeyGenerator 的 processAfter 方法被调用,通过 Statement.getGeneratedKeys() 获取主键,并通过反射回写到实体类:

@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
  processBatch(ms, stmt, getParameters(parameter));
}

public void processBatch(MappedStatement ms, Statement stmt, Collection<Object> parameters) {
  ResultSet rs = null;
  try {
    //核心:使用getGeneratedKeys方法获取主键
    rs = stmt.getGeneratedKeys();
    final Configuration configuration = ms.getConfiguration();
    final TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    //获取参数对象主键属性
    final String[] keyProperties = ms.getKeyProperties();
    final ResultSetMetaData rsmd = rs.getMetaData();
    TypeHandler<?>[] typeHandlers = null;
    if (keyProperties != null && rsmd.getColumnCount() >= keyProperties.length) {
      for (Object parameter : parameters) {
        // there should be one row for each statement (also one for each parameter)
        if (!rs.next()) {
          break;
        }
        final MetaObject metaParam = configuration.newMetaObject(parameter);
        if (typeHandlers == null) {
          //获取主键对应的typeHandlers
          typeHandlers = getTypeHandlers(typeHandlerRegistry, metaParam, keyProperties, rsmd);
        }
        //反射设置到参数中
        populateKeys(rs, metaParam, keyProperties, typeHandlers);
      }
    }
  }
  //省略一些异常处理的代码和关闭ResultSet的代码
}

3.<selectKey>方式的实现(SelectKeyGenerator)

SelectKeyGenerator实现了<selectKey>,需要根据order属性,判断该在processBefore还是processAfter 中执行:

// SelectKeyGenerator.java
@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
  if (executeBefore) { // order="BEFORE" 时执行
    processGeneratedKeys(executor, ms, parameter);
  }
}

@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
  if (!executeBefore) { // order="AFTER" 时执行
    processGeneratedKeys(executor, ms, parameter);
  }
}

private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {
    try {
        if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) {
            String[] keyProperties = keyStatement.getKeyProperties();
            final Configuration configuration = ms.getConfiguration();
            final MetaObject metaParam = configuration.newMetaObject(parameter);
            // 创建执行器,执行主键查询操作
            Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
            // 执行查询主键的操作
            List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
            if (values.size() == 0) {
                throw new ExecutorException("SelectKey returned no data.");
            } else if (values.size() > 1) {
                throw new ExecutorException("SelectKey returned more than one value.");
            } else {
                // 创建 MetaObject 对象,访问查询主键的结果
                MetaObject metaResult = configuration.newMetaObject(values.get(0));
                // 单个主键
                if (keyProperties.length == 1) {
                    // 设置属性到 metaParam 中,相当于设置到 parameter 中
                    if (metaResult.hasGetter(keyProperties[0])) {
                        setValue(metaParam, keyProperties[0], metaResult.getValue(keyProperties[0]));
                    } else {
                        setValue(metaParam, keyProperties[0], values.get(0));
                    }
                // 多个主键
                } else {
                    // 遍历,进行赋值
                    handleMultipleProperties(keyProperties, metaParam, metaResult);
                }
            }
        }
    }
    //省略一些异常处理
}

4. 调用链路总结

自增主键的处理贯穿 MyBatis 插入操作的全流程,核心链路如下:

  1. 用户调用:sqlSession.insert("insertUser", user)
  2. 进入执行器:Executor.update(ms, parameter)(insert 本质是 update 操作)
  3. 创建 StatementHandler:PreparedStatementHandler 根据 MappedStatement 的 keyGenerator 类型,决定是否设置 RETURN_GENERATED_KEYS 标志。
  4. 执行插入:Statement.execute() 执行 INSERT 语句。
  5. 主键处理:
    • 若为 Jdbc3KeyGenerator:调用 processAfter,通过 stmt.getGeneratedKeys() 获取主键并回写。
    • 若为 SelectKeyGenerator:根据 order 调用 processBefore 或 processAfter,执行 <selectKey> 中的 SQL 获取主键并回写。

三、总结

MyBatis 自增主键的获取本质是适配数据库特性 + 封装 JDBC 接口:

  • 对于支持 getGeneratedKeys() 的数据库(如 MySQL),优先使用 useGeneratedKeys 方式,通过 Jdbc3KeyGenerator 直接获取主键,简洁高效。
  • 对于依赖序列的数据库(如Oracle),使用 <selectKey> 方式,通过 SelectKeyGenerator 显式查询主键,灵活兼容。 。

到此这篇关于Mybatis获取自增主键的实现原理小结的文章就介绍到这了,更多相关Mybatis获取自增主键内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

相关文章

  • 解决@MapperScan和@Mapper共存之坑XxxMapper that could not be found.

    解决@MapperScan和@Mapper共存之坑XxxMapper that could not be fo

    这篇文章主要介绍了解决@MapperScan和@Mapper共存之坑XxxMapper that could not be found问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-06-06
  • Java实现的计时器【秒表】功能示例

    Java实现的计时器【秒表】功能示例

    这篇文章主要介绍了Java实现的计时器【秒表】功能,结合实例形式分析了Java结合JFrame框架的计时器功能相关操作技巧,需要的朋友可以参考下
    2019-02-02
  • JPA如何使用findBy方法自定义查询

    JPA如何使用findBy方法自定义查询

    这篇文章主要介绍了JPA如何使用findBy方法自定义查询,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • javaWeb连接数据库实现简单登陆注册功能的全过程

    javaWeb连接数据库实现简单登陆注册功能的全过程

    初学javaWeb,老师留下一小作业,用JAVA实现与服务器端交互,实现登录和注册功能,下面这篇文章主要给大家介绍了关于javaWeb连接数据库实现简单登陆注册功能的相关资料,需要的朋友可以参考下
    2022-06-06
  • 掌握Java拼音转换:pinyin4j库使用方法及应用价值

    掌握Java拼音转换:pinyin4j库使用方法及应用价值

    pinyin4j是一个开源的Java库,用于将汉字转换为拼音,它支持将中文字符转换为标准的全拼形式,并能够处理多音字和声调,本文详细解析pinyin4j库的核心特性、使用方法及其应用价值,感兴趣的朋友一起看看吧
    2025-08-08
  • MyBatis Oracle 自增序列的实现方法

    MyBatis Oracle 自增序列的实现方法

    这篇文章给大家分享MyBatis Oracle 自增序列的实现方法及mybatis配置oracle的主键自增长的方法,非常不错具有一定的参考借鉴价值,感兴趣的朋友一起看看吧
    2016-11-11
  • Go Java算法之简化路径实例详解

    Go Java算法之简化路径实例详解

    这篇文章主要为大家介绍了Go Java算法之简化路径实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • 安全漏洞修复导致SpringBoot2.7与Springfox不兼容的伪命题解决

    安全漏洞修复导致SpringBoot2.7与Springfox不兼容的伪命题解决

    项目基于Spring Boot 2.5.2使用Springfox Swagger2生成API文档,因安全漏洞需升级至2.7.8,导致兼容问题,下面就来介绍一下问题解决,感兴趣的可以了解一下
    2025-06-06
  • Java实现系统限流的示例代码

    Java实现系统限流的示例代码

    限流是保障系统高可用的方式之一,也是大厂高频面试题,它在微服务系统中,缓存、限流、熔断是保证系统高可用的三板斧,所以本文我们就来聊聊如何实现系统限流吧
    2023-09-09
  • Mybatis新增数据并返回主键id的两种方法实现

    Mybatis新增数据并返回主键id的两种方法实现

    本文主要介绍了Mybatis新增数据并返回主键id的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-02-02

最新评论