MyBatis概览(各组件以及底层实现原理等)

2019 Java 开发者跳槽指南.pdf (吐血整理)….>>>

一、MyBatis概览

MyBatis概览(各组件以及底层实现原理等)

这是Mybatis的整体架构图,可以看出它是由几个主要组件组成,分别为Configuration、Sql映射、Mapper、MappedStatements组成,Configuration包含了所有启动时的配置信息,包括mapper中方法映射SQL,数据源信息、对象工厂ObjectFactory和一些参数配置例如是否懒加载、是否开启缓存等等一系列信息,在接下来的动作或多或少都会用到Configuration里的数据。

MyBatis概览(各组件以及底层实现原理等)

Mybatis的组件分三大类

第一类SqlSession为主要调配者,主要工作为调度工作,类似委派模式的委派者。

第二类为调配者分发下去主要工作的类。例如Parsing负责解析各种配置文件、SQL Parsing负责解析SQL语句、ParameterMapping负责SQL语句参数的配置、Executor为SQL语句执行者,负责执行SQL语句、ResultSetMapping负责去映射结果集成POJO、Plugins负责拦截SQL语句,对SQL进行操作。

第三类为底层用到的技术或是辅助的配置信息。例如反射,在动态代理Mapper类时经常用到。例如Binding,其辅助Mapper的动态代理创建,以及Mapper与方法对应的SQL的绑定工作。例如类型转换,返回结果字段映射到指定POJO时也会用到。资源加载资源解析Parsing就是去加载配置解析配置,等等等等就不多赘述了。

二、深入底层分析Mybatis的实现原理

1
2
3
4
    public static void main(String[] args) throws FileNotFoundException {
        TestMapper testMapper = getSqlSession().getMapper(TestMapper.class);
        testMapper.selectByPrimaryKey(1);
    }

简单写了一个Demo,主要看一下SqlSession获取Mapper,再到Mapper执行一个接口方法到底底层做了什么。首先抛出一个问题,TestMapper是一个Mapper接口,并没有写具体的实现类,为什么可以执行接口的方法?带着问题看下去。

首先Debug运行,进入SqlSession的getMapper方法。

MyBatis概览(各组件以及底层实现原理等)

可以看到,SqlSession调用了他的Configuration去getMapper,实际是委派了Configuration去做这个工作。进入Configuration。

MyBatis概览(各组件以及底层实现原理等)

而Configuration又从mapperRegistry中去getMapper,看看mapperRegistry里有什么?

MyBatis概览(各组件以及底层实现原理等)

Registry里的knownMappers中存放了两个Mapper的信息,key为namespace,value为一个MapperProxyFactory,可以猜想,在启动时这个Map就已经初始化好了,帮我们装配好了所有的Mapper信息,在Configuration的getMapper时就去这里取Mapper。继续进入MapperRegistry看看吧。

MyBatis概览(各组件以及底层实现原理等)

这里可以看到,就像上面说的,去map根据接口类对象取出需要的MapperProxyFactory,此时如果你的Mapper文件没有注册到Configuration,就取不到对应的mapperProxyFactory,就会抛出找不到的异常。拿到这个proxyFactory之后,再调用它的newInstance方法去初始化一个mapper,继续进入。

MyBatis概览(各组件以及底层实现原理等)

到这里应该恍然大悟了,这个newInstance方法实际上做的是一个动态代理,传入实际mapper接口类对象,和一个mapperProxy,对mapper接口做一个代理,代理人为mapperProxy(需要有一定动态代理基础),来看看mapperProxy到底怎么去代理mapper?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
    }
 
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }
}

这里只保留了一些关键方法,可以看到MapperProxy持有一个sqlSession、一个mapper接口类对象,在invoke方法中,调用了mapperMethod的execute方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    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 SELECT:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = sqlSession.selectOne(this.command.getName(), param);
            break;
        }
    }

这里删掉了很多无关的东西,注意看这里,在执行时会判断SQL类型,为SELECT时sqlSession就会执行selectOne方法,到这里应该明白了,实际上SqlSession委托了Configuration去拿到Mapper信息,然后再用动态代理用MapperProxy类给mapper接口做代理,其每个方法都会用command判断SQL类型,例如SELECT就会回到SqlSession去做一个selectOne方法,归根结底还是回到了SqlSession,mapper只是提供了一个接口去做一个动态代理,携带SQL语句和参数回到SqlSession去执行查询。

SimpleExecutor

MyBatis概览(各组件以及底层实现原理等)

执行查询的工作实际上是交给了Executor去做,而Executor又委托StatementHandler去查询。

StatementHandler

MyBatis概览(各组件以及底层实现原理等)

在执行完语句之后,返回的结果集又交给resultSerHandler做结果的映射处理。resultSetHandler的工作有点复杂,大致就是用一个ObjectFactory去根据mapper.xml的reusltMap或者resultType去实例化一个空的POJO,然后一一判断类型,填充到POJO里,实现结果映射POJO。

总结

大致流程可以总结成一个图。

MyBatis概览(各组件以及底层实现原理等)

SqlSession为主要的调配者,持有Configuration与Executor,先是创建Mapper委托Configuration去以MapperProxy给Mapper接口做动态代理,底层查询方法根据mapper.xml的查询类型执行SqlSession的查询方法,而SqlSession在查询时又委托Executor去做实际的查询,Executor会使用Statement查询结果集,然后使用ResultSetmapping做结果集的映射POJO,然后返回给SqlSession,因为动态代理,所以mapper的方法实际是SqlSession执行的查询方法,所以这时候SqlSession返回给方法查询结果,表面看起来像是Mapper的方法返回的结果,实际上却是SqlSession在做事情。

综上所述,底层原理与前面提到的组件概述图结合起来看,SqlSession就像是一个调度者(委派者),Configuration与Executor是实际工作者,这三个大类为主要组件,这里提供给我们接下来手写一个简单的Mybatis框架的一个思路,可以围绕这三个主要类展开,先写出一个简单版本的1.0,再慢慢扩展其功能与组件。