资讯专栏INFORMATION COLUMN

mybatis sqlSession的运行过程

BoYang / 2027人阅读

摘要:在找那个可以根据中的属性,确定类的全限定类名。并且根据节点所对应的,找到所要执行的方法。因为他根据的是类名方法名进行唯一确定节点的。

当我们使用sqlSession.getMapper(xx.class)方法时,Mybatis其实是使用了jdk的动态代理技术,在MapperProxyFactory中生成对应的Mapper对象。

这段是MappedProxyFactory中的一段代码

protected T newInstance(MapperProxy mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

其中mapperProxy对应的是MapperProxy,该类实现了InvocationHanlder接口。
正如我们所知,JDK的动态代理是根据接口来生成代理对象的。在Mybatis找那个可以根据Mapper.xml中的namespace属性,确定类的全限定类名。并且根据节点(insert | select | update | delete)所对应的ID,找到所要执行的方法。也因此,Mybatis中的方法是不能重载的。因为他根据的是类名+方法名进行唯一确定MapperStatement(节点)的。

这个就是MapperStatement所封装的一些信息了

再接着,当我们调用Mapper对应的方法时,此时,会交给代理对象进行处理。
MapperProxy#的invoke

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(Object.class.equals(method.getDeclaringClass())) {
            try {
                return method.invoke(this, args);
            } catch (Throwable var5) {
                throw ExceptionUtil.unwrapThrowable(var5);
            }
        } else {
        //执行到这一步,如果方法是第一次调用,那么会创建对象,如果不是则使用缓存
        //需要注意的是调用的cacheMapperMethod方法,其实就是用一个Map进行缓存                        
            MapperMethod mapperMethod = this.cachedMapperMethod(method);
            return mapperMethod.execute(this.sqlSession, args);
        }
    }

紧接着,调用了MapperMethod#execute(this,sqlSession,args);
这个方法比较简单,就是根据节点的类型,进行相应的处理。比如节点是insert 那就走到insert的逻辑,其他类似了。。。

本人的节点类型是select,方法返回值是list,所以代码执行了这个方法

private  Object executeForMany(SqlSession sqlSession, Object[] args) {
        Object param = this.method.convertArgsToSqlCommandParam(args);
        List result;
        if(this.method.hasRowBounds()) {
            RowBounds rowBounds = this.method.extractRowBounds(args);
            result = sqlSession.selectList(this.command.getName(), param, rowBounds);
        } else {
            result = sqlSession.selectList(this.command.getName(), param);
        }

        return !this.method.getReturnType().isAssignableFrom(result.getClass())?(this.method.getReturnType().isArray()?this.convertToArray(result):this.convertToDeclaredCollection(sqlSession.getConfiguration(), result)):result;
    }

这里看到方法还执行了convertArgsToSqlCommandParam(args)方法,这个方法返回的对象值如下。其实说白了,就是对我们传进去参数的封装。需要注意到,这边其实是个Map对象,因为我的Mapper接口的方法是使用@Param注解的形式的。如果你传进去的是个POJO或者Map,那么这边就是POJO或者Map,如果是基本数据类型(单个值),那么会被转换成包装数据类型

中间的有些步骤就省略了。接下里的话,会将于需要执行的对象的方法,sql,sql参数创建一个缓存key
.

然后调用CacheExecutor#query,根据key查询有没有缓存,如果有缓存,直接从缓存中拿,如果没有,则继续执行。

期间会调用一个比较重要的方法 BaseExecutor#queryFromDatabase

private  List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);  //key其实就是之前提到过的CacheKey,value只是充当一个占位。。

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

下面就是比较关键的地方了
代码执行了simpleExecutor#doQuery

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

从这边可以看出在Executor内创建了StatementHandler,并对参数进行了预处理,而调用的handler.query()方法后,var9的值,就是我们所要查询的结果了。

值得一提的是StatementHandler有4个默认的实现类:
RoutingStatementHandler:这是一个封装类,不提供具体的实现,根据Executor的类型,创建不同的类型的StatementHandler
SimpleStatementHandler:这个类对应于JDBC的Statement对象,用于没有预编译参数的SQL的运行
PreparedStatementHandler:用于预编译参数SQL的运行
CallableStatementHandler:用于存储过程的调度

在newStatementHandler方法中,我们也可以看到是创建了RoutingStatementHandler对象,会根据具体的Executor类型,创建不同的StatementHandler。而这个具体的StatementHandler被存储在了RoutingStatementHandler的delegate属性中

先看configuration#newStatementHandler。注意到这边有个interceptorChain.pluginAll 这边就是用来执行插件的。

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

这个是所创建的StatementHandler所包含的信息,可以看到包含了很多东西

再来看simpleExecutor#prepareStatement

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Connection connection = this.getConnection(statementLog); // 在此处获取了数据的连接,此对象是个包装对象,包装了JDBC的Connection
        Statement stmt = handler.prepare(connection);  // 这边是进行一些预处理
        handler.parameterize(stmt);  
        return stmt;
    }

这边的调用的parameterize的方法就是对参数进行预处理了。其实就是遍历parameterMappings集合,然后从里面取出参数的属性,对参数进行处理,这个就是这个方法的逻辑。(注:parameterMappings 集合中存放的是传递进来的参数的属性)

当查询到结果到时候,会调用DefaultResultSetHandler对结果进行包装。并且查询完结果后,会将结果放到缓存中去

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67751.html

相关文章

  • 【深入浅出MyBatis笔记】MyBatis解析和运行原理

    摘要:的解析和运行原理构建过程提供创建的核心接口。在构造器初始化时会根据和的方法解析为命令。数据库会话器定义了一个对象的适配器,它是一个接口对象,构造器根据配置来适配对应的对象。它的作用是给实现类对象的使用提供一个统一简易的使用适配器。 MyBatis的解析和运行原理 构建SqlSessionFactory过程 SqlSessionFactory提供创建MyBatis的核心接口SqlSess...

    bitkylin 评论0 收藏0
  • MyBatis原理概括

    摘要:避免了几乎所有的代码和手动设置参数以及获取结果集。这个对象主要是获取方法对应的命令和执行相应操作等的处理,具体细节同学们可以抽空研究。所以这里的方法主要使用了和对象帮助我们处理语句集和参数的处理。 博文目标:希望大家看了这篇博文后,对Mybatis整体运行过程有一个清晰的认识和把握。 1.什么是 MyBatis ? MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程...

    mikasa 评论0 收藏0
  • Mybatis系列】从源码角度深度理解Mybatis缓存特性

    摘要:一级缓存介绍及相关配置。在这个章节,我们学习如何使用的一级缓存。一级缓存实验配置完毕后,通过实验的方式了解一级缓存的效果。源码分析了解具体的工作流程后,我们队查询相关的核心类和一级缓存的源码进行走读。 我,后端Java工程师,现在美团点评工作。爱健身,爱技术,也喜欢写点文字。个人网站: http://kailuncen.me公众号: KailunTalk (凯伦说) 前言 本文主要涉及...

    Ku_Andrew 评论0 收藏0
  • Mybatis系列】从源码角度深度理解Mybatis缓存特性

    摘要:一级缓存介绍及相关配置。在这个章节,我们学习如何使用的一级缓存。一级缓存实验配置完毕后,通过实验的方式了解一级缓存的效果。源码分析了解具体的工作流程后,我们队查询相关的核心类和一级缓存的源码进行走读。 我,后端Java工程师,现在美团点评工作。爱健身,爱技术,也喜欢写点文字。个人网站: http://kailuncen.me公众号: KailunTalk (凯伦说) 前言 本文主要涉及...

    young.li 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<