首页 技术 正文
技术 2022年11月20日
0 收藏 748 点赞 4,699 浏览 8194 个字

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) {
}
}
———————

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:8,942
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,466
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,281
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,095
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,728
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,765