在Mybatis中,执行insert操作时,如果我们希望返回数据库生成的自增主键值,那么就需要使用到KeyGenerator对象。

需要注意的是,KeyGenerator的作用,是返回数据库生成的自增主键值,而不是生成数据库的自增主键值。返回的主键值放到哪儿呢?放到parameter object的主键属性上。

下面看看其接口定义。

public interface KeyGenerator {void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
}

接口定义还是比较简单的,就是在insert前、insert后,策略处理主键值。

Jdbc3KeyGenerator:用于处理数据库支持自增主键的情况,如MySQL的auto_increment。

NoKeyGenerator:空实现,不需要处理主键。

SelectKeyGenerator:用于处理数据库不支持自增主键的情况,比如Oracle的sequence序列。

上面都比较泛泛而谈,我们来点实际的,看看它们都是如何工作的。

1. JDBC实现insert后,返回自增主键值的原理

Class.forName("com.mysql.jdbc.Driver");Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "root", "123");
conn.setAutoCommit(false);
PreparedStatement pstm = conn.prepareStatement("insert into students(name, email) values(?, ?)",
Statement.RETURN_GENERATED_KEYS);pstm.setString(1, "name1");
pstm.setString(2, "email1");
pstm.addBatch();
pstm.setString(1, "name2");
pstm.setString(2, "email2");
pstm.addBatch();
pstm.executeBatch();
// 返回自增主键值
ResultSet rs = pstm.getGeneratedKeys();
while (rs.next()) {Object value = rs.getObject(1);System.out.println(value);}
conn.commit();
rs.close();
pstm.close();
conn.close();output:
246
247

以上代码,仅作为演示使用。Mybatis是对JDBC的封装,其Jdbc3KeyGenerator类,就是使用上面的原理,来返回数据库生成的主键值的。

2. Jdbc3KeyGenerator源码解读

public class Jdbc3KeyGenerator implements KeyGenerator {@Overridepublic void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {// do nothing}@Overridepublic 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 {// 获得返回的主键值结果集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) {// 给参数object对象的属性赋主键值(批量插入,可能是多个)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 = getTypeHandlers(typeHandlerRegistry, metaParam, keyProperties, rsmd);}// 赋值populateKeys(rs, metaParam, keyProperties, typeHandlers);}}} catch (Exception e) {throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);} finally {if (rs != null) {try {rs.close();} catch (Exception e) {// ignore}}}}
private void populateKeys(ResultSet rs, MetaObject metaParam, String[] keyProperties, TypeHandler<?>[] typeHandlers) throws SQLException {// 主键字段,可能是多个(一般情况下,是一个)for (int i = 0; i < keyProperties.length; i++) {TypeHandler<?> th = typeHandlers[i];if (th != null) {Object value = th.getResult(rs, i + 1);// 反射赋值metaParam.setValue(keyProperties[i], value);}}}
//...

Mapper.Xml配置方式。

<insert id="insertStudents" useGeneratedKeys="true" keyProperty="studId" parameterType="Student">

3. NoKeyGenerator源码解读

完全是空实现,没啥可说的。

4. SelectKeyGenerator的原理

 <insert id="insertStudent" parameterType="Student" ><selectKey keyProperty="studId" resultType="int" order="BEFORE"> SELECT ELEARNING.STUD_ID_SEQ.NEXTVAL FROM DUAL </selectKey>INSERT INTOSTUDENTS(STUD_ID, NAME, EMAIL, DOB, PHONE)VALUES(#{studId}, #{name},#{email}, #{dob}, #{phone})</insert>

在执行insert之前,先发起一个sql查询,将返回的序列值赋值给Student的stuId属性,然后再执行insert操作,这样表中的stud_id字段就有值了。order="BEFORE"表示insert前执行,比如取sequence序列值;order="AFTER"表示insert之后执行,比如使用触发器给主键stud_id赋值。比较简单,我就不再贴源码了。

注意:由于selectKey本身返回单个序列主键值,也就无法支持批量insert操作并返回主键id列表了。如果要执行批量insert,请选择使用for循环执行多次插入操作。

5. KeyGenerator的创建过程

每一个MappedStatement,都有一个非空的KeyGenerator引用。

org.apache.ibatis.mapping.MappedStatement.Builder.Builder()构造方法赋初始值源码。

mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? new Jdbc3KeyGenerator() : new NoKeyGenerator();

org.apache.ibatis.builder.xml.XMLStatementBuilder.parseStatementNode()覆盖KeyGenerator初始值的源码。

String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
if (configuration.hasKeyGenerator(keyStatementId)) {// 表示存在selectKey获取主键值方式keyGenerator = configuration.getKeyGenerator(keyStatementId);} else {keyGenerator = context.getBooleanAttribute("useGeneratedKeys",configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))? new Jdbc3KeyGenerator() : new NoKeyGenerator();}

org.apache.ibatis.builder.xml.XMLStatementBuilder.parseSelectKeyNode()解析元素,构建SelectKeyGenerator的源码。

MappedStatement keyStatement = configuration.getMappedStatement(id, false);
configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));

因此,只有SelectKeyGenerator会保存至Configuration对象的Map<String, KeyGenerator> keyGenerators属性当中。元素,会被Mybatis解析为一个MappedStatement对象,并作为构造参数传递至SelectKeyGenerator内保存起来。

public class SelectKeyGenerator implements KeyGenerator {public static final String SELECT_KEY_SUFFIX = "!selectKey";private boolean executeBefore;private MappedStatement keyStatement;
//...

Map<String, KeyGenerator> keyGenerators的存储结构如下。

{insertStudent!selectKey=org.apache.ibatis.executor.keygen.SelectKeyGenerator@59d016c9,
com.mybatis3.mappers.StudentMapper.insertStudent!selectKey=org.apache.ibatis.executor.keygen.SelectKeyGenerator@59d016c9}

至此,每一个MappedStatement对象,都恰当的绑定了一个KeyGenerator对象,就可以开始工作了。

6. KeyGenerator的使用过程

keyGenerator.processBefore()方法调用时机。

org.apache.ibatis.executor.statement.BaseStatementHandler.BaseStatementHandler()构造方法源码。

if (boundSql == null) {// 调用keyGenerator.processBefore()方法generateKeys(parameterObject);boundSql = mappedStatement.getBoundSql(parameterObject);}
// ...protected void generateKeys(Object parameter) {KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();keyGenerator.processBefore(executor, mappedStatement, null, parameter);}

即,创建StatementHandler对象时,就会执行keyGenerator.processBefore()方法。keyGenerator.processAfter()方法,自然就是Statement执行后执行了。

org.apache.ibatis.executor.statement.SimpleStatementHandler.update(Statement)方法源码。其他的StatementHandler都是类似的。

 @Overridepublic int update(Statement statement) throws SQLException {String sql = boundSql.getSql();Object parameterObject = boundSql.getParameterObject();KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();int rows;if (keyGenerator instanceof Jdbc3KeyGenerator) {statement.execute(sql, Statement.RETURN_GENERATED_KEYS);rows = statement.getUpdateCount();keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);} else if (keyGenerator instanceof SelectKeyGenerator) {statement.execute(sql);rows = statement.getUpdateCount();keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);} else {statement.execute(sql);rows = statement.getUpdateCount();}return rows;}

7. 批量插入,返回主键id列表

for (Student student : students) {studentMapper.insertStudent(student);
}

对的,你没看错,就是像上面这样for循环逐一insert操作的,此时,如果你考虑性能的话,可以使用BatchExecutor来完成,当然了,其他的Executor也是可以的。

如果文章就像上面这样写,那么就完全失去了写文章的价值,上面的for循环,谁都懂这么操作可以实现,但是,很多人想要的并不是这个例子,而是另外一种批量插入操作,返回主键id列表。那么,看第8条。

8. Mybatis批量插入,返回主键id列表为null

<insert id="insertStudents" useGeneratedKeys="true" keyProperty="studId" parameterType="java.util.ArrayList">INSERT INTOSTUDENTS(STUD_ID, NAME, EMAIL, DOB, PHONE)VALUES<foreach collection="list" item="item" index="index" separator=","> (#{item.studId},#{item.name},#{item.email},#{item.dob}, #{item.phone}) </foreach>
</insert>

很多同学,包括开源中国社区,都遇到使用上面的批量insert操作,返回的主键id列表是null的问题,很多人得出结论:Mybatis不支持这种形式的批量插入并返回主键id列表。真是这样吗?

我必须明确的跟大家说,Mybatis是支持上述形式的批量插入,且可以正确返回主键id列表的。之所以返回null值,是Mybatis框架的一个bug,下一篇将具体讲述产生这个bug的原因,以及如何修复它。

通过源码分析Mybatis是如何返回数据库生成的自增主键值?相关推荐

  1. datatable如何生成级联数据_通过源码分析Mybatis是如何返回数据库生成的自增主键值?...

    在Mybatis中,执行insert操作时,如果我们希望返回数据库生成的自增主键值,那么就需要使用到KeyGenerator对象. 需要注意的是,KeyGenerator的作用,是返回数据库生成的自增 ...

  2. 通过源码分析MyBatis的缓存

    前方高能! 本文内容有点多,通过实际测试例子+源码分析的方式解剖MyBatis缓存的概念,对这方面有兴趣的小伙伴请继续看下去~ MyBatis缓存介绍 首先看一段wiki上关于MyBatis缓存的介绍 ...

  3. 通过源码分析Mybatis运行原理

    SqlSession类关系图 MapperFactoryBean 获取SqlSessionTemplate,SqlSessionTemplate的Configuration持有了mapper **** ...

  4. 通过源码分析Android 的消息处理机制

    2019独角兽企业重金招聘Python工程师标准>>> #通过源码分析Android 的消息处理机制 我们知道,Android应用是通过消息来驱动的,每一个进程被fork之后,都会在 ...

  5. MyBatis映射文件1(增删改、insert获取自增主键值)

    增删改 Mybatis为我们提供了<insert>.<update>.<delete>标签来对应增删改操作 在接口中写增删改的抽象方法 void addEmp(Em ...

  6. MyBatis 获取数据库中自增主键值

    一.在 SQL 映射文件的 select 标签中添加 useGeneratedKeys="true" 属性与 keyProperty=" " 属性,keyPro ...

  7. 通过源码分析各种Map(含LinkedHashMap、IdentityHashMap、ConcurrentHashMap)

    [干货预警,强烈建议关注收藏阅读] HashMap HashMap中的树存储 Hashtable(已过时,了解即可) TreeMap LinkedHashMap IdentityHashMap Con ...

  8. 源码分析 | Mybatis接口没有实现类为什么可以执行增删改查

    微信公众号:bugstack虫洞栈 | 案例源码:https://github.com/fuzhengwei/itstack-demo-code-mybatis 作为一款好用的ORM框架,一定是萝莉脸 ...

  9. Mybatis返回Mysql表的自增主键

    2019独角兽企业重金招聘Python工程师标准>>> <insert id="insertUplusDns" parameterType="co ...

最新文章

  1. LeetCode 860.柠檬水找零(C++)
  2. windows server服务器上部署java+tomcat网站域名配置
  3. Chrome DevTools 之 Network,网络加载分析利器
  4. go gorm 密码隐藏_掀开华为云的Go语言编程底座!有深度、有点难、需细品(上)...
  5. idea中使用osgi_OSGi中的权限
  6. css清除浮动的原理
  7. html免费天气预报代码,免费自我定制天气预报代码
  8. 极光实时监听怎么调用_源码分析 Sentinel 实时数据采集实现原理(图文并茂)
  9. Jmeter接口测试使用beanshell断言json返回
  10. vscode eslint 格式化完之后,一个标签多行,看的头疼
  11. java 核心API day05 File类
  12. QT中on_pushButton_clicked()用法
  13. LTE学习-OFDM
  14. 【电脑版微信文件存储在什么位置】
  15. 一起开心2020蓝桥寒假训练(二)7-6 彩虹瓶 (25分)用到栈,队列
  16. RabbitMQ报错(2)——Message:Already closed: The AMQP operation was interrupted: AMQP close-reason, initia
  17. LeetCode-求一个集合的子集
  18. 为什么不建议程序员做“外包”?
  19. VHDL三输入与门、四选一复用器
  20. 互联网日报 | iPhone 12系列手机正式发布;盒马入局火锅市场;东风汽车A股IPO申请获受理...

热门文章

  1. Glide核心设计一:皮皮虾,我们走
  2. Cassandra HBase和MongoDb性能比较
  3. spring事务配置
  4. 近期北京动点软件发现XXX公司盗用我公司WPF项目案例
  5. MATLAB生成正态样本以及正态矩阵、从文件读入矩阵
  6. S4:分布式流计算平台
  7. JBoss Portal CAS 的配置
  8. Mono 2.4 and MonoDevelop 2.0 have been released
  9. 最优控制理论 一、变分法和泛函极值问题
  10. AAC音频文件时长计算