mybatis sqlSession的运行过程

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

这段是MappedProxyFactory中的一段代码

protected T newInstance(MapperProxy<T> 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所封装的一些信息了
clipboard.png

再接着,当我们调用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<Method,MapperMethod>进行缓存                        
            MapperMethod mapperMethod = this.cachedMapperMethod(method);
            return mapperMethod.execute(this.sqlSession, args);
        }
    }

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

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

private <E> 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,如果是基本数据类型(单个值),那么会被转换成包装数据类型

clipboard.png

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

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

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

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

从这边可以看出在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所包含的信息,可以看到包含了很多东西

clipboard.png

再来看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对结果进行包装。并且查询完结果后,会将结果放到缓存中去

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值