MyBatis传统方式源码剖析

一.源码剖析-初始化

@Test
    public void test1() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        //这行代码是初始化工作的开始
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        IOrderMapper mapper = sqlSession.getMapper(IOrderMapper.class);
        List<Order> orderAndUser = mapper.findOrderAndUser();
        for (Order order : orderAndUser) {
            System.out.println(order);
        }
    }

1. 首先进入SqlsessionFactoryBuilder().build(inputStream)方法

    // 1.我们最初调用的build
    public SqlSessionFactory build(InputStream inputStream) {
        //调用了重载方法
        return build(inputStream, null, null);
    }
    // 2.调用的重载方法
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
            // 创建 XMLConfigBuilder, XMLConfigBuilder是专门解析mybatis的配置文件的类
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            // 执行 XML 解析
            // 创建 DefaultSqlSessionFactory 对象
            return build(parser.parse());
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
            ErrorContext.instance().reset();
            try {
                inputStream.close();
            } catch (IOException e) {
                // Intentionally ignore. Prefer previous error.
            }
        }
    }

MyBatis在初始化的时候,会将MyBatis的配置信息全部加载到内存中,使⽤

org.apache.ibatis.session.Configuratio n 实例来维护

下⾯进⼊对配置⽂件解析部分:

⾸先对Configuration对象进⾏介绍:

Configuration对象的结构和xml配置⽂件的对象⼏乎相同。

回顾⼀下xml中的配置标签有哪些:

properties (属性),settings (设置),typeAliases (类型别名),typeHandlers (类型处理 器),

objectFactory (对象⼯⼚),mappers (映射器)等 Configuration也有对应的对象属性来封装它们 。

也就是说,初始化配置⽂件信息的本质就是创建Configuration对象,将解析的xml数据封装到 Configuration内部属性中。

    /**
     * 解析 XML 成 Configuration 对象。
     *
     * @return Configuration 对象
     */
    public Configuration parse() {
        // 若已解析,抛出 BuilderException 异常
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        // 标记已解析
        parsed = true;
        ///parser是XPathParser解析器对象,读取节点内数据,<configuration>是MyBatis配置文件中的顶层标签
        // 解析 XML configuration 节点
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }
 /**
     * 解析 XML
     *
     * 具体 MyBatis 有哪些 XML 标签,参见 《XML 映射配置文件》http://www.mybatis.org/mybatis-3/zh/configuration.html
     *
     * @param root 根节点
     */
    private void parseConfiguration(XNode root) {
        try {
            //issue #117 read properties first
            // 解析 <properties /> 标签
            propertiesElement(root.evalNode("properties"));
            // 解析 <settings /> 标签
            Properties settings = settingsAsProperties(root.evalNode("settings"));
            // 加载自定义的 VFS 实现类
            loadCustomVfs(settings);
            // 解析 <typeAliases /> 标签
            typeAliasesElement(root.evalNode("typeAliases"));
            // 解析 <plugins /> 标签
            pluginElement(root.evalNode("plugins"));
            // 解析 <objectFactory /> 标签
            objectFactoryElement(root.evalNode("objectFactory"));
            // 解析 <objectWrapperFactory /> 标签
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            // 解析 <reflectorFactory /> 标签
            reflectorFactoryElement(root.evalNode("reflectorFactory"));
            // 赋值 <settings /> 到 Configuration 属性
            settingsElement(settings);
            // read it after objectFactory and objectWrapperFactory issue #631
            // 解析 <environments /> 标签
            environmentsElement(root.evalNode("environments"));
            // 解析 <databaseIdProvider /> 标签
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            // 解析 <typeHandlers /> 标签
            typeHandlerElement(root.evalNode("typeHandlers"));
            // 解析 <mappers /> 标签
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }
介绍⼀下 MappedStatement
作⽤: MappedStatement Mapper 配置⽂件中的⼀个 select/update/insert/delete 节点相对应。
mapper 中配置的标签都被封装到了此对象中,主要⽤途是描述⼀条 SQL 语句。

2. 初始化过程

回顾刚开 始介绍的加载配置⽂件的过程中,会对 mybatis-config.xm l 中的各个标签都进⾏解析,其
中有 mappers 标签⽤来引⼊ mapper.xml ⽂件或者配置 mapper 接⼝的⽬录。
<select id="getUser" resultType="user" >
 select * from user where id=#{id}
</select>

以上的select标签会在初始化配置⽂件时被解析封装成⼀个MappedStatement对象,然后存储在 Configuration 对象的mappedStatements属性中,mappedStatements 是⼀个HashMap,存储时key =全限定类名+⽅法名, value =对应的MappedStatement对象。

configuration中对应的属性为:

Map < String , MappedStatement > mappedStatements = new StrictMap < MappedStatement > ( "MappedStatements collection" )
XMLConfigBuilder 中的处理:
private void parseConfiguration(XNode root) {
     try {
         //省略其他标签的处理
         mapperElement(root.evalNode("mappers"));
     } catch (Exception e) {
         throw new BuilderException("Error parsing SQL Mapper Configuration.Cause:" + e,     e);
     }
 }
到此对 xml 配置⽂件的解析就结束了,回到步骤 2. 中调⽤的重载 build ⽅法

// 5.调⽤的重载⽅法
public SqlSessionFactory build(Configuration config) {
 //创建了 DefaultSqlSessionFactory 对象,传⼊ Configuration 对象。
 return new DefaultSqlSessionFactory(config);
}

二. 源码剖析-执⾏SQL流程

先简单介绍 SqlSession
SqlSession 是⼀个接⼝,它有两个实现类: DefaultSqlSession ( 默认 )
SqlSessionManager ( 弃⽤,不做介绍 )
SqlSession MyBatis 中⽤于和数据库交互的顶层类,通常将它与 ThreadLocal 绑定,⼀个会话使⽤⼀ 个SqlSession, 并且在使⽤完毕后需要 close
public class DefaultSqlSession implements SqlSession {
        private final Configuration configuration ;
        private final Executor executor ;
SqlSession 中的两个最重要的参数, configuration 与初始化时的相同, Executor 为执⾏器
Executor
Executor 也是⼀个接⼝,他有三个常⽤的实现类:
BatchExecutor ( 重⽤语句并执⾏批量更新 )
ReuseExecutor ( 重⽤预处理语句 prepared statements)
SimpleExecutor ( 普通的执⾏器,默认 )
继续分析,初始化完毕后,我们就要执⾏ SQL
SqlSession sqlSession = factory . openSession ();
List < User > list = sqlSession . selectList ( "com.lagou.mapper.UserMapper.getUserByName" );
获得 sqlSession
/**
 * Copyright 2009-2016 the original author or authors.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.ibatis.session;

import java.sql.Connection;

/**
 * Creates an {@link SqlSession} out of a connection or a DataSource
 *
 * {@link SqlSession} 工厂接口
 *
 * @author Clinton Begin
 */
public interface SqlSessionFactory {

    SqlSession openSession();

    SqlSession openSession(boolean autoCommit);

    SqlSession openSession(Connection connection);

    SqlSession openSession(TransactionIsolationLevel level);

    SqlSession openSession(ExecutorType execType);

    SqlSession openSession(ExecutorType execType, boolean autoCommit);

    SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);

    SqlSession openSession(ExecutorType execType, Connection connection);

    Configuration getConfiguration();

}
/**
 * 默认 SqlSessionFactory 实现类
 *
 * @author Clinton Begin
 */
public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private final Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    //6. 进入openSession方法
    @Override
    public SqlSession openSession() {
        //getDefaultExecutorType()传递的是SimpleExecutor
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
    }
//7. 进入openSessionFromDataSource。
    //ExecutorType 为Executor的类型,TransactionIsolationLevel为事务隔离级别,autoCommit是否开启事务
    //openSession的多个重载方法可以指定获得的SeqSession的Executor类型和事务的处理
    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        try {
            // 获得 Environment 对象
            final Environment environment = configuration.getEnvironment();
            // 创建 Transaction 对象
            final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            // 创建 Executor 对象
            final Executor executor = configuration.newExecutor(tx, execType);
            // 创建 DefaultSqlSession 对象
            return new DefaultSqlSession(configuration, executor, autoCommit);
        } catch (Exception e) {
            // 如果发生异常,则关闭 Transaction 对象
            closeTransaction(tx); // may have fetched a connection so lets call close()
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }
执⾏ sqlsession 中的 api
//8.进入selectList方法,多个重载方法
    @Override
    public <E> List<E> selectList(String statement) {
        return this.selectList(statement, 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 对象
            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();
        }
    }

    @Override
    public void select(String statement, Object parameter, ResultHandler handler) {
        select(statement, parameter, RowBounds.DEFAULT, handler);
    }

    @Override
    public void select(String statement, ResultHandler handler) {
        select(statement, null, RowBounds.DEFAULT, handler);
    }

    @Override
    public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
        try {
            // 获得 MappedStatement 对象
            MappedStatement ms = configuration.getMappedStatement(statement);
            // 执行查询
            executor.query(ms, wrapCollection(parameter), rowBounds, handler);
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }

    @Override
    public int insert(String statement) {
        return insert(statement, null);
    }

    @Override
    public int insert(String statement, Object parameter) {
        return update(statement, parameter);
    }

    @Override
    public int update(String statement) {
        return update(statement, null);
    }

    @Override
    public int update(String statement, Object parameter) {
        try {
            // 标记 dirty ,表示执行过写操作
            dirty = true;
            // 获得 MappedStatement 对象
            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();
        }
    }

    @Override
    public int delete(String statement) {
        return update(statement, null);
    }

    @Override
    public int delete(String statement, Object parameter) {
        return update(statement, parameter);
    }
源码剖析 -executor
继续源码中的步骤,进⼊ executor.query()
//此方法在SimpleExecutor的父类BaseExecutor中实现
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        //根据传入的参数动态获得SQL语句,最后返回用BoundSql对象表示
        BoundSql boundSql = ms.getBoundSql(parameter);
        //为本次查询创建缓存的Key
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        // 查询
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
    @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());
        // 已经关闭,则抛出 ExecutorException 异常
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        // 清空本地缓存,如果 queryStack 为零,并且要求清空本地缓存。
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
            clearLocalCache();
        }
        List<E> list;
        try {
            // queryStack + 1
            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 - 1
            queryStack--;
        }
        if (queryStack == 0) {
            // 执行延迟加载
            for (DeferredLoad deferredLoad : deferredLoads) {
                deferredLoad.load();
            }
            // issue #601
            // 清空 deferredLoads
            deferredLoads.clear();
            // 如果缓存级别是 LocalCacheScope.STATEMENT ,则进行清理
            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;
        // 在缓存中,添加占位对象。此处的占位符,和延迟加载有关,可见 `DeferredLoad#canLoad()` 方法
        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类中实现⽗类的doQuery抽象⽅法
@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();
            // 传入参数创建StatementHanlder对象来执行查询
            StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            // 创建jdbc中的statement对象
            stmt = prepareStatement(handler, ms.getStatementLog());
            // 执行 StatementHandler  ,进行读操作
            return handler.query(stmt, resultHandler);
        } finally {
            // 关闭 StatementHandler 对象
            closeStatement(stmt);
        }
    }
// 初始化 StatementHandler 对象
    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        // 获得 Connection 对象
        Connection connection = getConnection(statementLog);
        // 创建 Statement 或 PrepareStatement 对象
        stmt = handler.prepare(connection, transaction.getTimeout());
        // 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
        handler.parameterize(stmt);
        return stmt;
    }
 /**
     * 获得 Connection 对象
     *
     * @throws SQLException 获得失败
     */
    protected void openConnection() throws SQLException {
        if (log.isDebugEnabled()) {
            log.debug("Opening JDBC Connection");
        }
        // 获得连接
        connection = dataSource.getConnection();
        // 设置隔离级别
        if (level != null) {
            connection.setTransactionIsolation(level.getLevel());
        }
        // 设置 autoCommit 属性
        setDesiredAutoCommit(autoCommit);
    }
上述的 Executor.query() ⽅法⼏经转折,最后会创建⼀个 StatementHandler 对象,然后将必要的参数传 递给StatementHandler ,使⽤ StatementHandler 来完成对数据库的查询,最终返回 List 结果集。 从上⾯的代码中我们可以看出, Executor 的功能和作⽤是:
(1 、根据传递的参数,完成 SQL 语句的动态解析,⽣成 BoundSql 对象,供 StatementHandler 使⽤;
(2 、为查询创建缓存,以提⾼性能
(3 、创建 JDBC Statement 连接对象,传递给 *StatementHandler* 对象,返回 List 查询结果。

三. 源码剖析-StatementHandler

StatementHandler 对象主要完成两个⼯作:
对于 JDBC PreparedStatement 类型的对象,创建的过程中,我们使⽤的是 SQL 语句字符串会包含若⼲个?占位符,我们其后再对占位符进⾏设值。 StatementHandler 通过 parameterize(statement) ⽅法 Statement 进⾏设值;
StatementHandler 通过 List query(Statement statement, ResultHandler resultHandler) ⽅法来 完成执⾏Statement ,和将 Statement 对象返回的 resultSet 封装成 List
进⼊到 PrepareStatementHandler parameterize(statement) ⽅法的实现:
    @Override
    public void parameterize(Statement statement) throws SQLException {
        //使用ParameterHandler对象来完成对Statement的设值
        parameterHandler.setParameters((PreparedStatement) statement);
    }
/** ParameterHandler 类的 setParameters(PreparedStatement ps) 实现
 * 对某⼀个Statement进⾏设置参数
 * */
@SuppressWarnings("Duplicates")
    @Override
    public void setParameters(PreparedStatement ps) {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        // 遍历 ParameterMapping 数组
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
            for (int i = 0; i < parameterMappings.size(); i++) {
                // 获得 ParameterMapping 对象
                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、jdbcType 属性
                    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);
                    }
                }
            }
        }
    }
从上述的代码可以看到 ,StatementHandler parameterize(Statement) ⽅法调⽤了
ParameterHandler setParameters(statement) ⽅法,
ParameterHandler setParameters(Statement ) ⽅法负责根据我们输⼊的参数,对 statement 对象的 ? 占位符处进⾏赋值。
进⼊到 StatementHandler List query(Statement statement, ResultHandler resultHandler) ⽅法的 实现:
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws
SQLException {
 // 1.调⽤preparedStatemnt。execute()⽅法,然后将resultSet交给ResultSetHandler处理
 PreparedStatement ps = (PreparedStatement) statement;
 ps.execute();
 
 //2.使⽤ ResultHandler 来处理 ResultSet
 return resultSetHandler.<E> handleResultSets(ps);
}
从上述代码我们可以看出, StatementHandler List query(Statement statement, ResultHandler
resultHandler) ⽅法的实现,是调⽤了 ResultSetHandler handleResultSets(Statement) ⽅法。
ResultSetHandler handleResultSets(Statement) ⽅法会将 Statement 语句执⾏后⽣成的 resultSet 结 果集转换成 List 结果集:
//
    // HANDLE RESULT SETS
    //
    // 处理 {@link java.sql.ResultSet} 结果集

    @Override
    public List<Object> handleResultSets(Statement stmt) throws SQLException {
        ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

        // 多 ResultSet 的结果集合,每个 ResultSet 对应一个 Object 对象。而实际上,每个 Object 是 List<Object> 对象。
        // 在不考虑存储过程的多 ResultSet 的情况,普通的查询,实际就一个 ResultSet ,也就是说,multipleResults 最多就一个元素。
        final List<Object> multipleResults = new ArrayList<>();

        int resultSetCount = 0;
        // 获得首个 ResultSet 对象,并封装成 ResultSetWrapper 对象
        ResultSetWrapper rsw = getFirstResultSet(stmt);

        // 获得 ResultMap 数组
        // 在不考虑存储过程的多 ResultSet 的情况,普通的查询,实际就一个 ResultSet ,也就是说,resultMaps 就一个元素。
        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        int resultMapCount = resultMaps.size();
        validateResultMapsCount(rsw, resultMapCount); // 校验
        while (rsw != null && resultMapCount > resultSetCount) {
            // 获得 ResultMap 对象
            ResultMap resultMap = resultMaps.get(resultSetCount);
            // 处理 ResultSet ,将结果添加到 multipleResults 中
            handleResultSet(rsw, resultMap, multipleResults, null);
            // 获得下一个 ResultSet 对象,并封装成 ResultSetWrapper 对象
            rsw = getNextResultSet(stmt);
            // 清理
            cleanUpAfterHandlingResultSet();
            // resultSetCount ++
            resultSetCount++;
        }

        // 因为 `mappedStatement.resultSets` 只在存储过程中使用,本系列暂时不考虑,忽略即可
        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 单元素,则取首元素返回
        return collapseSingleResultList(multipleResults);
    }

节选自拉钩教育JAVA系列教程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值