MyBatis源码分析(各组件关系+底层原理
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) {
}
}
---------------------
MyBatis源码分析(各组件关系+底层原理的更多相关文章
- MyBatis源码分析(2)—— Plugin原理
@(MyBatis)[Plugin] MyBatis源码分析--Plugin原理 Plugin原理 Plugin的实现采用了Java的动态代理,应用了责任链设计模式 InterceptorChain ...
- Mybatis源码分析之Cache二级缓存原理 (五)
一:Cache类的介绍 讲解缓存之前我们需要先了解一下Cache接口以及实现MyBatis定义了一个org.apache.ibatis.cache.Cache接口作为其Cache提供者的SPI(Ser ...
- Mybatis源码分析之Cache一级缓存原理(四)
之前的文章我已经基本讲解到了SqlSessionFactory.SqlSession.Excutor以及Mpper执行SQL过程,下面我来了解下myabtis的缓存, 它的缓存分为一级缓存和二级缓存, ...
- springboot整合mybatis源码分析
springboot整合mybatis源码分析 本文主要讲述mybatis在springboot中是如何被加载执行的,由于涉及的内容会比较多,所以这次只会对调用关系及关键代码点进行讲解,为了避免文章太 ...
- MyBatis 源码分析 - 缓存原理
1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 Redis 或 memcached 等缓存中间件,拦截大量奔向数据库的请求,减轻数据库压力.作为一个重要的组件,MyBatis 自然 ...
- MyBatis源码分析-SQL语句执行的完整流程
MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...
- MyBatis源码分析(4)—— Cache构建以及应用
@(MyBatis)[Cache] MyBatis源码分析--Cache构建以及应用 SqlSession使用缓存流程 如果开启了二级缓存,而Executor会使用CachingExecutor来装饰 ...
- MyBatis源码分析(3)—— Cache接口以及实现
@(MyBatis)[Cache] MyBatis源码分析--Cache接口以及实现 Cache接口 MyBatis中的Cache以SPI实现,给需要集成其它Cache或者自定义Cache提供了接口. ...
- MyBatis 源码分析 - 映射文件解析过程
1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...
随机推荐
- c#打包时 Could not find file "I:\VS2012\myWork\SmartCam\SmartCam\bin\Debug\Emgu.CV.DebuggerVisualizers.VS2012.dll" ISEXP : error : -6103: Could not find file "I:\VS2012\myWork\SmartCam\SmartCam\bin
1.错误:C#打包时发生如下错误: 错误 1 -6103: Could not find file "I:\VS2012\myWork\SmartCam\SmartCam\bin\Debug ...
- 关于Bubblesort算法
Java中的经典算法之冒泡排序(Bubble Sort) 原理:比较两个相邻的元素,将值大的元素交换至右端. 思路:依次比较相邻的两个数,将小数放在前面,大数放在后面.即在第一趟:首先比较第1个和第2 ...
- CentOS 6.9使用sudo时出现:“...不在 sudoers 文件中,此事将被报告”的问题解决
在终端切换root账号登录 su 修改/etc/sudoers文件 visudo 找到:root ALL=(ALL) ALL,修改成自己的账号: 保存即可,按Exc,输入”:wq!“,回车.
- gdb的follow-fork-mode使用以及多线程操作
对于多线程,如果希望让其他线程不执行,只有调试线程执行,使用 set scheduler-locking [on|off|step]
- currentThread()方法返回代码段正在被哪个线程调用
currentThread()方法返回代码段正在被哪个线程调用 package com.stono.thread2.page16; public class MyThread extends Thre ...
- Ralink5350开发环境搭建
一.安装虚拟机(Oracle VM VirtualBox 或 VMware Workstation) 二.在虚拟机中安装linux操作系统(当前使用的是Ubuntu1204桌面版) 三.配置linu ...
- wifi断线问题
近期在项目中,遇到wifi常常断线现象,平台是Android平台,现象是:连接wifi后,长时间播放视频,会出现wifi断开,界面上WiFi图标显示打叉,请问有WiFi方面的行家朋友,有没有办法来检測 ...
- 有一种蓝叫 APEC 蓝
有如是解释 APEC 者--Air Pollution Eventually Controlled. 有说此次是继零八后的重新万国来朝.丝路大略明白了,西域必通. 站在历史的远处回眸,这是继零八年后重 ...
- zoj3886--Nico Number(素数筛+线段树)
Nico Number Time Limit: 2 Seconds Memory Limit: 262144 KB Kousaka Honoka and Minami Kotori are ...
- NSAttributedString宽高计算小技巧
通常对于CoreText之类自己实现绘制的控件来说,计算富文本的宽高事实上须要依赖CTFramesetterSuggestFrameSizeWithConstraints这种方法. 但有些时候.我们可 ...