Java操作数据库需要经过3个大步骤:
- 获取数据库连接
- 执行SQL语句
- 关闭数据库连接
Mybatis将这几个步骤进行了封装,将获取数据库连接的给工作交给了SqlSessionFactory,将执行SQL的工作交给了SqlSession。
1 获取SqlSession
在程序启动时,会根据配置创建SqlSessionFactory:
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
SqlSessionFactory是一个工厂,在整个程序中应该是单例的存在。它会保存所有配置信息,需要操作数据库时,通过调用SqlSessionFactory#openSession()方法,可以根据配置信息生成新的SqlSession。
SqlSessionFactory#openSessionFromDataSource()展示了创建SqlSession的流程,本质上只是创建了Executor对象,并对其进行了封装:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
需要注意的是,Configuration#newExecutor()方法会对执行器进行缓存代理和插件代理。默认情况下,生成从外到内Interceptor -> CachingExecutor -> SimpleExecutor的层级结构:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
2 执行SQL语句
Mybatis提供了两种执行SQL的方式:
- 根据statementId执行
- 使用映射接口代理对象执行
2.1 根据statementId执行
SqlSession提供了selectXxx/insert/update/delete等方法操作数据库。
默认情况下,DefaultSqlSession会将selectXxx(除了selectCursor)方法统一交给selectList
方法处理,将insert/update/delete方法统一交给update
方法处理。它们的处理方式相同:
- 从Configuration中获取对应的MappedStatement
- 使用Executor执行SQL
DefaultSqlSession#selectList:
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
DefaultSqlSession#update:
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
2.2.1 Executor#selectList
除了自定义Interceptor,首先会进入到CachingExecutor#query方法,它主要对全局缓存进行了处理:
- 根据查询条件(包括id、offset、limit、sql、查询参数和environmentId)创建CacheKey
- 获取MappedStatement的缓存对象Cache
- 从Cache中获取查询条件对应的缓存:如果有缓存,直接返回;如果没有缓存,进一步查询,并添加缓存
CachingExecutor#query:
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
之所以说CachingExecutor#query方法处理的是全局缓存,是因为这个缓存存在于MappedStatement,是所有会话共享的对象。
接着,会执行BaseExecutor#query方法,它主要对会话缓存进行处理:
- 从执行器localCache中获取查询条件对应的缓存
- 如果有缓存,直接返回;如果没有缓存,进一步查询数据库,并添加缓存
之所以说BaseExecutor#query方法处理的是会话缓存,是应为这个缓存存在于BaseExecutor,只有当前SqlSession才可以访问。
BaseExecutor#query和BaseExecutor#queryFromDatabase:
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache();
}
}
return list;
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
具体查询数据库逻辑交给子类实现,默认SimpleExecutor执行步骤如下:
- 创建StatementHandler
- 创建Statement(参数解析)
- 执行Statement
- 结果集封装
SimpleExecutor#doQuery:
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 创建StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 创建Statement(参数解析)
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行Statement&结果集封装
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
默认会创建RoutingStatementHandler,它只是一个代理对象,会根据MappedStatement创建具体SimpleStatementHandler/PreparedStatementHandler/CallableStatementHandler实现类。
在创建StatementHandler过程中,还会使用Interceptor进行代理,因此最终返回的对象是Interceptor -> RoutingStatementHandler -> 具体实现类的层级结构。
默认情况下,创建的StatementHandler具体实现类一般是PreparedStatementHandler。
BaseStatementHandler是SimpleStatementHandler/PreparedStatementHandler/CallableStatementHandler的共同父类,它的构造函数会创建ParameterHander和ResultSetHandler对象(并使用插件代理),用于参数解析和结果集封装。
Configuration#newStatementHandler、RoutingStatementHandler构造函数和BaseStatementHandler构造函数:
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 创建RoutingStatementHandler
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 使用插件代理
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
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);
}
SimpleExecutor#prepareStatement会创建Statement,并设置参数:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
// 创建Statement
stmt = handler.prepare(connection, transaction.getTimeout());
// 设置参数
handler.parameterize(stmt);
return stmt;
}
设置参数时,除了自定义Interceptor,默认会执行PreparedStatementHandler#parameterize方法,实际上会调用ParameterHandler(默认是DefaultParameterHandler)进行处理:
- 获取参数映射配置信息
- 遍历参数映射配置信息,使用TypeHandler设置参数
PreparedStatementHandler#parameterize、DefaultParameterHandler#setParameters和BaseTypeHandler#setParameter:
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null) {
if (jdbcType == null) {
throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
}
try {
ps.setNull(i, jdbcType.TYPE_CODE);
} catch (SQLException e) {
throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "
+ "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "
+ "Cause: " + e, e);
}
} else {
try {
setNonNullParameter(ps, i, parameter, jdbcType);
} catch (Exception e) {
throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . "
+ "Try setting a different JdbcType for this parameter or a different configuration property. "
+ "Cause: " + e, e);
}
}
}
接下来,默认情况下使用PreparedStatementHandler#query查询数据库,它会执行Statement,然后使用ResultSetHandler对结果集进行封装:
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
ResultSetHandler的默认实现类是DefaultResultSetHandler,处理步骤如下:
- 从MappedStatement获取结果集字段映射关系ResultMap
- 根据ResultMap,使用ResultHandler对字段进行封装
DefaultResultSetHandler#handleResultSets、DefaultResultSetHandler#handleResultSet、DefaultResultSetHandler#handleRowValues、DefaultResultSetHandler#handleRowValuesForSimpleResultMap:
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults,
ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else if (resultHandler == null) {
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
multipleResults.add(defaultResultHandler.getResultList());
} else {
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
} finally {
closeResultSet(rsw.getResultSet());
}
}
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler,
RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
if (resultMap.hasNestedResultMaps()) {
ensureNoRowBounds();
checkResultHandler();
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
结果集封装主要有两个步骤:
- 根据ResultMap初始化响应Java对象
- 根据的属性-列字段名映射关系,从结果集获取对应值,设置到响应对象中
- 将该行结果集封装的Java对象,添加到返回值列表中
DefaultResultSetHandler#handleRowValuesForSimpleResultMap、DefaultResultSetHandler#getRowValue和DefaultResultSetHandler#applyAutomaticMappings:
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap,
ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
ResultSet resultSet = rsw.getResultSet();
skipRows(resultSet, rowBounds);
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
// 获取每一行的响应对象
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
// 添加返回值列表
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, false)) {
// 自动映射
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
String columnPrefix) throws SQLException {
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
boolean foundValues = false;
if (!autoMapping.isEmpty()) {
// 遍历属性-列字段名映射
for (UnMappedColumnAutoMapping mapping : autoMapping) {
// 将列数据封装成对应Java类型的属性值
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
if (value != null) {
foundValues = true;
}
if (value != null || configuration.isCallSettersOnNulls() && !mapping.primitive) {
// 将属性值设置到响应对象的成员变量中
metaObject.setValue(mapping.property, value);
}
}
}
return foundValues;
}
2.1.2 Executor#update
除了自定义Interceptor,首先会进入到CachingExecutor#update方法,它主要对全局缓存进行清除工作,然后再调用代理对象的BaseExecutor#update方法。
CachingExecutor#update和CachingExecutor#flushCacheIfRequired:
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
// 清除全局缓存
flushCacheIfRequired(ms);
// 执行代理对象更新方法
return delegate.update(ms, parameterObject);
}
private void flushCacheIfRequired(MappedStatement ms) {
// 从MappedStatement中获取缓存
Cache cache = ms.getCache();
if (cache != null && ms.isFlushCacheRequired()) {
// 清除缓存
tcm.clear(cache);
}
}
BaseExecutor#update会对局部缓存进行清除,然后调用实现类的doUpdate方法。
BaseExecutor#update和BaseExecutor#clearLocalCache:
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 清除局部缓存
clearLocalCache();
// 执行实现类更新方法
return doUpdate(ms, parameter);
}
public void clearLocalCache() {
if (!closed) {
localCache.clear();
localOutputParameterCache.clear();
}
}
默认执行器实现类为SimpleExecutor,与doQuery方法类似,其doUpdate方法会执行如下逻辑:
- 创建StatementHandler
- 创建Statement(参数解析)
- 执行Statement
- 结果集封装
SimpleExecutor#doUpdate:
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 创建StatementHandler
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
// 创建Statement(参数解析)
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行Statement&结果集封装
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
前2个步骤与doQuery方法相同,这里不再赘述,主要看后两个步骤,对应为StatementHandler#update方法,默认实现类为PreparedStatementHandler。
由于数据库insert/update/delete操作返回的都是影响条数,同查询相比,它们的结果集封装就显得十分简单:直接返回即可。
相反,由于数据库insert操作会新增主键,为了将主键值回显给入参对象,这里还需要进行特殊处理。
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行SQL
ps.execute();
// 获取影响条数
int rows = ps.getUpdateCount();
// 回显主键值
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
如果没有设置回显主键(默认情况下),KeyGenerator实现类为NoKeyGenerator,其processAfter是个空方法:
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
// Do Nothing
}
通过以下方式可以开启回显主键功能:
<insert id="createUser" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
<!-- SQL -->
</insert>
此时默认KeyGenerator实现类为Jdbc3KeyGenerator,它会从结果集中获取对应数据库主键值(keyColumn),设置到入参对象的对应属性(keyProperty):
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
processBatch(ms, stmt, parameter);
}
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);
}
}
private void assignKeys(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, String[] keyProperties,
Object parameter) throws SQLException {
if (parameter instanceof ParamMap || parameter instanceof StrictMap) {
// Multi-param or single param with @Param
assignKeysToParamMap(configuration, rs, rsmd, keyProperties, (Map<String, ?>) parameter);
} else if (parameter instanceof ArrayList && !((ArrayList<?>) parameter).isEmpty()
&& ((ArrayList<?>) parameter).get(0) instanceof ParamMap) {
// Multi-param or single param with @Param in batch operation
assignKeysToParamMapList(configuration, rs, rsmd, keyProperties, (ArrayList<ParamMap<?>>) parameter);
} else {
// Single param without @Param
assignKeysToParam(configuration, rs, rsmd, keyProperties, parameter);
}
}
private void assignKeysToParam(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,
String[] keyProperties, Object parameter) throws SQLException {
Collection<?> params = collectionize(parameter);
if (params.isEmpty()) {
return;
}
List<KeyAssigner> assignerList = new ArrayList<>();
for (int i = 0; i < keyProperties.length; i++) {
assignerList.add(new KeyAssigner(configuration, rsmd, i + 1, null, keyProperties[i]));
}
Iterator<?> iterator = params.iterator();
while (rs.next()) {
if (!iterator.hasNext()) {
throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, params.size()));
}
Object param = iterator.next();
// 设值
assignerList.forEach(x -> x.assign(rs, param));
}
}
protected void assign(ResultSet rs, Object param) {
if (paramName != null) {
// If paramName is set, param is ParamMap
param = ((ParamMap<?>) param).get(paramName);
}
MetaObject metaParam = configuration.newMetaObject(param);
try {
if (typeHandler == null) {
if (!metaParam.hasSetter(propertyName)) {
throw new ExecutorException("No setter found for the keyProperty '" + propertyName + "' in '"
+ metaParam.getOriginalObject().getClass().getName() + "'.");
}
Class<?> propertyType = metaParam.getSetterType(propertyName);
typeHandler = typeHandlerRegistry.getTypeHandler(propertyType,
JdbcType.forCode(rsmd.getColumnType(columnPosition)));
}
if (typeHandler == null) {
// Error?
} else {
// 获取数据库主键值
Object value = typeHandler.getResult(rs, columnPosition);
// 设置入参
metaParam.setValue(propertyName, value);
}
} catch (SQLException e) {
throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e,
e);
}
}
2.2 使用映射接口代理对象执行
通过SqlSession#getMapper方法,可以获取映射接口的代理对象。
该代理对象实际上是从Configuration中获取的,Configuration又是从MapperRegistry中获取的,MapperRegistry会从缓存中获取MapperProxyFactory创建SqlSession的代理对象。
DefaultSqlSession#getMapper、Configuration#getMapper、MapperRegistry#getMapper和MapperProxyFactory#newInstance:
// DefaultSqlSession
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
// Configuration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
// MapperRegistry
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 获取缓存对应的MapperProxyFactory
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 创建代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
// MapperProxyFactory
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
MapperProxyFactory使用JDK的Proxy创建代理对象,实际逻辑位于InvocationHandler实现类MapperProxy的invoke方法中,它会创建MapperMethodInvoker对象(PlainMethodInvoker)并执行其invoke方法:
// MapperProxy
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return MapUtil.computeIfAbsent(methodCache, method, m -> {
if (!m.isDefault()) {
// 对于映射接口方法,会创建此对象
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
// PlainMethodInvoker
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
PlainMethodInvoker#invoke又会执行MapperMethod#execute方法,从该方法中可以明显看到,映射接口代理对象只不过是对SqlSession对象进行的封装,实际操作数据库还是得依靠SqlSession(此时又回到了2.1根据statementId执行的流程):
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
// 插入
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
// 更新
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
// 删除
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
// 查询
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
// 刷新
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ "' attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
还有个问题,MapperProxyFactory是什么时候生成的?实际上,在Mybatis启动时,通过MapperRegistry#addMapper方法就会创建MapperProxyFactory缓存(具体流程可以查看Mybatis如何添加映射接口和映射文件?):
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 添加MapperProxyFactory缓存
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
我们也可以发现,MapperProxyFactory缓存与MappedStatement并没有直接关系,它们之间需要SqlSession进行连接。即映射接口代理对象 -> SqlSession -> Configuration -> MapperRegistry -> 映射文件。
3 关闭SqlSession
数据库连接是计算机网络资源,使用完需要及时关闭,避免资源泄露,造成服务器性能下降。
如果使用了数据库连接池,则需要清理本次会话的缓存数据,将连接放回连接池。
使用SqlSession#close()可以关闭本次会话连接,由于SqlSession实现了Closeable接口,通常使用以下方式自动关闭:
try (SqlSession session = sqlSessionFactory.openSession(true)) {
// 执行SQL语句
}
SqlSession#close()方法会本次会话的执行器Executor和游标Cursor,具体怎么关闭资源,实际上是由执行器和游标实现类自己去实现的。总体来说会完成一下工作:
- 清除会话缓存数据。
- 关闭数据库连接。如果使用连接池,则会本次连接放回连接池。
以下为相关源码。
SqlSession#close()关闭executor和cursor:
public void close() {
try {
executor.close(isCommitOrRollbackRequired(false));
closeCursors();
dirty = false;
} finally {
ErrorContext.instance().reset();
}
}
CachingExecutor#close()会回滚/提交本次会话的缓存,然后关闭代理执行器资源:
public void close(boolean forceRollback) {
try {
// issues #499, #524 and #573
if (forceRollback) {
tcm.rollback();
} else {
tcm.commit();
}
} finally {
delegate.close(forceRollback);
}
}
BaseExecutor#close()会关闭事务,并清除本地缓存:
public void close(boolean forceRollback) {
try {
try {
rollback(forceRollback);
} finally {
if (transaction != null) {
transaction.close();
}
}
} catch (SQLException e) {
// Ignore. There's nothing that can be done at this point.
log.warn("Unexpected exception on closing transaction. Cause: " + e);
} finally {
transaction = null;
deferredLoads = null;
localCache = null;
localOutputParameterCache = null;
closed = true;
}
}
Transaction#close()会关闭数据库连接,具体怎么实现交给子类决定。
例如,JdbcTransaction#Close()会直接关闭connection:
public void close() throws SQLException {
if (connection != null) {
resetAutoCommit();
if (log.isDebugEnabled()) {
log.debug("Closing JDBC Connection [" + connection + "]");
}
connection.close();
}
}
SqlSession#closeCursors()遍历关闭所有corsor:
private void closeCursors() {
if (cursorList != null && !cursorList.isEmpty()) {
for (Cursor<?> cursor : cursorList) {
try {
cursor.close();
} catch (IOException e) {
throw ExceptionFactory.wrapException("Error closing cursor. Cause: " + e, e);
}
}
cursorList.clear();
}
}
DefaultCursor#close()关闭结果集ResultSet:
public void close() {
if (isClosed()) {
return;
}
ResultSet rs = rsw.getResultSet();
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
// ignore
} finally {
status = CursorStatus.CLOSED;
}
}