一、MyBatis核心组件:
- Configuration:用于描述 MyBatis 主配置文件信息,MyBatis 框架在启动时会加载主配置文件,将配置信息转换为
Configuration 对象。 - SqlSession:面向用户的 API,是 MyBatis 与数据库交互的接口。
- Executor:SQL 执行器,用于和数据库交互。SqlSession 可以理解为 Executor 组件的外观(外观模式),真正执行
SQL 的是 Executor 组件。 - MappedStatement:用于描述 SQL 配置信息,MyBatis 框架启动时,XML 文件或者注解配置的 SQL信息会被转换为
MappedStatement 对象注册到 Configuration 组件中。 - StatementHandler:封装了对 JDBC 中 Statement 对象的操作,包括为 Statement
参数占位符设置值,通过 Statement 对象执行 SQL语句。 - TypeHandler:类型处理器,用于 Java 类型与 JDBC 类型之间的转换。
- ParameterHandler:用于处理 SQL 中的参数占位符,为参数占位符设置值。
- ResultSetHandler:封装了对 ResultSet 对象的处理逻辑,将结果集转换为 Java 实体对象。
环境:SpringBoot + mybatis
四大组件:Executor、ParameterHandler、StatementHandler、ResultSetHandler
二、启动时期:
项目启动时会加载MybatisAutoConfiguration类,在这个类中创建了SqlSessionFactory,并且会将XML文件或者注解配置的 SQL信息会被转换为 MappedStatement 对象注册到 Configuration 组件中。
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
applyConfiguration(factory);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if (this.properties.getTypeAliasesSuperType() != null) {
factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.typeHandlers)) {
factory.setTypeHandlers(this.typeHandlers);
}
// 解析本地xml文件成一个个Resource数组放到factory的mapperLocations中
// Resource中有xml文件的绝对地址
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
Set<String> factoryPropertyNames = Stream
.of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)
.collect(Collectors.toSet());
Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
// Need to mybatis-spring 2.0.2+
factory.setScriptingLanguageDrivers(this.languageDrivers);
if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
defaultLanguageDriver = this.languageDrivers[0].getClass();
}
}
if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
// Need to mybatis-spring 2.0.2+
factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
}
// 接着往下看
return factory.getObject();
}
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
// 进入到这里
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
// 创建SqlSessionFactory,我们进去看看
this.sqlSessionFactory = buildSqlSessionFactory();
}
SqlSessionFactoryBean
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
/** .......省略部分代码 */
// 判断mapperLocations是否不为空,这个值在上文已经设置,里面存储的是一个Resource数组
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
// 解析xml配置文件的SQL信息转换为 MappedStatement 对象注册到 Configuration 组件中
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}
return this.sqlSessionFactoryBuilder.build(configuration);
}
Configuration组件中的MappedStatements是一个Map类型,key是sql的方法,value是MappedStatement对象,里面包含id,sql语句等等
创建完SqlSessionFactory之后创建SqlSessionTemplate
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
// 传入sqlSessionFactory
return new SqlSessionTemplate(sqlSessionFactory);
}
}
三、运行时期:
当我们使用到Dao时,由于我们并没有写任何Dao的实现,所以在Spring DI(属性注入)的时候肯定是注入的代理类,调用方法的时候会调用InvocationHandler的invoke方法:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 是否是Object
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 是否是默认方法
else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 执行方法 进去看看
return mapperMethod.execute(sqlSession, args);
}
MapperMethod
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);
}
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;
}
这里我用的是SELECT查询语句,返回的是多个数据,所以我们进入到executeForMany(sqlSession, args)方法
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
// 真正的查询方法来了,注意看
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
// 进去看
return this.sqlSessionProxy.<E> selectList(statement, parameter);
}
SqlSessionTemplate.SqlSessionInterceptor
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取Sqlsession
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
// 调用方法
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
//从当前线程的threadLocal 中获取sqlSessionHolder
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
//调用静态方法sessionHoler 判断是否存在符合要求的sqlSession
SqlSession session = sessionHolder(executorType, holder);
// 判断当前sqlSessionHolder 中是否持有sqlSession (即当前操作是否在事务当中)
if (session != null) {
return session;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating a new SqlSession");
}
//获取新的sqlSession 对象。这里由sessionFacory产生的defaultSqlSession
session = sessionFactory.openSession(executorType);
//判断,当前是否存在事务,将sqlSession 绑定到sqlSessionHolder 中,并放到threadLoacl 当中
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
由此可知,SqlSession的创建时机是:如果开启了事务,在一个事务中是共享一个SqlSession的,只有第一次使用的时候会创建,如果没有开启事务,那么每次使用时都创建一个新的SqlSession。
紧接着我们进去看真正的方法调用的地方:
DefaultSqlSession.
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 根据方法名(类名+方法名)从configuration中的MappedStatements属性获取MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
// SqlSession中封装了Executor,Executor才是真正的SQL 执行器,用于和数据库交互
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
在selectList方法中根据方法名(类名+方法名)从configuration中的MappedStatements属性获取MappedStatement,之前有说到,在MyBatis启动时会创建SqlSessionFactory,同时将所有的xml文件解析成MappedStatement放入configuration组件中。
然后executor调用相应的方法:
Plugin.java
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
// 进入到这里
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
被分页插件拦截
@Override
public Object intercept(Invocation invocation) throws Throwable {
try {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
RowBounds rowBounds = (RowBounds) args[2];
ResultHandler resultHandler = (ResultHandler) args[3];
Executor executor = (Executor) invocation.getTarget();
CacheKey cacheKey;
BoundSql boundSql;
//由于逻辑关系,只会进入一次
if(args.length == 4){
//4 个参数时
boundSql = ms.getBoundSql(parameter);
cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
} else {
//6 个参数时
cacheKey = (CacheKey) args[4];
boundSql = (BoundSql) args[5];
}
List resultList;
//调用方法判断是否需要进行分页,如果不需要,直接返回结果
if (!dialect.skip(ms, parameter, rowBounds)) {
//反射获取动态参数
String msId = ms.getId();
Configuration configuration = ms.getConfiguration();
Map<String, Object> additionalParameters = (Map<String, Object>) additionalParametersField.get(boundSql);
//判断是否需要进行 count 查询
if (dialect.beforeCount(ms, parameter, rowBounds)) {
String countMsId = msId + countSuffix;
Long count;
//先判断是否存在手写的 count 查询
MappedStatement countMs = getExistedMappedStatement(configuration, countMsId);
if(countMs != null){
count = executeManualCount(executor, countMs, parameter, boundSql, resultHandler);
} else {
countMs = msCountMap.get(countMsId);
//自动创建
if (countMs == null) {
//根据当前的 ms 创建一个返回值为 Long 类型的 ms
countMs = MSUtils.newCountMappedStatement(ms, countMsId);
msCountMap.put(countMsId, countMs);
}
count = executeAutoCount(executor, countMs, parameter, boundSql, rowBounds, resultHandler);
}
//处理查询总数
//返回 true 时继续分页查询,false 时直接返回
if (!dialect.afterCount(count, parameter, rowBounds)) {
//当查询总数为 0 时,直接返回空的结果
return dialect.afterPage(new ArrayList(), parameter, rowBounds);
}
}
//判断是否需要进行分页查询
if (dialect.beforePage(ms, parameter, rowBounds)) {
//生成分页的缓存 key
CacheKey pageKey = cacheKey;
//处理参数对象
parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey);
//调用方言获取分页 sql
String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);
BoundSql pageBoundSql = new BoundSql(configuration, pageSql, boundSql.getParameterMappings(), parameter);
//设置动态参数
for (String key : additionalParameters.keySet()) {
pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
}
//执行分页查询
resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
} else {
//不执行分页的情况下,也不执行内存分页
resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
}
} else {
//rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
return dialect.afterPage(resultList, parameter, rowBounds);
} finally {
dialect.afterAll();
}
}
里面很多逻辑,不用管,我们直接看里面的executor.query代码:
CachingExecutor
@Override
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.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 走到这里,进去看看
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
BaseExecutor
@Override
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++;
// localCache:MyBatis的一级缓存
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
// list不为空,说明MyBatis的一级缓存中已经有数据
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();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
localCache是MyBatis的一级缓存,MyBatis的一级缓存是SqlSession级别的,因为Executor是SqlSession的一个属性(在getSqlSession()时),如果缓存中有数据,则直接返回,如果没有则从数据库查询数据。
1、Mybatis一级缓存的生命周期和SqlSession一致。
2、Mybatis的缓存没有更新缓存和缓存过期的概念,同时只是使用了默认的hashmap,也没有做容量上的限定。
3、Mybatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,有操作数据库写的话,会引起脏数据,建议是把一级缓存的默认级别设定为Statement,即不使用一级缓存。
我们进入queryFromDatabase方法看看:
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);
}
// 将新数据添加至缓存,key就是sql语句加上一些其他,例如方法名
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
localCache是BaseExecutor中的一个属性:
而PerpetualCache里面是一个Map
key的大概样子【与sql有关】:
接下来我们进入到doQuery方法看看:
SimpleExecutor
@Override
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,在方法里获取了Connection对象
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
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;
}
在doQuery方法里面获取了Connection 对象,并且创建了Statement。
接下来我们看一下handler.query(stmt, resultHandler)方法
// RoutingStatementHandler
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
return delegate.<E>query(statement, resultHandler);
}
// PreparedStatementHandler.java
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// Statement执行sql语句
ps.execute();
// 处理返回结果,进去看看
return resultSetHandler.<E> handleResultSets(ps);
}
DefaultResultSetHandler
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<Object>();
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++;
}
}
// 返回multipleResults中的数据,数据在handleResultSet方法里面放入的
return collapseSingleResultList(multipleResults);
}
DefaultResultSetHandler
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中
multipleResults.add(defaultResultHandler.getResultList());
} else {
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}
相关文章:
MyBatis四大组件:https://blog.csdn.net/csdn_tiger1993/article/details/112417864
https://www.freesion.com/article/14401169270/
SqlSession创建时机:https://www.ucloud.cn/yun/69739.html
MyBatis源码:https://blog.csdn.net/weixin_39687667/article/details/111276301