mybatis获取insert操作自增主键值原理
大家好,我是joker,希望你快乐。
上一篇mybatis insert操作获取自增主键中介绍了如何获取主键值,接下来这篇我们将通过跟踪源码的方式进一步探究mybatis是如何获取到主键的。
其实上一篇中,通过官方文档我们可以看出mybatis还是通过 JDBC 的 getGeneratedKeys 方法获取由数据库内部生成的主键。
-
useGeneratedKeys
(仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。
-
keyProperty
(仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。
为了对这个过程有一个更加清晰的认识,下面我们开始跟踪主要代码并做简单说明。
- 跟踪了一路代理,模板,执行器中的方法后,跟踪到了PreparedStatementHandler.java中的update方法。具体代码如下:
@Override public int update(Statement statement) throws SQLException { 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; }
- 可以看到getKeyGenerator()获取主键生成器,进一步跟踪,会进入到Jdbc3KeyGenerator.java的processBatch方法。
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); } }
可以看到mybatis获取数据库生成的id方法还是通过jdbc的驱动的实现类进行获取的。
- 我使用的是mysql,最终会进入到mysql的jdbc驱动中StatementImpl.java实现类。如果使用其他数据也会进入对应jdbc驱动的StatementImpl.java实现类,获取自增主键需要数据库及jdbc驱动支持。
protected ResultSetInternalMethods getGeneratedKeysInternal(long numKeys) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { String encoding = this.session.getServerSession().getCharsetSettings().getMetadataEncoding(); int collationIndex = this.session.getServerSession().getCharsetSettings().getMetadataCollationIndex(); Field[] fields = new Field[1]; fields[0] = new Field("", "GENERATED_KEY", collationIndex, encoding, MysqlType.BIGINT_UNSIGNED, 20); ArrayList<Row> rowSet = new ArrayList<>(); long beginAt = getLastInsertID(); if (this.results != null) { String serverInfo = this.results.getServerInfo(); // // Only parse server info messages for 'REPLACE' queries // if ((numKeys > 0) && (this.results.getFirstCharOfQuery() == 'R') && (serverInfo != null) && (serverInfo.length() > 0)) { numKeys = getRecordCountFromInfo(serverInfo); } if ((beginAt != 0 /* BIGINT UNSIGNED can wrap the protocol representation */) && (numKeys > 0)) { for (int i = 0; i < numKeys; i++) { byte[][] row = new byte[1][]; if (beginAt > 0) { row[0] = StringUtils.getBytes(Long.toString(beginAt)); } else { byte[] asBytes = new byte[8]; asBytes[7] = (byte) (beginAt & 0xff); asBytes[6] = (byte) (beginAt >>> 8); asBytes[5] = (byte) (beginAt >>> 16); asBytes[4] = (byte) (beginAt >>> 24); asBytes[3] = (byte) (beginAt >>> 32); asBytes[2] = (byte) (beginAt >>> 40); asBytes[1] = (byte) (beginAt >>> 48); asBytes[0] = (byte) (beginAt >>> 56); BigInteger val = new BigInteger(1, asBytes); row[0] = val.toString().getBytes(); } rowSet.add(new ByteArrayRow(row, getExceptionInterceptor())); beginAt += this.connection.getAutoIncrementIncrement(); } } } ResultSetImpl gkRs = this.resultSetFactory.createFromResultsetRows(ResultSet.CONCUR_READ_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE, new ResultsetRowsStatic(rowSet, new DefaultColumnDefinition(fields))); return gkRs; } }
可以看到getLastInsertID()方法获取了最后一次插入的id,这个方法只是读取了最后的自增主键,通过注释可以看出select LAST_INSERT_ID()使用这种方式有并发问题,那具体是如何为这个lastInsertId赋值的我们接着说。
/** * getLastInsertID returns the value of the auto_incremented key after an * executeQuery() or excute() call. * * <p> * This gets around the un-threadsafe behavior of "select LAST_INSERT_ID()" which is tied to the Connection that created this Statement, and therefore could * have had many INSERTS performed before one gets a chance to call "select LAST_INSERT_ID()". * </p> * * @return the last update ID. */ public long getLastInsertID() { synchronized (checkClosed().getConnectionMutex()) { return this.lastInsertId; } }
至此,我们验证了MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键。
在JDBC中是通过mysql协议进行通讯获取的,在这里做一个简单说明,有兴趣可以去看代码。
来源:http://www.cnblogs.com/Crazy_Joker
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步