mybatis源码解析(三)-SqlSession.selectOne类似方法调用过程

♚java♚ 同时被 2 个专栏收录
39 篇文章 0 订阅
7 篇文章 1 订阅

mybatis源码解析(一)-开篇
mybatis源码解析(二)-加载过程
mybatis源码解析(三)-SqlSession.selectOne类似方法调用过程
mybatis源码解析(四)-Mapper方法调用过程
mybatis源码解析(五)-mybatis如何实现的事务控制
mybatis源码解析(六)-配合spring-tx实现事务的原理
mybatis源码解析(七)-当mybatis一级缓存遇上spring

转载请标明出处:
http://blog.csdn.net/bingospunky/article/details/79202721
本文出自马彬彬的博客

上篇博客中介绍了mybatis的加载过程,这篇博客介绍一下org.apache.ibatis.session.SqlSession的增、删、改、查方法是怎么实现的。我们使用mybatis时,基本都是使用Mapper进行增、删、改、查操作,但是SqlSession也给了我们方法来完成相似的功能。从源码的角度去看,Mapper调用了SqlSession,所以研究SqlSession是很有必要的,且是研究Mapper的基础。

org.apache.ibatis.session.SqlSession.selectOne方法

使用org.apache.ibatis.session.SqlSession.selectOne类似方法时,主要过程如下面代码:

Code 1

    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        List var5;
        try {
            MappedStatement ms = this.configuration.getMappedStatement(statement);
            var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception var9) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var9, var9);
        } finally {
            ErrorContext.instance().reset();
        }
        return var5;
    }

第4行,就从org.apache.ibatis.session.Configuration里的protected final Map

    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
        return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

第2行就是通过MappedStatement还有查询参数来生成BoundSql。BoundSql就是对sql的包装,包含sql语句,参数,List(参数的mapping)。生成BoundSql的核心代码如下:

Code 3

    public BoundSql getBoundSql(Object parameterObject) {
        DynamicContext context = new DynamicContext(this.configuration, parameterObject);
        this.rootSqlNode.apply(context);
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(this.configuration);
        Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
        SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        Iterator i$ = context.getBindings().entrySet().iterator();
        while(i$.hasNext()) {
            Entry<String, Object> entry = (Entry)i$.next();
            boundSql.setAdditionalParameter((String)entry.getKey(), entry.getValue());
        }
        return boundSql;
    }

生成BoundSql的过程是比较复杂的。第2行构造DynamicContext,并且把参数传递进去,第3行把sql传递进DynamicContext,DynamicContext中的sql语句是包含#{xxx}的。第6行生成SqlSource,SqlSource是个接口,只有一个方法BoundSql getBoundSql(Object var1);,所以SqlSource就是来生成BoundSql的,SqlSource里包含sql和private List parameterMappings;,这里的sql就把占位符换成了,ParameterMapping的个数和占位符一样多,ParameterMapping这个类包含了传递进去的参数是如何解析的,就像我们写sql时,#{xxx}里面会加上jdbcType等属性。第7行通过sqlSource和传递进来的参数(Map)生成BoundSql,这一行的代码比较简单,直接new一个BoundSql就行,因为BoundSql需要的内容已经在上面几行都凑出来了。其余代码忽略。

再看Code 2,第3行是缓存相关的,我们先忽略掉缓存相关的,后面也会先忽略掉缓存。第4行执行sql的代码如下:

Code 4

    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        List var9;
        try {
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            stmt = this.prepareStatement(handler, ms.getStatementLog());
            var9 = handler.query(stmt, resultHandler);
        } finally {
            this.closeStatement(stmt);
        }
        return var9;
    }

第5、6行没有复杂逻辑,忽略掉;第7行是生成Statement;第8行是执行Statement,然后解析ResultSet生成对应的Bean。第7、8行的内容是很复杂的,有很多的细节,后面也只是记录主要的过程,第7行生成Statement的主要代码如下:

Code 5

    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Connection connection = this.getConnection(statementLog);
        Statement stmt = handler.prepare(connection);
        handler.parameterize(stmt);
        return stmt;
    }

获取Statement的过程分为两步:第一步通过sql(包含?的)创建PreparedStatement,代码第3行所完成的;第二部给这个Statement设置参数,代码第4行所完成的,设置参数的过程大体上就是遍历List parameterMappings,在传递进来的参数中获取到适当的参数,然后设置到Statement中。这儿可以关注一点,给Statement设置参数的时候需要根据不同的参数类型调用不同的setXXX方法,这是根据参数的Java类型和JdbcType找到合适的TypeHadler,然后使用TypeHadler给Statement参数赋值。关于TypeHadler具体描述可以参考后面内容。

Code 4中第8行执行Statement的代码就一句话,和解析结果的代码是分开的,这里不贴执行Statement的代码了,解析ResultSet的主要代码如下:

Code 6

public List<Object> handleResultSets(Statement stmt) throws SQLException {
        ErrorContext.instance().activity("handling results").object(this.mappedStatement.getId());
        List<Object> multipleResults = new ArrayList();
        int resultSetCount = 0;
        ResultSetWrapper rsw = this.getFirstResultSet(stmt);
        List<ResultMap> resultMaps = this.mappedStatement.getResultMaps();
        int resultMapCount = resultMaps.size();
        this.validateResultMapsCount(rsw, resultMapCount);
        while(rsw != null && resultMapCount > resultSetCount) {
            ResultMap resultMap = (ResultMap)resultMaps.get(resultSetCount);
            this.handleResultSet(rsw, resultMap, multipleResults, (ResultMapping)null);
            rsw = this.getNextResultSet(stmt);
            this.cleanUpAfterHandlingResultSet();
            ++resultSetCount;
        }
        String[] resultSets = this.mappedStatement.getResulSets();
        if (resultSets != null) {
            while(rsw != null && resultSetCount < resultSets.length) {
                ResultMapping parentMapping = (ResultMapping)this.nextResultMaps.get(resultSets[resultSetCount]);
                if (parentMapping != null) {
                    String nestedResultMapId = parentMapping.getNestedResultMapId();
                    ResultMap resultMap = this.configuration.getResultMap(nestedResultMapId);
                    this.handleResultSet(rsw, resultMap, (List)null, parentMapping);
                }
                rsw = this.getNextResultSet(stmt);
                this.cleanUpAfterHandlingResultSet();
                ++resultSetCount;
            }
        }
        return this.collapseSingleResultList(multipleResults);
    }

第5行构造了ResultSetWrapper,ResultSetWrapper是对ResultSet的包装,生成了一些column的信息,有column的name、jdbcType、className。
第6行获取了List resultMaps,一般就一个,一般可以再xxxMapper.xml文件的resultMap配置。
第9行的循环次数是List resultMaps的个数,不是结果集的个数,这里不要搞混了。
第11行实现从ResultSet到Bean的转化,转化一个Bean的核心代码如下:

Code 7

    private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
        ResultLoaderMap lazyLoader = new ResultLoaderMap();
        Object resultObject = this.createResultObject(rsw, resultMap, lazyLoader, (String)null);
        if (resultObject != null && !this.typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
            MetaObject metaObject = this.configuration.newMetaObject(resultObject);
            boolean foundValues = !resultMap.getConstructorResultMappings().isEmpty();
            if (this.shouldApplyAutomaticMappings(resultMap, false)) {
                foundValues = this.applyAutomaticMappings(rsw, resultMap, metaObject, (String)null) || foundValues;
            }
            foundValues = this.applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, (String)null) || foundValues;
            foundValues = lazyLoader.size() > 0 || foundValues;
            resultObject = foundValues ? resultObject : null;
            return resultObject;
        } else {
            return resultObject;
        }
    }

第3行创建一个对象,属性都为空。
第10行给这个对象属性赋值,赋值的过程就是根据org.apache.ibatis.mapping.resultMapList propertyMappings,遍历List propertyMappings,在ResultSet里获取相应的属性,获取时使用的是TypeHandler获取的,ResultMapping可以获取到TypeHandler,然后调用Bean适当的方法设置值,setXXX是使用反射调用的。

外层代码循环处理ResultSet,对于生成的每个Bean收集到org.apache.ibatis.executor.result.DefaultResultHandlerDefaultResultHandler中的list就包含了结果集中生成的所有Bean,这个list又被外层的List包含,外层的List对应的是不同的ResultMap解析出来的Bean集合,一般配置一个ResultMap,所以外层List就一个元素,至于什么情况下配置两个ResultMap我还没有见到。

至此,使用org.apache.ibatis.session.selectOne查询的过程就已经描述完了。

org.apache.ibatis.session.SqlSession.update方法

该方法也是在SqlSession上直接执行的,不需要Mapper。这个方法执行增、删、改的sql。
该方法的执行过程与上述org.apache.ibatis.session.SqlSession.selectOne的过程基本一致,他们执行到PreparedStatement.execute基本都是一样的,只是selectOne方法后面是解析ResultSet,而update方法是获取变更的记录数。

org.apache.ibatis.session.SqlSession参数

org.apache.ibatis.session.SqlSession系列方法,传递参数的话只能传递一个值,这个值可以是:单个对象、集合类型(Collection、Array)、Map。那么框架是如何支持他们的呢?原因是:框架包含org.apache.ibatis.reflection.MetaObject类和org.apache.ibatis.reflection.wrapper包下的一些XXXWrapper完成的。

关于TypeHandler

org.apache.ibatis.type.TypeHandlerRegistry这个类里注册是很多个org.apache.ibatis.type.TypeHandler,TypeHandler的功能就是:在执行sql时把参数以适当类型设置给PreparedStatement,在解析ResultSet时把执行的列转化为对应的类型。

org.apache.ibatis.type.TypeHandlerRegistryTypeHandler存放在一个两层的Map中,外层Map的key为Java Class,value为一个内层Map;内层Map的key为org.apache.ibatis.type.JdbcType(这是个枚举类型),value为对应的Handler。

  • 7
    点赞
  • 0
    评论
  • 14
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值