这篇文章研究下 Mybatis 配置主键回显相关功能。
本篇文章将以以下几个问题切入:
Connection conn = DriverManager.getConnection(url, "root", "123456");
String[] columnNames = {"ids", "name"};
PreparedStatement stmt = conn.prepareStatement(sql, columnNames);
stmt.setString(1, "test jdbc3 ");
stmt.executeUpdate();
ResultSet rs = stmt.getGeneratedKeys();
int id = 0;
if (rs.next()) {
id = rs.getInt(1);
System.out.println("----------" + id);
}
即当使用 prepareStatement 或者 Statement时候,可以通过getGeneratedKeys
获取 当条插入语句的自增而成的主键。
当然还有其他几种方式,原理是 数据库端返回一个LAST_INSERT_ID
,这个跟auto_increment_id
强相关。
其他几种方式可以参考oracle文档:https://docs.oracle.com/cd/E17952_01/connector-j-en/connector-j-usagenotes-last-insert-id.html
Mybtis 对于数据库主键回显,主要分为两种,一种是数据库支持自增主键字段,另一种是数据库不支持方式即手动指定方式。
Mysql,postgresql
使用 Jdbc3KeyGenerator
进行数据库回显:useGeneratedKeys="true" keyColumn="id" keyProperty="id"
,即当插入或者批量插入后,成功后即可返回配置id:
insert into department (name) values (#{department.name})
oracle,DB2
。则使用 selectKey
先查出最大id,而后进行插入操作:
SELECT if(max(id) is null,1,max(id)+10) as newId FROM department
insert into department (id, name) values (#{id}, #{department.name})
下面以 上面三个用法为基础,分析Mybatis 主键自增回显原理。
当 Mybatis 解析 xml节点是,读到 insert
有配置时,会判断是否 有配置 useGeneratedKeys
,如果有则会使用 Jdbc3KeyGenerator
作为sql回显,否则会以 NoKeyGenerator
作为主键回显。
当执行插入数据操作时,执行以下逻辑:
MapperMethod
的 execute
方法,判断执行类型INSERT
时,则会 进入 DefaultSqlSession
的 insert
方法,随后进入 其 update
方法。Executor
的 update
方法,配置完 ErrorContext
后,进入 doUpdate
方法: public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
clearLocalCache();
return doUpdate(ms, parameter);
}
Mybatis
对 KeyGenerator
使用是在 PreparedStatementHandler
的 update
中: @Override
public int update(Statement statement) throws SQLException {
// 获取 preparedStatement
PreparedStatement ps = (PreparedStatement) statement;
// 执行 语句
ps.execute();
// 获取影响行
int rows = ps.getUpdateCount();
// 获取参数
Object parameterObject = boundSql.getParameterObject();
// 获取 KeyGenerator,自增则通过 jdbc获取,否则就通过selectKey 查询获取
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
// 回填 配置星到 参数中
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
Mybatis 对于 KeyGenerator
主要集中在上面的update
代码中,下面看看 三种 KeyGenerator
含义:
KeyGenerator
接口 中 只有 两个方法:
void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
processBefore
:主要用于 插入前操作,即获取和设置主键(不支持主键自增)
processAfter
:用于插入后对主键进行回显到参数中
KeyGenerator
的空实现,主要是不配置 generatorKey
,所以 processAfter
和 processBefore
事实上不需要进行任何操作processBefore
是空实现,只实现了 processAfter
: @Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
processBatch(ms, stmt, parameter);
}
public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
final String[] keyProperties = ms.getKeyProperties();
if (keyProperties == null || keyProperties.length == 0) {
return;
}
try (ResultSet rs = stmt.getGeneratedKeys()) {
// 获取自增主键
final ResultSetMetaData rsmd = rs.getMetaData();
final Configuration configuration = ms.getConfiguration();
if (rsmd.getColumnCount() < keyProperties.length) {
// Error?
} else {
// 设值
assignKeys(configuration, rs, rsmd, keyProperties, parameter);
}
} catch (Exception e) {
throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
}
}
上面代码主要就是获取自增主键并且设值:
private void assignKeys(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, String[] keyProperties,
Object parameter) throws SQLException {
if (parameter instanceof ParamMap || parameter instanceof StrictMap) {
// 多参数或者单参数使用 @Param Multi-param or single param with @Param
assignKeysToParamMap(configuration, rs, rsmd, keyProperties, (Map) parameter);
} else if (parameter instanceof ArrayList && !((ArrayList) parameter).isEmpty()
&& ((ArrayList) parameter).get(0) instanceof ParamMap) {
// 批量插入 操作 Multi-param or single param with @Param in batch operation
assignKeysToParamMapList(configuration, rs, rsmd, keyProperties, ((ArrayList>) parameter));
} else {
// 没有 用 @Param注解的单参数 Single param without @Param
assignKeysToParam(configuration, rs, rsmd, keyProperties, parameter);
}
}
从上面看到,Mybatis
通过判断三种情况对 数据进行回填,从而使用不同回填,具体不在分析。
Jdbc mysql 连接驱动 中 getGeneratedKeys
获取有以下几个分析点:
LAST_INSERT_ID
返回。insert on duplicate key update
不能回填所有id? 因为insert on duplicate key update
这种 方式 有多种情况,分别会返回0(存在但数值一致),1(插入),2(更新), 不是固定的1,所以不能通过遍历设值。具体可以看看这篇文章,关于 Jdbc Mysql 驱动的 getGeneratedKeys
: 深入分析Mybatis 使用useGeneratedKeys获取自增主键
Mybatis
提供一种 SelectKey
方式,即先查出,再将id填充进参数进行插入操作: @Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
if (executeBefore) {
// 执行前
processGeneratedKeys(executor, ms, parameter);
}
}
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
if (!executeBefore) {
// 执行后
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);
if (keyProperties != null) {
// Do not close keyExecutor.
// The transaction will be closed by parent executor.
// 获取一个Executor
Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
List
对于 SelectKeyGenerator
方式进行插入,则需要在 配置 SelectKey
时候指定 order
, 如果没有指定,则默认为 BEFORE
:
order="BEFORE"
, BEFORE
则为在 插入前设值,即 上述代码中 executeBefore
为 true
。
processGeneratedKeys
方法主要意思为 获取一个 Executor
,而后对 当前 MappedStatement
进行查询操作,最终返回到 List
的value中,并设值到 parameter
中。
以上,就是 Mybatis
中 KeyGenerator
主键回显原理。
觉得博主写的有用,不妨关注博主公众号: 六点A君。
哈哈哈,一起研究Mybatis: