博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
mybatis sqlSession的运行过程
阅读量:5900 次
发布时间:2019-06-19

本文共 5617 字,大约阅读时间需要 18 分钟。

当我们使用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所封装的一些信息了

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

clipboard.png

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

.clipboard.png

然后调用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所包含的信息,可以看到包含了很多东西

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

转载地址:http://tiesx.baihongyu.com/

你可能感兴趣的文章
基于udp的scoket通信
查看>>
053(四十二)
查看>>
053(六十二)
查看>>
053(六十六)
查看>>
一些好用的库 paramiko
查看>>
(转载)屌丝从毕业时的月入3000到三年后亿万身家的精彩励志之旅
查看>>
大白话5分钟带你走进人工智能-目录
查看>>
iOS推送消息报错误“Domain=NSCocoaErrorDomain Code=3000”的可能问题
查看>>
JavaScript获取页面宽高度的方法
查看>>
找不到方法:“Boolean System.Runtime.Serialization.DataContractAttribute.get_IsReference()”
查看>>
eclipse启动时提示"Failed to load the jni shared library"
查看>>
企业级 SpringCloud 教程 (三) 服务消费者(Feign)
查看>>
Mysql基础知识--触发器
查看>>
配置sshkeys(GitHub)
查看>>
关于海明距离和编辑距离
查看>>
Android开发指南(30) —— Multimedia and Camera
查看>>
kvm-1
查看>>
SQL Server中查询某列所在的数据库中所在的表
查看>>
ros在QT下编程
查看>>
Jmeter的接口测试简介
查看>>