mybatis源码解析-如何执行查询操作

//1.通过输入流解析xml配置文件
InputStream inputstream = Resources.getResourceAsStream("xxx.xml")
SqlsessionFactory sqlsessionfactory = new SqlsessionFactoryBuilder().build(inputstream);
//2.获取和数据库的链接,创建会话
SqlSession openSession = sqlsessionfactory.openSession();
//3.获取接口的实现类对象,会为接口自动的创建一个代理对象mapper,代理对象会去执行增删改查方法
xxxMapper mapper = openSession.getMapper(xxxMapper.class)
//4.执行增删改的方法

mybatis源码解析-SqlsessionFactory
mybatis源码解析-获取Sqlsession
mybatis源码解析-getMapper
一个mybatis运行前的步骤我们都分析的差不多了,现在来看一下具体的执行过程

查询如何实现

我们假如有一个方法叫做getXXXById(),我们便顺理成章的进入了这个方法内部,我们之前提到过,openSession.getMapper(xxxMapper.class)返回的是MapperProxy对象,也就是mapper的代理对象,我们看下代码:
1.1

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }

            if (this.isDefaultMethod(method)) {
                return this.invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }

        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }

首先这个invoke方法会先做判断:if (Object.class.equals(method.getDeclaringClass()))它的意思是判断通过代理对象执行的方法是不是Object方法,或者是不是mapper接口中的方法,如果是Object的方法比如toString就直接执行下面的代码了。入如果是mapper接口中的方法,则会将当前方法通过MapperMethod mapperMethod = this.cachedMapperMethod(method);封装成MapperMethod,然后通过mapperMethod.execute(this.sqlSession, args);来执行,这个参数中的sqlsession是我们MapperProxy封装好了的,Object[] args是我们查询方法的参数,我们来看一下MapperMethod这个类:
1.2

public class MapperMethod {
    private final MapperMethod.SqlCommand command;
    private final MapperMethod.MethodSignature method;

    public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
        this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
        this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
    }

这是这个类的基本信息,其中MapperMethod.SqlCommand command是这个类中的静态内部类,在构造方法中就进行了解析,目的是判断要运行的方法是增删改查的那种类型,然后看这个类中的execute方法:

 public Object execute(SqlSession sqlSession, Object[] args) {
        Object param;
        Object result;
        switch(this.command.getType()) {
        case INSERT:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case DELETE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case SELECT:
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else if (this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
                param = this.method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(this.command.getName(), param);
            }
            break;

我们要运行的是根据id查询对象,所以是select类型的,进入了select之后会根据返回值类型进行判断,我们的情况都不满足,直接进入else语句,这里面的method.convertArgsToSqlCommandParam(args)是将Object类型的参数转为我们需要的类型,我们不妨细看一下:
在这里插入图片描述
看到这里是不是很熟悉,和mybatis参数处理过程及原理里面讲得一样,这里就不多说了,参数转换完成后,就会执行sqlSession.selectOne(this.command.getName(), param);这个方法,其中参数this.command.getName() 就是我们在mapper.xml中写的select标签的唯一标识,即方法所在接口的全限定类名,我们进入这个selectOne方法
1.3这个方法是DefaultSqlSession实现的

public <T> T selectOne(String statement, Object parameter) {
        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;
        }
    }

我们发现,无论是查询单个还是多个,都调用了selectList方法,我们来看一看这个方法,参数statement是我们对应mapper接口中的一个方法,有唯一标识,parameter是方法的参数
1.4

public <E> List<E> selectList(String statement, Object parameter) {
        return this.selectList(statement, parameter, RowBounds.DEFAULT);
    }

    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;
    }

这里需要注意了,MappedStatement ms = this.configuration.getMappedStatement(statement); 就是根据configuration全局配置对象来获取之前被封装的MappedStatement对象,而这个对象正是封装了增删改查标签中的信息,包括我们配置的SQL,SQL的id,缓存信息,resultMap等,每个标签都有一个该对象封装,这个在本票文章开始的mybatis源码解析-SqlsessionFactory有详细介绍,每个增删改查标签都有一个封装它的MappedStatement对象,他们通过map封装,这里的selectList方法通过唯一标识statement找到了对应的接口方法
我们接着往下走,
完成了MappedStatement的查询,会将这个作为参数传递给this.executor.query方法,我们来看一下这个方法:其中有个参数this.wrapCollection(parameter),我们看一下这个封装参数的方法:

private Object wrapCollection(Object object) {
        DefaultSqlSession.StrictMap map;
        if (object instanceof Collection) {
            map = new DefaultSqlSession.StrictMap();
            map.put("collection", object);
            if (object instanceof List) {
                map.put("list", object);
            }

            return map;
        } else if (object != null && object.getClass().isArray()) {
            map = new DefaultSqlSession.StrictMap();
            map.put("array", object);
            return map;
        } else {
            return object;
        }
    }

是不是有一种豁然开朗的感觉?是的,就是这段代码的作用我们才可以将List类型的集合封装为list或者是collection,好了,现在跳过这段代码,接着往下走,看query方法,注意了,这里的this.executor.query是我们在获取SqlSession步骤中创建Executor时的Executor子类CachingExecutor
1.5

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);
    }

注意第一句的BoundSql,我们进入ms.getBoundSql看一下:
在这里插入图片描述
这个类提供三个主要的属性:sql,parameterMappings,parameterObject,我们这里简单介绍一下:

  • sql就是我们卸载增删改标签里的sql语句
  • parameterObject就是我们写在接口里方法的参数
  • parameterMappings,它是一个List,它的每一个参数都是ParameterMapping对象,这个对象会描述我们的参数,包括属性,名称,表达式,javatype等重要信息,我们一般不需要去改变它,通过它可以实现参数和SQL的结合

回到query方法,里面还有一个二级缓存key:CacheKey key,这里就不多说它了,然后它再次调用本类中的另一个重载的query方法:
1.6

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) {
            this.flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
                this.ensureNoOutParams(ms, boundSql);
                List<E> list = (List)this.tcm.getObject(cache, key);
                if (list == null) {
                    list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    this.tcm.putObject(cache, key, list);
                }

                return list;
            }
        }

        return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

在之前介绍获取sqlsession的文章中我们提到,通过openSession创建的Executor也就是executor = new SimpleExecutor(this, transaction);这段代码被executor = new CachingExecutor((Executor)executor);封装了,真正起作用的是CachingExecutor类中的delegate,如果没有缓存,这个执行器进行查询this.delegate.query,我们进入这个query方法:

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 (this.closed) {
            throw new ExecutorException("Executor was closed.");
        } else {
            if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
                this.clearLocalCache();
            }

            List list;
            try {
                ++this.queryStack;
                list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
                if (list != null) {
                    this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
                } else {
                    list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
                }
            } finally {
                --this.queryStack;
            }

            if (this.queryStack == 0) {
                Iterator var8 = this.deferredLoads.iterator();

                while(var8.hasNext()) {
                    BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();
                    deferredLoad.load();
                }

                this.deferredLoads.clear();
                if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                    this.clearLocalCache();
                }
            }

            return list;
        }
    }

我们发现会执行**list = resultHandler == null ? (List)this.localCache.getObject(key) : null;**这行代码,这是执行了二级缓存之后再执行的本地缓存,这也应证了mybatis会先查二级,再查一级缓存,如果一级二级都为null,就会执行queryFromDatabase方法,我们进入这个方法:
1.7

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);

        List list;
        try {
            list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            this.localCache.removeObject(key);
        }

        this.localCache.putObject(key, list);
        if (ms.getStatementType() == StatementType.CALLABLE) {
            this.localOutputParameterCache.putObject(key, parameter);
        }

        return list;
    }

这个方法会现在本地缓存中设置一个key,再查出数据放进本地缓存,我们看doQuery方法,这个方法的参数我们都很熟悉了,直接进入该方法了
1.8

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;
    }

这个方法就很有意思了,里面有Statement原生JDBC,而且出现了StatementHandler,这是SqlSession下的一个重要的对象,我们之前说的Executor也是一个,它的作用是创建出statement(prepareStatement)对象,起到承上启下的作用,我们进入这个方法看一下:
1.9

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        StatementHandler statementHandler = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
        return statementHandler;
    }

我们在mapper中写select标签时有一个属性:
在这里插入图片描述
默认采取预编译模式,那么在这个方法中就会创建PreparedStatementHandler它是StatementHandler的子类
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章