MyBatis源码分析
MyBatis流程图

下面将结合代码具体分析。

MyBatis具体代码分析

SqlSessionFactoryBuilder根据XML文件流,或者Configuration类实例build出一个SqlSessionFactory。

SqlSessionFactory.openSession()相当于从连接池中获取了一个connection,创建Executor实例,创建事务实例。

DefaultSqlSessionFactory.class

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;

DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}

return var8;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
此时我们只是获得一条connection,session.getMapper(XxxMapper.class)时才进行创建代理实例的过程,后面会介绍。SqlSession.getMapper实际上托付给Configuration去做。

public <T> T getMapper(Class<T> type) {
return this.configuration.getMapper(type, this);
}
1
2
3
Configuration交给自己的成员变量mapperRegistry去做。这个成员变量是Map再封装之后的,持有configuration实例和Map<Class<?>, MapperProxyFactory<?>> knownMappers,正如xml文件中写的那样,每个mappee.xml中都有一个namespace,这个namespace就是Class<?>,而后者是对这个接口进行代理的工厂MapperProxyFactory实例,其中封装了被代理接口和缓存。这个knownMappers应该是初始化configuration的时候就已经处理完毕的。

MapperRegistry.class

private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
1
2
3
4
类似于Spring中的getBean方法,MyBatis中用getMapper的方式进行创建。下面代码可以看出,先根据class类型获取代理类工厂,去工厂中newInstance。注意这里是没有Spring中的单例多例的,只要你getMapper,框架就会给你newInstance一个全新的被代理实例。

MapperRegistry.class

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
newInstance()中做了什么呢?

MapperProxyFactory.class

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

public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
1
2
3
4
5
6
7
8
9
10
其实我们知道,Proxy.newProxyInstance()需要三个参数,类加载器,被代理接口和InvocationHnadler,**什么?不知道?快去补习基础。**其中InvocationHandler掌管着invoke方法,正是这个方法中实现了对被代理实例的代码增强(或者叫做代理代码)。那我们就要着重看这个InvocationHandler里面到底有什么,特别是他的invoke方法。

MapperProxy.class

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
...省略...
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
1
2
3
4
5
6
7
invoke方法中,重点是调用了mapperMethod.execute()。这个mapperMethod就是:**被代理接口A,A中有方法a(),代理类实例((A)proxyA).a()中的这个a,就是method,而mapperMethod就是method被包装了一层。**换而言之,(session.getMapper(XxxMapper)).interfaceMethod()时,都在走mapperMethod.execute()这个方法。

下面我们来看mapperMethod.execute这个方法。

MapperMethod.class

public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
switch(this.command.getType()) {
...省略...
case SELECT:
...省略...
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
...省略...
}
...省略...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这个方法做了两件事,1.对参数用参数解析器转化为JDBCType的参数,这边不是重点。2.执行sqlSession.selectOne(),当然我删去了一些代码,为讲清楚,只讲selectOne()即可,其他都是大同小异的。又回到最初的起点,呆呆地望着镜子前。 sqlSession又见面了,发现了么?sqlSession先是把getMapper交给configuration做,然后自己还能执行类似selecOne,update之类的命令,这是因为sqlSession是暴露给用户的接口,如果用户要用传统方式,就可以直接调用selectOne之类的方法,比如Employee employee = session.selectOne("mybatisDemo.dao.EmployeeMapper.getEmpById",1);如果用户想用mapper.xml和mapper接口的方法,就getMapper获得代理实例然后调用接口方法即可。所以本质上,所有跟JDBC打交道的还是sqlsession的select、update等方法

现在还都是表面功夫,直到sqlSession.selectOne才开始真正的辉煌旅程。小结一下,目前我们看到的MyBatis组件包括SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession。还未看到的有,Executor,ParameterHandler,StatementHandler,ResultSetHandler。这几个部件都会在之后出现。

下面来分析session.selectOne()。selectOne内部调用的还是selectList,因此直接看SqlSession的实现类DefaultSqlSession中的方法。可以发现,Executor组件终于出现了,而这个组件才是真正执行query()方法的组件。SqlSession真的是领导,getMapper交给config做,select等脏活累活又交给Executor完成。Executor.query的入参有什么?被代理方法参数parameter,ms用于动态sql的。

DefaultSqlSession.class

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
...省略...
MappedStatement ms = this.configuration.getMappedStatement(statement);
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
...省略...
return var5;
}
1
2
3
4
5
6
7
8
9
10
下面去看query方法,在Executor的一个抽象实现类,其实也就是模板类BaseExecutor中。

BaseExecutor.class

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
1
2
3
4
5
6
7
BoundSql就是动态sql,key是将sql语句,入参组合起来作为缓存参数,即:如果sql语句相同且参数一样,那可以认为两个sql语句会返回同样的结果(缓存未失效的情况下)。query方法中进一步调用doQuery方法,这个方法在BaseExecutor中只给出抽象方法,交给子类去继承实现。这个子类就是SimpleExecutor。

SimpleExecutor.class

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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
这里出现了StatementHandler这个组件,先别急着点进newStatementHandler()方法,先看一下StatementHandler接口,发现这个接口有ParameterHandler getParameterHandler();方法和<E> List<E> query(Statement var1, ResultHandler var2)。这时候,ParmeterHandler和ResultHandler两大组件也出现了。所以这三个组件的关系是,StatementHandler中需要通过ParamterHandler处理参数,然后将结果通过ResultHandler处理成要求的JavaBean、Map、List后输出。

小结一下:SqlSession将查询等任务交给Executor接口实现类完成,Executor内有StatementHandler,StatementHandler内有ParameterHandler和ResultHandler,分别进行参数处理和结果处理。

还没讲newStatementHandler()这个方法呢,为什么要现在讲?

Configuration.class

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler)this.interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}

public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
ResultSetHandler resultSetHandler = (ResultSetHandler)this.interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}

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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
值得注意的是interceptorChain,拦截器链,这里的拦截器链通过pluginAll对几个Handler进行织入。织入的是什么代码呢?是你写的拦截器代码。回忆一下MyBatis写拦截器代码的时候要指定哪些呢?1.要指定针对Exector,ParameterHandler,StatementHandler,或者ResultHandler进行拦截2.要指定针对什么方法拦截。针对拦截器这一部分的原理,建议阅读

https://www.jianshu.com/p/b82d0a95b2f3

@Intercepts({@Signature(type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
return invocation.proceed();
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
}
}
---------------------

最新文章

  1. Using Flash Builder with Flash Professional
  2. PAT (Basic Level) Practise:1009. 说反话
  3. UI:target-action设计模式、手势识别器
  4. C# - implicit, explicit
  5. mac 上iterm终端显示中文为乱码解决方案
  6. 统计难题 HDOJ --1251
  7. java创建XML及开源DOM4J的使用
  8. dropload.js(上拉加载插件使用过程中遇到的坑)
  9. 总结C语言字符检测函数:isalnum、isalpha...
  10. CAD快捷键
  11. Java中三种代理模式
  12. Java连接数据库,及增删改查
  13. 领域驱动设计系列文章(2)——浅析VO、DTO、DO、PO的概念、区别和用处
  14. python全栈开发day76-博客主页
  15. Git 与 SVN 命令学习笔记
  16. C# 获取文件图标
  17. layer弹出层设置相对父级元素定位
  18. Java SPI机制和使用示例
  19. Team Member Introduction and Division of Work
  20. 如果天空不死博客java阅读列表整理

热门文章

  1. 使用Layer完成图片放大功能
  2. jquery源码分析(二)——架构设计
  3. Java并发之CAS与AQS简介
  4. reset清除所有浏览器默认样式
  5. 05springMVC数据格式化
  6. Sigma Function 数学 因子求和
  7. 导致“mysql has gone away”的两种情况
  8. HDU 1115
  9. 彻底解决Android GPS没法定位这一顽固问题
  10. bzoj2878 [Noi2012]迷失游乐园 [树形dp]