本博客着重介绍MyBatis执行Sql的流程,关于在执行过程中缓存、动态SQl生成等细节不在本博客中体现,相应内容后面再单独写博客分析吧。

还是以之前的查询作为列子:

public class UserDaoTest {

    private SqlSessionFactory sqlSessionFactory;

    @Before
public void setUp() throws Exception{
ClassPathResource resource = new ClassPathResource("mybatis-config.xml");
InputStream inputStream = resource.getInputStream();
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} @Test
public void selectUserTest(){
String id = "{0003CCCA-AEA9-4A1E-A3CC-06D884BA3906}";
SqlSession sqlSession = sqlSessionFactory.openSession();
CbondissuerMapper cbondissuerMapper = sqlSession.getMapper(CbondissuerMapper.class);
Cbondissuer cbondissuer = cbondissuerMapper.selectByPrimaryKey(id);
System.out.println(cbondissuer);
sqlSession.close();
} }

之前提到拿到sqlSession之后就能进行各种CRUD操作了,所以我们就从sqlSession.getMapper这个方法开始分析,看下整个Sql的执行流程是怎么样的。

获取Mapper

进入sqlSession.getMapper方法,会发现调的是Configration对象的getMapper方法:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//mapperRegistry实质上是一个Map,里面注册了启动过程中解析的各种Mapper.xml
//mapperRegistry的key是接口的全限定名,比如com.csx.demo.spring.boot.dao.SysUserMapper
//mapperRegistry的Value是MapperProxyFactory,用于生成对应的MapperProxy(动态代理类)
return mapperRegistry.getMapper(type, sqlSession);
}

进入getMapper方法:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
//如果配置文件中没有配置相关Mapper,直接抛异常
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//关键方法
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}

进入MapperProxyFactory的newInstance方法:

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>(); public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
} public Class<T> getMapperInterface() {
return mapperInterface;
} public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
} //生成Mapper接口的动态代理类MapperProxy
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
} public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
} }

下面是动态代理类MapperProxy,调用Mapper接口的所有方法都会先调用到这个代理类的invoke方法(注意由于Mybatis中的Mapper接口没有实现类,所以MapperProxy这个代理对象中没有委托类,也就是说MapperProxy干了代理类和委托类的事情)。好了下面重点看下invoke方法。

//MapperProxy代理类
public class MapperProxy<T> implements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//获取MapperMethod,并调用MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
} private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
} @UsesJava7
private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
throws Throwable {
final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
.getDeclaredConstructor(Class.class, int.class);
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
final Class<?> declaringClass = method.getDeclaringClass();
return constructor
.newInstance(declaringClass,
MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
.unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
} /**
* Backport of java.lang.reflect.Method#isDefault()
*/
private boolean isDefaultMethod(Method method) {
return ((method.getModifiers()
& (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC)
&& method.getDeclaringClass().isInterface();
}
}

所以这边需要进入MapperMethod的execute方法:

public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//判断是CRUD那种方法
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}

然后,通过一层一层的调用,最终会来到doQuery方法, 这儿咱们就随便找个Excutor看看doQuery方法的实现吧,我这儿选择了SimpleExecutor:

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
//内部封装了ParameterHandler和ResultSetHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
//StatementHandler封装了Statement, 让 StatementHandler 去处理
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}

接下来,咱们看看StatementHandler 的一个实现类 PreparedStatementHandler(这也是我们最常用的,封装的是PreparedStatement), 看看它使怎么去处理的:

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//到此,原形毕露, PreparedStatement, 这个大家都已经滚瓜烂熟了吧
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
//结果交给了ResultSetHandler 去处理,处理完之后返回给客户端
return resultSetHandler.<E> handleResultSets(ps);
}

到此,整个调用流程结束。

简单总结

这边结合获取SqlSession的流程,做下简单的总结:

  • SqlSessionFactoryBuilder解析配置文件,包括属性配置、别名配置、拦截器配置、环境(数据源和事务管理器)、Mapper配置等;解析完这些配置后会生成一个Configration对象,这个对象中包含了MyBatis需要的所有配置,然后会用这个Configration对象创建一个SqlSessionFactory对象,这个对象中包含了Configration对象;
  • 拿到SqlSessionFactory对象后,会调用SqlSessionFactory的openSesison方法,这个方法会创建一个Sql执行器(Executor组件中包含了Transaction对象),这个Sql执行器会代理你配置的拦截器方法
  • 获得上面的Sql执行器后,会创建一个SqlSession(默认使用DefaultSqlSession),这个SqlSession中也包含了Configration对象和上面创建的Executor对象,所以通过SqlSession也能拿到全局配置;
  • 获得SqlSession对象后就能执行各种CRUD方法了。

以上是获得SqlSession的流程,下面总结下本博客中介绍的Sql的执行流程:

  • 调用SqlSession的getMapper方法,获得Mapper接口的动态代理对象MapperProxy,调用Mapper接口的所有方法都会调用到MapperProxy的invoke方法(动态代理机制);
  • MapperProxy的invoke方法中唯一做的就是创建一个MapperMethod对象,然后调用这个对象的execute方法,sqlSession会作为execute方法的入参;
  • 往下,层层调下来会进入Executor组件(如果配置插件会对Executor进行动态代理)的query方法,这个方法中会创建一个StatementHandler对象,这个对象中同时会封装ParameterHandler和ResultSetHandler对象。调用StatementHandler预编译参数以及设置参数值,使用ParameterHandler来给sql设置参数。

Executor组件有两个直接实现类,分别是BaseExecutor和CachingExecutor。CachingExecutor静态代理了BaseExecutor。Executor组件封装了Transction组件,Transction组件中又分装了Datasource组件。

  • 调用StatementHandler的增删改查方法获得结果,ResultSetHandler对结果进行封装转换,请求结束。

Executor、StatementHandler 、ParameterHandler、ResultSetHandler,Mybatis的插件会对上面的四个组件进行动态代理。

重要类

  • MapperProxyFactory

  • MapperProxy

  • MapperMethod

  • SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能;

  • Executor:MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护;

    StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。

    ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所需要的参数,

    ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;

    TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换

    MappedStatement MappedStatement维护了一条<select|update|delete|insert>节点的封装,

    SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回

    BoundSql 表示动态生成的SQL语句以及相应的参数信息

    Configuration MyBatis所有的配置信息都维持在Configuration对象之中。

参考

02-MyBatis执行Sql的流程分析的更多相关文章

  1. Mybatis执行SQL的流程

    前篇:Mybatis初始化过程 SqlSession : SqlSession是一个接口,它有两个实现类:DefaultSqlSession (默认)和 SqlSessionManager (弃用,不 ...

  2. Springboot中mybatis执行逻辑源码分析

    Springboot中mybatis执行逻辑源码分析 在上一篇springboot整合mybatis源码分析已经讲了我们的Mapper接口,userMapper是通过MapperProxy实现的一个动 ...

  3. log4j打印mybatis执行sql,将占位符换成真实的参数输出

    背景: 在我日常码代码的时候,由于对mybatis的动态sql,比较依赖,并且有时候需求复杂,导致sql较长,而且参数众多,当出现问题是,需要将sql,放到navicat里面去执行查看结果,但是对于复 ...

  4. 在mybatis执行SQL语句之前进行拦击处理

    转载自:http://blog.csdn.net/hfmbook/article/details/41985853 比较适用于在分页时候进行拦截.对分页的SQL语句通过封装处理,处理成不同的分页sql ...

  5. Mybatis执行sql(insert、update、delete)返回值问题

    数据库:Mysql 在使用mybatis的过程中对执行sql的返回值产生疑问,顺手记录一下. 结论: insert:   插入n条记录,返回影响行数n.(n>=1,n为0时实际为插入失败) up ...

  6. Spark修炼之道(高级篇)——Spark源代码阅读:第十二节 Spark SQL 处理流程分析

    作者:周志湖 以下的代码演示了通过Case Class进行表Schema定义的样例: // sc is an existing SparkContext. val sqlContext = new o ...

  7. Mybatis执行SQL的完整过程及四大组件介绍

    一切的执行从MapperProxy开始,MapperProxy是MapperProxyFactory使用SqlSession创建出来的.所以MapperProxy中包含SqlSession. 可以看到 ...

  8. 使用Mybatis执行sql脚本

    pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="htt ...

  9. MyBatis动态SQL底层原理分析 与 JavaScript中的Date对象,以及UTC、GMT、时区的关系

    http://fangjian0423.github.io/categories/mybatis/ http://xtutu.me/the-date-object-in-js/

随机推荐

  1. python入门之jieba库的使用

    对于一段英文,如果希望提取其中的的单词,只需要使用字符串处理的split()方法即可,例如“China is a great country”.   然而对于中文文本,中文单词之间缺少分隔符,这是中文 ...

  2. C# .Net Framework

    .NET Framework概述 .NET Framework是由微软开发,一个致力于敏捷软件开发.快速应用开发.平台无关性和网络透明化的软件开发平台. .NET Framework组成 .NET F ...

  3. Vue-cli中axios传参的方式以及后端取的方式

    0917自我总结 Vue-cli中axios传参的方式以及后端取的方式 一.传参 params是添加到url的请求字符串中的,用于get请求. data是添加到请求体(body)中的, 用于post请 ...

  4. 从实践角度重新理解BIO和NIO

    前言 这段时间自己在看一些Java中BIO和NIO之类的东西,看了很多博客,发现各种关于NIO的概念说的天花乱坠头头是道,可以说是非常的完整,但是整个看下来之后,自己对NIO还是一知半解的状态,所以这 ...

  5. sqli-labs靶机注入笔记1-10关

    嗯,开始记录sqli-lab的每关笔记,复习一次 1-2关 基于错误的字符串/数字型注入 闭合的符号有区别而已 http://www.sqli-lab.cn/Less-1/?id=1 or 1=1 - ...

  6. windows 7 专业版 64位 无法安装.Net 4.7版本解决方案

    什么暂停windows update,改文件夹,再恢复windows update等等方法都试了,不行之后就觉得这才是终极解决方案. 不管你是win 7什么版本,你只需要下载安装对应系统的Window ...

  7. Cocos2d-x 学习笔记(8) ActionManager

    1. 概述 ActionManager管理所有的action,调度所有的action,删除指定的action.每个action对应一个node对象,action存储在actions中,actions和 ...

  8. C# 结合 Golang 开发

    1. 实现方式与语法形式 基本方式:将 Go 程序编译成 DLL 供 C# 调用. 1.1 Go代码 注意:代码中 export 的注释是定义的入口描述不能省略 package main import ...

  9. Vue路由守卫(跳转页面置顶的处理方)

    在用Vue 框架开发时,在电脑调试没有任何问题,但是用手机调试时会发现页面跳转的不对.就是跳转时页面展示的滑动位置不对,会保留上次跳转页面时的跳转位置.因此需要对页面的路由跳转进行优化,需要用到Vue ...

  10. 基于mysql-8.0.16-winx64的主从搭建

    1.主服务器的my.ini文件内容:[mysqld]# 主库和从库需要不一致server-id=1log-bin=mysql-bin# 同步的数据库binlog-do-db=master-slave# ...