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
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。