Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码
在文章:Mybatis源码解析,一步一步从浅入深(一):创建准备工程,中我们为了解析mybatis源码创建了一个mybatis的简单工程(源码已上传github,链接在文章末尾),并实现了一个查询功能。接下来就顺着查询功能的实现开始一步一步开始解析mybatis源码。
首先们观察我们的测试代码类UserDaoTest:
package com.test.learnmybatis; import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test; import com.zcz.learnmybatis.dao.UserDao;
import com.zcz.learnmybatis.entity.User; import junit.framework.Assert; public class UserDaoTest {
@Test
public void finUserById() {
//2,获取SqlSession
SqlSession sqlSession = getSessionFactory().openSession();
//3,获取UserDao代理类
UserDao userMapper = sqlSession.getMapper(UserDao.class);
//4,执行查询
User user = userMapper.findUserById(1);
Assert.assertNotNull("not find", user); } /**
* 1,获取SqlSessionFactory
* @return
*/
private static SqlSessionFactory getSessionFactory() {
SqlSessionFactory sessionFactory = null;
//配置文件名称
String resource = "configuration.xml";
try {
//使用配置文件构造SqlSessionFactory
sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));
}catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return sessionFactory;
}
}
整个过程可以分为四个步骤
1,获取SqlSessionFactory
2,获取SqlSession
3,获取UserDao代理类
4,执行查询
我接下来也会根据这四步进行源码的解析。
一,获取SqlSessionFactory
先来看一下静态方法getSessionFactory。在这个方法中我们读取configuration.xml,并使用configuration.xml配置文件实例化了一个SqlSessionFactory。
/**
* 获取SqlSessionFactory
* @return
*/
private static SqlSessionFactory getSessionFactory() {
SqlSessionFactory sessionFactory = null;
//配置文件名称
String resource = "configuration.xml";
try {
//使用配置文件构造SqlSessionFactory
sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));
}catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return sessionFactory;
}
代码:sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));
相信大家都能看懂这段代码:
1,Resources.getResourceAsReader(resource),读取了配置文件configuration.xml,并返回一个字符输入流(Reader),这样configuration.xml中的配置信息就都被读取到了一个字符输入流中了。
public static Reader getResourceAsReader(String resource)方法不过多解释,大家又需要的话,我就在写一篇文章去详细阐述。
2,使用new关键字创建了一个SqlSessionFactoryBuilder的匿名对象。
3,调用匿名对象的build(Reader reader)方法,并将1中的字符输入流作为参数传入。
这样SqlSessionFactory对象就创建成功了,接下来我们详细的分析一下build(Reader reader)方法,先看源码

这里直接调用了public SqlSessionFactory build(Reader reader, String environment, Properties properties)方法,并且environment和properties是null;
方法详情如下:
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
//构造(XML配置解析器)XMLConfigBuilder对象
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
//调用build方法并返回SqlSessionFactory
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
代码:XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
这段代码的作用是使用上一步读取的configuration.xml字符输入流,实例化一个xml配置解析器(XMLConfigBuilder),并且我们已经知道 environment, properties的值为null;
在XMLConfigBuilder对象的实例化过程中初始化一个非常重要的对象:Configuration,而Configuration对象承载了configuration.xml中的所有配置内容,具体的初始化内容请查看:Mybatis源码解析,一步一步从浅入深(三):实例化xml配置解析器(XMLConfigBuilder)
代码:return build(parser.parse());
1,parser.parse()返回一个Configuration对象实例,这是一个及其重要的方法,因为解析configuration.xml并生成Configuration对象,都是在这个方法里完成的。具体的执行细节,请查阅:Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例,Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析
2,调用SqlSessionFactoryBuilder的public SqlSessionFactory build(Configuration config)方法创建一个SqlSessionFactory对象实例,我们来看一下build(Configuration config)方法的内容:
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
在这个方法中,使用parser.parse()解析出来的Configuration对象实例作为参数,实例化了一个DefaultSqlSessionFactory类的对象。
我们看一下类DefaultSqlSessionFactory的声明:
public class DefaultSqlSessionFactory implements SqlSessionFactory {
}
DefaultSqlSessionFactory类实现了SqlSessionFactory接口,我们知道java可以通过多态让SqlSessionFactory 父类引用指向DefaultSqlSessionFactory子类实例。
到这里我们的SqlSessionFactory就创建完成了。
二,获取SqlSession
关键代码:SqlSession sqlSession = getSessionFactory().openSession();
在上一步我们了解到,getSessionFactory()方法,返回一个DefaultSqlSessionFactory实例对象,那么接下来我们就看一下这个DefaultSqlSessionFactory的openSession方法:
public SqlSession openSession() {
//直接调用了openSessionFromDataSource方法
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 获取运行环境
final Environment environment = configuration.getEnvironment();
// 从运行环境中获取事务工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 实例化事务
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 实例化sql执行器
final Executor executor = configuration.newExecutor(tx, execType);
// 返回默认SqlSession 实例化对象
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
// 从运行环境中获取事务工厂
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
if (environment == null || environment.getTransactionFactory() == null) {
return new ManagedTransactionFactory();
}
return environment.getTransactionFactory();
}
到这里SqlSession也获取成功了,获取到的是DefaultSqlSession的示例对象。
三,获取UserDao代理类
关键代码:UserDao userMapper = sqlSession.getMapper(UserDao.class);
从上一步知道这里的sqlSession是DefaultSqlSession的实例化对象,那么就来看一下getMapper方法的源码:
public <T> T getMapper(Class<T> type) {
// 这里的configuration就是一开始一直陪伴着我们的那个Configuration实例对象
return configuration.<T>getMapper(type, this);
}
在这里就有几个奇怪的问题:
1,为什么在以前的代码流程中从来没有addMapper,而这里却有getMapper?
2,UserDao明明是我们定义的一个接口类,根本没有定义实现类,那这个userMapper是什么?是mybatis自动为我们生成的实现类吗?
带着这两个问题,我们在文章Mybatis源码解析,一步一步从浅入深(六):映射代理类的获取进行了详细分析。
四,执行查询
经上一系列的分析,已经基本明确了configuration.xml,userDao-mapping.xml文件的解析,映射代理类实例化对象的生成的整个过程。接下来就剩余最后一个步骤:执行查询。一起来看一下吧。
关键代码:User user = userMapper.findUserById(1);
看过文章Mybatis源码解析,一步一步从浅入深(六):映射代理类的获取的同学们应该已经知道了,代理类对象在执行方法的时候,是调用了InvocationHandler实现类的Invoke方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
而真正执行查询的代码就在第10行,即:mapperMethod.execute(sqlSession, args);看源码:
//根据查询类型执行不同的方法
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
//insert
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
//update
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
//delete
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
//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 {
//我们的代码执行的是这里
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
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;
}
关键代码:result = sqlSession.selectOne(command.getName(), param);
别看这只是一行简单的代码,其实这句代码的背后有很多的逻辑操作,我在文章:Mybatis源码解析,一步一步从浅入深(七):执行查询中进行了详细的分析。请大家查阅;
源代码中的第28行代码的执行结果就是我们期望的查询结果了。
五,整体结束
到这里,mybatis的源码解析系列文章就正式结束了,我们的示例工程很简单,只有一个Mapper文件,并且只有一个根据Id查询的功能。这整个系列的文章,就是根据这个简单的工程,通过断点调试的方法,一步一步的跟踪源代码,最后明确的这个简单的查询是如何执行的。当然mybais还有更多的功能,例如insert,update等,其他的标签,其他的功能,都没有在本系列的文章中阐述,同时在mybatis中对设计模式的使用也是恰到好处,而且因为作者的水平,时间,精力有限。只能为大家大概的介绍了一下。解释不够准确,信息不够丰富也是本系列文章的一大遗憾,但是没有关系,我会在接下来的学习和感悟中继续补充并完善这一些列的文章,供大家阅读参考。
“学习如逆水行舟,不进则退”
这句话送给大家,以期共勉。
原创不易,转载请声明出处:https://www.cnblogs.com/zhangchengzi/p/9672922.html
Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码的更多相关文章
- Mybatis源码解析,一步一步从浅入深(一):创建准备工程
Spring SpringMVC Mybatis(简称ssm)是一个很流行的java web框架,而Mybatis作为ORM 持久层框架,因其灵活简单,深受青睐.而且现在的招聘职位中都要求应试者熟悉M ...
- Mybatis源码解析,一步一步从浅入深(三):实例化xml配置解析器(XMLConfigBuilder)
在上一篇文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码 ,中我们看到 代码:XMLConfigBuilder parser = new XMLConfigBuilder(read ...
- Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例
在Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中我们看到了XMLConfigBuilder(xml配置解析器)的实例化.而且这个实例化过程在文章:Mybatis源码解析,一步一步从浅 ...
- Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析
在上一篇文章Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例中我们谈到了properties,settings,envir ...
- Mybatis源码解析,一步一步从浅入深(六):映射代理类的获取
在文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中我们提到了两个问题: 1,为什么在以前的代码流程中从来没有addMapper,而这里却有getMapper? 2,UserDao ...
- Mybatis源码解析,一步一步从浅入深(七):执行查询
一,前言 我们在文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码的最后一步说到执行查询的关键代码: result = sqlSession.selectOne(command.ge ...
- MyBatis 从浅入深 随笔整理
MyBatis? archetypeCatalog = internal 本文档单独出现的_parameter都标识为变量名 一.三个基本要素: 核心接口和类 MyBatis 核心配置文件 SQL映射 ...
- 从浅入深详解独立ip网站域名恶意解析的解决方案
立IP空间的好处想必大家都能耳熟闻详,稳定性强,利于seo等让大家选择了鼎峰网络香港独立IP空间.那么, 网站独享服务器IP地址,独立IP空间利于百度收录和权重的积累.不受牵连.稳定性强等诸多优势为一 ...
- 一步一步学多线程-ThreadLocal源码解析
上网查看了很多篇ThreadLocal的原理的博客,上来都是文字一大堆,费劲看了半天,大脑中也没有一个模型,想着要是能够有一张图明确表示出来ThreadLocal的设计该多好,所以就自己看了源码,画了 ...
随机推荐
- 教你用原生CSS写炫酷页面切换效果,跟第三方组件说拜拜
因为项目需要,别人想让我给他写一个个人博客,并且给了我一个其他人的网页,可以点此查看.有的同学可能说了,第三方博客框架这么多,为什么还要去手写的,你说这个有可能是没有看到打开这个博客. 样式介绍 给大 ...
- 求平方根算法 Heron’s algorithm
求平方根问题 概述:本文介绍一个古老但是高效的求平方根的算法及其python实现,分析它为什么可以快速求解,并说明它为何就是牛顿迭代法的特例. 问题:求一个正实数的平方根. 给定正实数 \(m\),如 ...
- Spring源码剖析7:AOP实现原理详解
前言 前面写了六篇文章详细地分析了Spring Bean加载流程,这部分完了之后就要进入一个比较困难的部分了,就是AOP的实现原理分析.为了探究AOP实现原理,首先定义几个类,一个Dao接口: pub ...
- 简单架构:反射实现抽象工厂+IDAL接口完全独立DAL
一.普通架构中存在的问题 StudentDB数据库,包含一张StudentInfoTB表,结构如下: s_id int primary key identity(1,1), s_name Nvarch ...
- Linux应用开发自学之路
前言 在 「关于我 」那篇博文里,朋友们应该知道了我不是科班出身,是由机械强行转行到Linux应用开发方向.下面我就详细向大家介绍自己这一路上的转行历程,希望对大家有所启发. 我是学机械专业的,对于机 ...
- 第五章 函数day2
5.2函数小高级 5.2.1 函数当参数 1 函数也可以当返回值 def v(dar): v = dar() def n (): print(444) v(n) # 实例2 def v(): prin ...
- Redis学习总结(九)-- Redis常用技巧
这里会记录下Redis 常用的小技巧 全局使用 redis-cli 等命令 在之前我们都在做 Redis 命令目录下通过 ./redis-cli这种形式访问,如果使用 redis-cli 的话就会报命 ...
- jvm系列(七):如何优化Java GC「译」
本文由CrowHawk翻译,地址:如何优化Java GC「译」,是Java GC调优的经典佳作. Sangmin Lee发表在Cubrid上的”Become a Java GC Expert”系列文章 ...
- GCN和GCN在文本分类中应用
1.GCN的概念 传统CNN卷积可以处理图片等欧式结构的数据,却很难处理社交网络.信息网络等非欧式结构的数据.一般图片是由c个通道h行w列的矩阵组成的,结构非常规整.而社交网络.信息网络 ...
- 传入值参数&传入引用参数的区别
传值&传引用 1.传值 是把实参的值赋值给行参 那么对行参的修改,不会影响实参的值 2.传地址 是传值的一种特殊方式,只是他传递的是地址 那么传地址以后,实参和行参都指向同一个对象 3.传引用 ...