Mybatis获取自增主键的两种实现原理小结
作者:灵魂猎手
一、自增主键的获取方式
自增主键是数据库常见的主键生成策略(如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
接口及其实现类完成,结合MappedStatement
、StatementHandler
等组件,实现主键的生成、获取与回写。
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 插入操作的全流程,核心链路如下:
- 用户调用:sqlSession.insert("insertUser", user)
- 进入执行器:Executor.update(ms, parameter)(insert 本质是 update 操作)
- 创建 StatementHandler:PreparedStatementHandler 根据 MappedStatement 的 keyGenerator 类型,决定是否设置 RETURN_GENERATED_KEYS 标志。
- 执行插入:Statement.execute() 执行 INSERT 语句。
- 主键处理:
- 若为 Jdbc3KeyGenerator:调用 processAfter,通过 stmt.getGeneratedKeys() 获取主键并回写。
- 若为 SelectKeyGenerator:根据 order 调用 processBefore 或 processAfter,执行 <selectKey> 中的 SQL 获取主键并回写。
三、总结
MyBatis 自增主键的获取本质是适配数据库特性 + 封装 JDBC 接口:
- 对于支持 getGeneratedKeys() 的数据库(如 MySQL),优先使用 useGeneratedKeys 方式,通过 Jdbc3KeyGenerator 直接获取主键,简洁高效。
- 对于依赖序列的数据库(如Oracle),使用 <selectKey> 方式,通过 SelectKeyGenerator 显式查询主键,灵活兼容。 。
到此这篇关于Mybatis获取自增主键的实现原理小结的文章就介绍到这了,更多相关Mybatis获取自增主键内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!