Mybatis技术原理理——整体流程理解
前言:2018年,是最杂乱的一年!所以你看我的博客,是不是很空!
网上有很多关于Mybatis原理介绍的博文,这里介绍两篇我个人很推荐的博文 Mybatis3.4.x技术内幕和 MyBaits源码分析!让我找到了学习的入口,当然另外你必须要看的官方文档 MyBatis学习。那么有了这些知识,就让我们愉快的吃鸡之路吧!
一:你首先得知道的知识点。
1.1 JDBC
在个人看来, Mybatis的核心就是对SQL语句的管理!那么在JAVA环境下,对SQL的管理或者其他任何的实现,肯定是离不开JAVA的数据库操作接口,包括Connrction接口、Statement接口、PreparadStatement接口以及ResultSet接口等等的属性,你可以先通过JDBC来操作一次或者更多次的数据库。这个就不多做赘述了!
1.2 动态代理
不知道你最初有没有和我一样,有这样的疑问。Mybatis的mapper层明明就是一个接口,都没有实现类!即使是在TMapper.xml中做了映射,也没有看到任何关于接口的实现类,那他是怎么被实例化的呢,又是怎么实现方法的功能的呢?动态代理会告诉你答案!
先来看看jdk动态代理的一个小例子:
建立一个实体
public class TestDemo {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
建立一个mapper
public interface TestDemoMapper {
TestDemo getById(Integer id);
}
建立一个Mapper的代理类,需要继承一个 InvocationHandler 即可
public class DemoMapperProxy<T> implements InvocationHandler {
@SuppressWarnings("unchecked")
public T newInstance(Class<T> clz) {
return (T)Proxy.newProxyInstance(clz.getClassLoader(), new Class[] { clz }, this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
TestDemo testDemo = new TestDemo();
testDemo.setId(1);
testDemo.setName("proxyName");
return testDemo;
}
}
测试类
public static void main(String[] args) {
DemoMapperProxy<TestDemoMapper> demoMapperProxy = new DemoMapperProxy<TestDemoMapper>();
TestDemoMapper testDemoMapper = demoMapperProxy.newInstance(TestDemoMapper.class);
TestDemo byId = testDemoMapper.getById(1);
System.out.println(byId);
}
结果:TestDemo{id=1, name='proxyName'},
所以这就应该大致能够说明Mybatis中的mapper是怎么工作的了吧!
二:关于mapper的整体流程理解
2.1 先来看看官方网站的一个例子吧
在官方文档中的 Mybatis 3 的入门介绍中,介绍了怎么配置一个mybatis的测试类
mybatis-config.xml 中的一个简单配置:
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://172.20.139.237:3306/rubber-fruit?useUnicode=true&characterEncoding=utf-8&autoReconnect=true" />
<property name="username" value="user123" />
<property name="password" value="u123" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="UserMapper.xml" />
</mappers>
</configuration>
然后在写一个测试类:
public static void main(String[] args) throws Exception {
String resouse = "mybatis-config.xml";
InputStream stream = Resources.getResourceAsStream(resouse);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(stream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User byId = mapper.getById(1);
sqlSession.close();
System.out.println(byId);
}
其实这个逻辑很简单,首先是拿到mabatis-config.xml 中的配置对象,生成SqlSessionFactory,然后通过sqlSession拿到mapper,通过动态代理完成方法的执行,并返回结果!那么我们就可以简单的拆分成两个部分,第一:配置文件的初始化,第二:sql的执行,后面的文章也会更具这个大模块来更具细化的讲解
其实上面的第四行代码 可以更换成config对象的,可能看的更清楚一些:
String resouse = "mybatis-config.xml";
InputStream stream = Resources.getResourceAsStream(resouse);
XMLConfigBuilder configBuilder = new XMLConfigBuilder(stream,null,null);
Configuration parse = configBuilder.parse();
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(parse);
其实Mybatis的前期初始化的一个重要的对象就是 Configuration对象,后面会慢慢揭开它的面纱!
2.2 整体流程图
通过上面的这个测试方法,我们通过源码流程,大致可以拆分出这样的一个图:
其实从这个图中我们已经可以看出Mybatis划分先两个部分:
1:初始化过程
首先是通过XMLConfigBuilder解析 mybatis-config.xml 对象来进行初始化,拿到Configuration对象,然后SqlSessionFactoryBuilder通过初始化的Configuration对象,生成SqlSession,SqlSession中调用getMapper对象方法,其实也就是Configuration中的getMapper方法 通过MapperProxy代理对象生成一个mapper。到这里位置就是一个简单的初初始化思路了!当然Mybatis的初始化远远不止这些。
2:mybatia中执行一个sql的完整流程
从图中也是能够很较清晰的看出这个执行流程的。因为Mapper是一个接口,所以不能直接实例化的!那么MapperProxy的作用,就是通过JDK的动态代理,来间接的对mapper进行实例化,不了解的可以看上面的1.2中的例子。那么我们可以看看org.apache.ibatis.binding.MapperProxy源码中的对象方法:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
通过代理的对象方法方法,拿到了一个MapperMethod对象,并对mapperMethod对象进行来缓存。为啥要缓存呢?这个时候可以看看MapperMethod对象是什么?org.apache.ibatis.binding.MapperMethod中对两个私有方法
private final SqlCommand command;
private final MethodSignature method; public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
MapperMethod中的SqlCommand对象中的两个私有方法,
public static class SqlCommand {
private final String name;
private final SqlCommandType type;
}
public enum SqlCommandType {
UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
}
而MapperMethod中的MethodSignature对象呢?
private final boolean returnsMany;
private final boolean returnsMap;
private final boolean returnsVoid;
private final boolean returnsCursor;
private final Class<?> returnType;
private final String mapKey;
private final Integer resultHandlerIndex;
private final Integer rowBoundsIndex;
private final ParamNameResolver paramNameResolver;
所以这些很清晰了,MapperMethod对象是把mapper中的sql进行了封装,获取了sql的执行类型和返回值。
MapperMethod中的另外一个重要的方法:execute() 则担任这路由的重要任务:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case 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 if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
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;
}
通过执行的sql类型 选取不通的sqlSession进行执行。其实可以看出,转了一圈,最后的最后还是落在了SqlSession当中。而SqlSession又把这个重要的操作交个了执行器Executor。最后又到了StatementHandler来负责执行最后的sql,ResultSetHandler放回执行的结果。
2.3 记一个知识点
看到这里,突然是想问一下一个问题的,Mapper的方法是否支持重载呢?
答案是不能的!mybatis是使用package+Mapper+method 全名称作为Key值 去xml中寻找一个唯一sql来执行的那么,重载方法时将导致矛盾。对于Mapper接口,Mybatis禁止方法重载。通过代码断点可以看到。
org.apache.ibatis.session.Configuration对象中有一个方法,addMappedStatement()
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
/**中间部分省略掉**
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}

而这个地方put的ms.getId 就是通过mapper的配置文件组合成的一个唯一的key值。那我们在看看StrictMap 中的put方法源码,那么就一目了然了
@SuppressWarnings("unchecked")
public V put(String key, V value) {
if (containsKey(key)) {
throw new IllegalArgumentException(name + " already contains value for " + key);
}
if (key.contains(".")) {
final String shortKey = getShortName(key);
if (super.get(shortKey) == null) {
super.put(shortKey, value);
} else {
super.put(shortKey, (V) new Ambiguity(shortKey));
}
}
return super.put(key, value);
}
如果有相同的key的,那么会抛出异常,这也就是为啥mapper不能重载的原因!
Mybatis技术原理理——整体流程理解的更多相关文章
- Mybatis技术内幕(一)——整体架构概览
Mybatis技术内幕(一)--整体架构概览 Mybatis的整体架构分为三层,分别是基础支持层.核心处理层和接口层. 如图所示: 一.基础支持层 基础支持层包含整个Mybatis的基础模块,这些模块 ...
- TAF /tars必修课(一):整体架构理解
来自零点智能社区 一.前言 TAF,一个后台逻辑层的高性能RPC框架,目前支持C++,Java, node 三种语言, 往后可能会考虑提供更多主流语言的支持如 go等,自定义协议JCE,同时也支持HT ...
- 流水线技术原理和Verilog HDL实现(转)
源:流水线技术原理和Verilog HDL实现 所谓流水线处理,如同生产装配线一样,将操作执行工作量分成若干个时间上均衡的操作段,从流水线的起点连续地输入,流水线的各操作段以重叠方式执行.这使得操作执 ...
- (转)linux内存源码分析 - 内存回收(整体流程)
http://www.cnblogs.com/tolimit/p/5435068.html------------linux内存源码分析 - 内存回收(整体流程) 概述 当linux系统内存压力就大时 ...
- MyBatis 源码篇-整体架构
MyBatis 的整体架构分为三层, 分别是基础支持层.核心处理层和接口层,如下图所示. 基础支持层 反射模块 该模块对 Java 原生的反射进行了良好的封装,提供了更加简洁易用的 API ,方便上层 ...
- [原]Jenkins(一)---我理解的jenkins是这样的
/** * lihaibo * 文章内容都是根据自己工作情况实践得出. *版权声明:本博客欢迎转发,但请保留原作者信息! http://www.cnblogs.com/horizonli/p/5330 ...
- 使用git整体流程
一.git提交代码走meger请求的整体流程 工作中使用git推代码时,如果走merge请求,那么也就是说拉代码时拉公共代码库的代码,但是提交时需要先提交到自己的代码库,然后在gitlab上提交mer ...
- iOS开发从申请开发账号到APP上架的整体流程详解
应公司要求,写一份文档从申请账号一直到APP上架的整体流程,下面进入正文. https://blog.csdn.net/qq_35612929/article/details/78754470 首先第 ...
- [原]Jenkins(一)---我理解的jenkins是这样的(附全套PDF下载)
/** * lihaibo * 文章内容都是根据自己工作情况实践得出. *版权声明:本博客欢迎转发,但请保留原作者信息! http://www.cnblogs.com/horizonli/p/5330 ...
随机推荐
- element表格切入按钮以及复选框
1,element表格切入按钮 关键代码: html:<el-table :data="tableList" border style="width: 100%&q ...
- Vue项目需求实现记录(永久更新)
1.表单校验功能: 在el-form标签中定义:rules="rules";ref="reference" 在el-form-item定义prop=" ...
- 前端开发中使用mac自带apache服务
场景 前端开发中,总是会有这样的需求,就是快速的写一个脚本,或者一个简单的demo页面.这时,我们需要马上可以启动一个web服务,来支持开发. 我们可以安装一个全局的cli工具,通过node服务来满足 ...
- Dynamics 365-关于Solution的那些事(二)
接着上一篇的说,现在有一个已知前提:Solution的增量特性.然后我们再思考这么一个场景,项目开发过程中,存在多次迭代的情况,每次迭代可能涉及到的solution是同一个,唯一区别的,就是solut ...
- Your project path contains non-ASCII characters
Android studio导入project时报错 non-ASCII characters意味着中文字符报错,解决方法简单有效: 检查项目路径中是否出现中文名,将中文字符修改成英文就可以解决辽~
- DataPipeline | 享物说产品负责人夏凯:数据驱动的用户增长实战
夏凯 卡内基梅隆大学计算机系毕业,曾供职于Evernote数据团队和微软Bing.com搜索引擎广告部门.回国后作为早期成员加入小红书,先后从事大数据,用户增长,项目和团队管理等工作. 我最初是在美国 ...
- SQL Server含逗号分隔的数据匹配维表
日常有时候导出数据需求时,数据列会遇到带有分隔符的ID,但又需要匹配维表(如下图所示)将ID变成名称. 这种情况可以采用“分隔-匹配-合并”的方法 具体代码为: -- 准备工作1:创建事实表数据 CR ...
- 一:SqlServer中的 CEILING函数和 FLOOR函数以及ROUND()
例如 1.ROUND() 格式为ROUND(y1,y2,y3) y1:要被四舍五入的数字y2:保留的小数位数 y3:为0,可以不写,y1进行四舍五入,不为0则y1不进入四舍五入,如果y1有值就直接根据 ...
- requests库下载图片的方法
方法: 传入图片url,requests.get()方法请求一下,将源码以二进制的形式写在本地即可. 以前一直以为requests库中有特定的方法获取图片,类似urllib.request.urlre ...
- vtop工具使用分析
vtop工具可以为esxtop提供图形界面,并且可以显示实时统计数据,对于我们监控esxi主机的需求匹配度很高,同时,相对于vcenter中的数据统计选项实时性更高,操作简便,可作为工作使用 为便于我 ...