执行insert源码分析
示例代码
mapper xml配置:
<insert id="insertBlog" parameterType="org.net5ijy.mybatis.test.entity.Blog">
<selectKey resultType="java.lang.Integer" keyProperty="id" order="AFTER" >
SELECT LAST_INSERT_ID()
</selectKey>
insert into blog
(title, content, create_time, update_time)
values
(#{title}, #{content}, now(), now())
</insert>
API示例代码:
Blog blog = new Blog();
blog.setTitle("spring学习");
blog.setContent("spring深入 - 源码分析");
int rows = session.insert("org.net5ijy.mybatis.test.BlogMapper.insertBlog", blog);
// 由于使用sessionFactory.openSession()方法默认非自动提交,所以这里手动提交事务
session.commit();
// 1
System.out.println(rows);
// 由于没有配置selectKey所以是null
System.out.println(blog.getId());
session.insert方法
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
// 数据库更新操作
// wrapCollection(parameter)在batch insert时就有用了
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
executor.update方法
此处的executor还是CachingExecutor类的实例。
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
// BaseExecutor.update
public int update(MappedStatement ms, Object parameter) throws SQLException {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 清理一级缓存
clearLocalCache();
return doUpdate(ms, parameter);
}
// SimpleExecutor.doUpdate
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 这里创建Statement的代码与query时基本一致,不做展开分析
StatementHandler handler =
configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
// 数据库更新操作
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
StatementHandler.update方法
这里底层使用的是PreparedStatementHandler的update方法:
public int update(Statement statement) throws SQLException {
// 前三行都是JDBC的操作
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
这里的代码还是比较简单的,执行JDBC更新,获取被影响的行数,后面的KeyGenerator代码是为了给插入数据的主键列赋值。
selectKey和KeyGenerator
selectKey标签
我们的示例SQL中有一段配置:
<selectKey resultType="java.lang.Integer" keyProperty="id" order="AFTER" >
SELECT LAST_INSERT_ID()
</selectKey>
这个配置用于反查插入数据的主键值,并给参数的主键属性赋值。标签内部的语句在不同的数据库下不同。
selectKey的dtd约束如下:
<!ELEMENT selectKey (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST selectKey
resultType CDATA #IMPLIED
statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
keyProperty CDATA #IMPLIED
keyColumn CDATA #IMPLIED
order (BEFORE|AFTER) #IMPLIED
databaseId CDATA #IMPLIED
>
他支持的子标签与一般的select标签一样,重要属性:
- resultType - 返回值类型
- keyProperty - 参数中的主键属性名
- keyColumn - 主键列名
在mybatis中,会使用与普通查询一样的方式存储selectKey查询。
selectKey的解析
在解析statement的过程中,有一段逻辑是selectKey的解析,在XMLStatementBuilder类的parseStatementNode()方法中,这段代码在初始化阶段做过分析,但是解析selectKey的部分未做展开,这里回顾补充一下:
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType =
SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
// 是否为查询语句
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser =
new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// 解析parameterType
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
// 解析<selectKey ...>节点
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
// 创建KeyGenerator
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
// 解析useGeneratedKeys属性
keyGenerator = context.getBooleanAttribute(
"useGeneratedKeys",
configuration.isUseGeneratedKeys() &&
SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// 这个非常重要:将xml中的sql转成SqlSource对象,以便后续的进一步解析、
// 创建preparedstatement及参数赋值
SqlSource sqlSource =
langDriver.createSqlSource(configuration, context, parameterTypeClass);
// 这个基本就是默认的PREPARED了
StatementType statementType =
StatementType.valueOf(
context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
// 解析parameterMap
String parameterMap = context.getStringAttribute("parameterMap");
// 解析resultType
// 此处有个别名的问题,所以使用的是resolveClass(resultType)方法
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
// 解析resultMap
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
// key对应的属性名
String keyProperty = context.getStringAttribute("keyProperty");
// key对应的列名
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
// 创建MappedStatement
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
其中:
// Parse selectKey after includes and remove them.
// 解析<selectKey ...>节点
// 最终会和普通的statement一样,也创建MappedStatement并保存到Configuration中,不做展开分析
processSelectKeyNodes(id, parameterTypeClass, langDriver);
就是用于解析selectKey、并创建SelectKeyGenerator的部分代码。
KeyGenerator的实现
KeyGenerator
|-- NoKeyGenerator
|-- Jdbc3KeyGenerator
|-- SelectKeyGenerator
SelectKeyGenerator类
使用查询获取key的KeyGenerator实现。大概流程如下:
- 创建Executor
- 执行selectKey查询
- 为参数的主键属性赋值
代码有点多,就不记录了。
selectKey标签的替代方式
<insert id="insertBlog"
parameterType="org.net5ijy.mybatis.test.entity.Blog"
useGeneratedKeys="true"
keyProperty="id">
insert into blog
(title, content, create_time, update_time)
values
(#{title}, #{content}, now(), now())
</insert>
相关属性:
- useGeneratedKeys - (仅适用于 insert 和 update)MyBatis使用JDBC的getGeneratedKeys方法来取出由数据库内部生成的主键(比如MySQL和SQL Server这样的关系型数据库管理系统的自动递增字段),默认值false
- keyProperty - 参数中的主键属性名
这里使用的是Jdbc3KeyGenerator实现类。
他的process流程大致如下:
- 使用stmt.getGeneratedKeys()获取到generatedKeys
- 给参数的主键属性赋值
不做展开分析了。
KeyGenerator的创建
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
// 创建KeyGenerator
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
// 这里可以获取到keyGenerator就直接使用,因为上面做了selectKey解析,所以这里执行第一个分支
if (configuration.hasKeyGenerator(keyStatementId)) {
// 从Configuration获取,这个在之前解析selectKey的时候就创建了
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
// 解析useGeneratedKeys属性
keyGenerator = context.getBooleanAttribute(
"useGeneratedKeys",
configuration.isUseGeneratedKeys() &&
SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
就是用于获取/创建KeyGenerator的部分代码。
回到StatementHandler.update方法
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
现在这几行代码的作用就很清晰了。
结束语
至此,mybatis最核心的源码就分析完毕了。
后续会有mapper接口模式(含注解方式)、springboot集成、mybatis-plus核心原理,以及核心的一级缓存、二级缓存、延迟加载、嵌套结果、嵌套查询、多结果集等高级功能的深入源码分析。