一、自增主键的获取方式
自增主键是数据库常见的主键生成策略(如MySQL的AUTO_INCREMENT
、Oracle 的序列等)。MyBatis针对不同数据库的特性,提供了灵活的自增主键获取方案,核心分为两类:依赖数据库原生自增机制的 useGeneratedKeys
方式,以及通过显式SQL查询获取的 <selectKey>
方式。
1. MySQL 的两种获取方式
MySQL支持AUTO_INCREMENT
自增列,MyBatis 提供两种方式获取其生成的主键:
(1)useGeneratedKeys
方式(推荐)
这是最简单的方式,利用JDBC的 Statement.getGeneratedKeys()
接口直接获取数据库生成的主键,无需手动编写查询逻辑。
配置示例(XML 映射)
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
函数获取刚生成的主键:
配置示例:
xml
<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>
标签先查询序列值,再将其作为主键插入。
配置示例:
xml
<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
是自增主键处理的核心接口,定义了主键生成的两个关键时机(插入前 / 后):
java
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
,告知数据库返回自增主键:
java
// 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()
获取主键,并通过反射回写到实体类:
java
@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
中执行:
java
// 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
显式查询主键,灵活兼容。 。