主键策略
MybatisReuseExecutor->doUpdate():
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); // --1
Statement stmt = prepareStatement(handler, ms.getStatementLog(), false);
return stmt == null ? 0 : handler.update(stmt); // --2
Configuration->newStatementHandler(): // --1
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
RoutingStatementHandler->RoutingStatementHandler():
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
PreparedStatementHandler->PreparedStatementHandler():
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
BaseStatementHandler->BaseStatementHandler():
if (boundSql == null) {
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
Configuration->newParameterHandler():
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
MybatisXMLLanguageDriver->createParameterHandler():
return new MybatisDefaultParameterHandler(mappedStatement, parameterObject, boundSql);
MybatisDefaultParameterHandler->MybatisDefaultParameterHandler():
super(mappedStatement, processBatch(mappedStatement, parameterObject), boundSql);
MybatisDefaultParameterHandler->processBatch():
return populateKeys(metaObjectHandler, tableInfo, ms, parameterObject, isInsert);
// 根据反射信息查看实体类的主键字段与主键填充策略,当type设定为填充的字段时,会填充
// 源码注释已经很详细了
MybatisDefaultParameterHandler->populateKeys():
自增主键映射返回
RoutingStatementHandler->update():
return delegate.update(statement);
PreparedStatementHandler->update():
// ps内部封装了PreparedStatement,最终执行的是ps的execute()
// 插入成功时会返回并赋值ps的stmt的statment的delegate的results的updateId和updateCount,会把updateId赋给ps的stmt的statment的delegate的lastInsertId
ps.execute();
// 返回更新条数
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
// 获取主键类型,type=AUTO的时候返回的是Jdbc3KeyGenerator类
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
// Jdbc3KeyGenerator会对实体类主键作一些操作
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
Jdbc3KeyGenerator->processAfter():
processBatch(ms, stmt, parameter);
Jdbc3KeyGenerator->processBatch():
// 主键名称
final String[] keyProperties = ms.getKeyProperties();
// 底层调用ps.getGeneratedKeys(),新建一个ResultSetImpl对象,伪造返回字段名为GENERATED_KEY,值为lastInsertId
try (ResultSet rs = stmt.getGeneratedKeys()) {
......
// 主键赋值
assignKeys(configuration, rs, rsmd, keyProperties, parameter);
}
主键赋值操作最终进入下面这个方法
Jdbc3KeyGenerator->KeyAssigner->assign():
// 最终调用的是rs.getInt(1),获得了返回的主键id
Object value = typeHandler.getResult(rs, columnPosition);
// 调用method.invoke(target, args)给对象赋值,propertyName是主键字段名,这里就相当于tree.setId(value)
// 这样,自动填充主键就算是完成了
metaParam.setValue(propertyName, value);
FAQ:
- 当type为AUTO的时候,为什么即使设置了主键值,插入时仍然不会插入主键id?
save方法属于动态sql源,当type为AUTO的时候,SqlNode不会有关于id的部分
TableInfo->getKeyInsertSqlColumn():
if (StringUtils.isNotEmpty(keyColumn)) {
if (idType == IdType.AUTO) {
return EMPTY;
}
return keyColumn + COMMA + (newLine ? NEWLINE : EMPTY);
}
return EMPTY;