下面我们分析一个最常用的mapper接口,根据主键ID查询单个实体对象的源码实现逻辑。
省略配置文件…因为重点在于分析查询源码,先回顾下我们应用app服务端开发熟悉的配置mybatis过程。
//加载MapperConfig.xml的配置文件
final String resource = "org/apache/ibatis/builder/MapperConfig.xml";
//根据配置文件获取字符流
final Reader reader = Resources.getResourceAsReader(resource);
//根据字符流构建SqlSessionFactory
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
//获取全局核心配置类
Configuration configuration = new Configuration(sqlMapper.getConfiguration().getEnvironment());
//为全局配置添加需要的Mapper接口
configuration.addMapper(AuthorMapperWithAnnotation.class);
//创建SQL会话工厂
SqlSessionFactory sqlMapperWithAnnotation = new DefaultSqlSessionFactory(configuration);
//获取SQL会话
SqlSession sqlSession = sqlMapperWithAnnotation.openSession();
//从会话中获取绑定好的mapper接口信息
AuthorMapperWithAnnotation mapper = sqlSession.getMapper(AuthorMapperWithAnnotation.class);
//执行mapper接口的实现方法
Author author = mapper.selectAuthorInfoById(101);
//期待返回正确的数据
assertEquals(101,author.getId());
通常我们会定义mapper接口文件或者通过注解方式来实现,这里举例注解方式。
/**
* @author liuxiaocheng
* @create 2020/3/28 9:43 上午
*/
public interface AuthorMapperWithAnnotation {
// TODO: 2020/3/28 by wolf_love666 for study source code
@Select("select id, username, password, email, bio, favourite_section from author where id = #{id}")
Author selectAuthorInfoById(int id);
}
通常意义上我们的服务端开发已经结束了,然后会进行单元测试看下数据是否正常返回。而今天我们将揭开mybatis是如何返回正确的结果的。
MapperProxy(根据动态代理执行目标mapper接口)
MapperMethod(根据@Select找到对应的Select方法、根据方法签名确定返回参数)
ParamNameResolver(根据args入参解析属性入参,获取对应的动态值)
/**
* Mapper接口代理--真正的mapper执行类
*/
public class MapperProxy<T> implements InvocationHandler, Serializable {
...省略本次无关内容...
//mapper接口的执行
@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 {
return cachedInvoker(proxy, method, args).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
//缓存的执行器
private MapperMethodInvoker cachedInvoker(Object proxy, Method method, Object[] args) throws Throwable {
try {
return methodCache.computeIfAbsent(method, m -> {
if (m.isDefault()) {
try {
//jdk8和jdk9的默认方法执行
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
//返回声明的方法执行器
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
//Java9获取方法处理器
private MethodHandle getMethodHandleJava9(Method method)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
final Class<?> declaringClass = method.getDeclaringClass();
return ((Lookup) privateLookupInMethod.invoke(null, declaringClass, MethodHandles.lookup())).findSpecial(
declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()),
declaringClass);
}
//Java8获取方法处理器
private MethodHandle getMethodHandleJava8(Method method)
throws IllegalAccessException, InstantiationException, InvocationTargetException {
final Class<?> declaringClass = method.getDeclaringClass();
return lookupConstructor.newInstance(declaringClass, ALLOWED_MODES).unreflectSpecial(method, declaringClass);
}
//Mapper方法执行器接口
interface MapperMethodInvoker {
Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
}
...省略本次无关内容...
}
/**
* Mapper的方法 add by wolf_love666
*/
public class MapperMethod {
//命令
private final SqlCommand command;
//方法签名
private final MethodSignature method;
//映射的接口和方法和全局配置
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
//执行sqlSession和参数
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
...省略本次无关内容...
//查询
case SELECT:
//无返回值
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
//返回多条结果
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
//返回map
} 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;
...省略本次无关内容...
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;
}
}
/**
* 参数名字解析器 add by wolf_love666
*/
public class ParamNameResolver {
public static final String GENERIC_NAME_PREFIX = "param";
...省略本次无关内容...
/**
*
返回一个没有名称的非特殊参数。 使用命名规则来命名多个参数。 除了默认名称外,此方法还添加了通用名称(param1,param2,* ...)。
*/
public Object getNamedParams(Object[] args) {
//获取对象
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
} else {
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
//添加通用的参数名字
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
// 确保带有@Param注解参数的不被重写
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
}
/**
* 默认的会话实现类,这个类非安全 add by wolf_love666
*/
public class DefaultSqlSession implements SqlSession {
//全局配置
private final Configuration configuration;
//执行器
private final Executor executor;
...省略本次无关内容...
//查询单条数据
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
@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 {
MappedStatement ms = configuration.getMappedStatement(statement);
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();
}
}
...省略本次无关内容...
}
/**
*全局配置类 add by wolf_love666
*/
public class Configuration {
...省略本次无关内容...
//获取映射的会话根据会话ID
public MappedStatement getMappedStatement(String id) {
return this.getMappedStatement(id, true);
}
//获取映射的会话根据会话ID
public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
//是否需要验证不匹配的会话
if (validateIncompleteStatements) {
//构建所有会话验证
buildAllStatements();
}
//获取映射的会话根据会话ID
return mappedStatements.get(id);
}
/*
* 解析缓存中所有未处理的语句节点。建议一旦添加了所有映射器,便调用此方法,因为它提供了快速失败语句验证
*/
protected void buildAllStatements() {
parsePendingResultMaps();
if (!incompleteCacheRefs.isEmpty()) {
synchronized (incompleteCacheRefs) {
incompleteCacheRefs.removeIf(x -> x.resolveCacheRef() != null);
}
}
if (!incompleteStatements.isEmpty()) {
synchronized (incompleteStatements) {
incompleteStatements.removeIf(x -> {
x.parseStatementNode();
return true;
});
}
}
if (!incompleteMethods.isEmpty()) {
synchronized (incompleteMethods) {
incompleteMethods.removeIf(x -> {
x.resolve();
return true;
});
}
}
}
//解析等待的结果map集
private void parsePendingResultMaps() {
if (incompleteResultMaps.isEmpty()) {
return;
}
synchronized (incompleteResultMaps) {
boolean resolved;
IncompleteElementException ex = null;
do {
resolved = false;
Iterator<ResultMapResolver> iterator = incompleteResultMaps.iterator();
while (iterator.hasNext()) {
try {
iterator.next().resolve();
iterator.remove();
resolved = true;
} catch (IncompleteElementException e) {
ex = e;
}
}
} while (resolved);
if (!incompleteResultMaps.isEmpty() && ex != null) {
// At least one result map is unresolvable.
throw ex;
}
}
}
...省略本次无关内容...
}
//严格的map内部实现类
protected static class StrictMap<V> extends HashMap<String, V> {
...省略本次无关内容...
//获取全局配置的属性对应的值
@Override
public V get(Object key) {
V value = super.get(key);
if (value == null) {
throw new IllegalArgumentException(name + " does not contain value for " + key);
}
if (value instanceof Ambiguity) {
throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name
+ " (try using the full name including the namespace, or rename one of the entries)");
}
return value;
}
//内部语义不明的类
protected static class Ambiguity {
final private String subject;
public Ambiguity(String subject) {
this.subject = subject;
}
public String getSubject() {
return subject;
}
}
private String getShortName(String key) {
final String[] keyParts = key.split("\\.");
return keyParts[keyParts.length - 1];
}}
/**
*缓存执行器
*/
public class CachingExecutor implements Executor {
//代表执行器
private final Executor delegate;
...省略本次无关内容...
//查询,并创建缓存
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//从映射的会话中获取绑定的可执行SQL
BoundSql boundSql = ms.getBoundSql(parameterObject);
//创建缓存key,便于二次缓存查询提升性能(这里不关注这个缓存可以跳过)
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
//执行绑定SQL后和缓存key查询
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
//带缓存key查询
@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.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);
}
}
当前数据值
MappedStatement ms参数需要关注的值
/**
* 被映射的会话
*/
public final class MappedStatement {
...省略本次无关内容...
//获取绑定的SQL
public BoundSql getBoundSql(Object parameterObject) {
//1、根据上图可以得知sqlSource中的sql为:
//select id, username, password, email, bio, favourite_section from author where id = ?
//id入参数类型为int,
//2、将参数 动态数据值101 和 静态的SQL绑定,getBoundSql实际上调用的是BoundSql的构造函数BoundSql()。可参考下面BoundSql源码构造函数
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
//获取参数映射集合实际上就是动态参数传入的时候方法的入参数属性,这里就是int类型的ID
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
//如果无参数映射传入,则重新构造绑定SQL根据数据池的全局配置configuration
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// 检查参数映射中是否有嵌套情况
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
return boundSql;
}
...省略本次无关内容...
}
public class BoundSql {
//sql
private final String sql;
//参数映射
private final List<ParameterMapping> parameterMappings;
//参数对象
private final Object parameterObject;
//附加参数
private final Map<String, Object> additionalParameters;
//元参数
private final MetaObject metaParameters;
//绑定SQL构造器
public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.parameterObject = parameterObject;
this.additionalParameters = new HashMap<>();
this.metaParameters = configuration.newMetaObject(additionalParameters);
}
...省略本次无关内容...
}
/**
* 基础执行器 add by wolf_love666
*/
public abstract class BaseExecutor implements Executor {
//日志
private static final Log log = LogFactory.getLog(BaseExecutor.class);
//事务
protected Transaction transaction;
//执行器包装器
protected Executor wrapper;
//并发队列,延迟加载
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
//永久的本地缓存
protected PerpetualCache localCache;
//永久的本地外放参数缓存
protected PerpetualCache localOutputParameterCache;
//配置对象--全局很重要
protected Configuration configuration;
//查询栈
protected int queryStack;
//是否关闭
private boolean closed;
...省略本次无关内容...
//带缓存查询会话
@SuppressWarnings("unchecked")
@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.");
}
//查询栈=0并且要求被刷新缓存的会话操作
if (queryStack == 0 && ms.isFlushCacheRequired()) {
//清除本地缓存
clearLocalCache();
}
//查询栈+1
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 {
//查询栈-1维护初始0
queryStack--;
}
//如果查询栈=0则延迟加载队列 进行延迟循环加载
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
//延迟队列清空
// issue #601
deferredLoads.clear();
//如果配置的本地缓存范围是会话层
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
//清除本地缓存
// issue #482
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;
}
...省略本次无关内容...
}
/**
* 简单执行器 add by wolf_love666
*/
public class SimpleExecutor extends BaseExecutor {
...省略本次无关内容...
//执行查询
@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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//准备会话
stmt = prepareStatement(handler, ms.getStatementLog());
//执行查询
return handler.query(stmt, resultHandler);
} finally {
//关闭会话
closeStatement(stmt);
}
}
//预准备会话
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//从BaseExecutor中获取事务管理链接和会话日志
Connection connection = getConnection(statementLog);
//给会话绑定拦截器,可以跳过
stmt = handler.prepare(connection, transaction.getTimeout());
//拦截器的参数处理,可以跳过
handler.parameterize(stmt);
return stmt;
}
...省略本次无关内容...
}
/**
* 路由会话处理器
* @author Clinton Begin
*/
public class RoutingStatementHandler implements StatementHandler {
//会话处理器
private final StatementHandler delegate;
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());
}
...省略本次无关内容...
//查询
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
return delegate.query(statement, resultHandler);
}
}
/**
* 预会话处理器 add by wolf_love666
*/
public class PreparedStatementHandler extends BaseStatementHandler {
...省略本次无关内容...
//查询
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//执行jdbc操作 参考2.4操作数据库
ps.execute();
//处理返回的结果集 参考2.5结果集处理
return resultSetHandler.handleResultSets(ps);
}
...省略本次无关内容...
}
操作数据库阶段主要完成会话执行SQL操作–不是本次重点跳过
当然数据库这里可以有多种MySQL,Oracle,MongoDB等等
/**
* 默认结果集处理器 add by wolf_love666
*/
public class DefaultResultSetHandler implements ResultSetHandler {
//延迟对象
private static final Object DEFERRED = new Object();
//执行器
private final Executor executor;
//全局配置
private final Configuration configuration;
//映射会话
private final MappedStatement mappedStatement;
//行数据
private final RowBounds rowBounds;
//参数处理器
private final ParameterHandler parameterHandler;
//结果处理器
private final ResultHandler<?> resultHandler;
//执行SQL
private final BoundSql boundSql;
//类型处理器注册器
private final TypeHandlerRegistry typeHandlerRegistry;
//对象工厂
private final ObjectFactory objectFactory;
//反射工厂
private final ReflectorFactory reflectorFactory;
//嵌套结果映射
private final Map<CacheKey, Object> nestedResultObjects = new HashMap<>();
//原型对象
private final Map<String, Object> ancestorObjects = new HashMap<>();
//先前行值
private Object previousRowValue;
//多个结果集
private final Map<String, ResultMapping> nextResultMaps = new HashMap<>();
//等待的关系
private final Map<CacheKey, List<PendingRelation>> pendingRelations = new HashMap<>();
//自动映射的缓存
private final Map<String, List<UnMappedColumnAutoMapping>> autoMappingsCache = new HashMap<>();
//暂时标记 使用构造器映射(使用属性减少内存使用)
private boolean useConstructorMappings;
//等待关系
private static class PendingRelation {
public MetaObject metaObject;
public ResultMapping propertyMapping;
}
//未映射的列自动映射 内部类
private static class UnMappedColumnAutoMapping {
private final String column;
private final String property;
private final TypeHandler<?> typeHandler;
private final boolean primitive;
public UnMappedColumnAutoMapping(String column, String property, TypeHandler<?> typeHandler, boolean primitive) {
this.column = column;
this.property = property;
this.typeHandler = typeHandler;
this.primitive = primitive;
}
}
...省略本次无关内容...
//处理结果集
// HANDLE RESULT SETS
//
@Override
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);
}