聊聊Mybatis的实现原理
使用示例
平时我们使用的一般是集成了Spring或是Spring Boot的Mybatis,封装了一层,看源码不直接;如下,看看原生的Mybatis使用示例

示例解析
通过代码可以清晰地看出,MyBatis的操作主要分为两大阶段:
- 第一阶段:MyBatis初始化阶段。该阶段用来完成MyBatis运行环境的准备工作,读取配置并初始化关键的对象,提供给后续使用,只在 MyBatis启动时运行一次。
- 第二阶段:数据读写阶段。该阶段由数据读写操作触发,将根据要求完成具体的增、删、改、查等数据库操作。
在第一阶段,最关键的就是SqlSessionFactory对象。在Spring集成Mybatis的源码中,SqlSessionFactoryBean也是做这个事情,读取配置并初始化构建SqlSessionFactory。
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
// Spring Bean的生命周期会调用此方法
public void afterPropertiesSet() throws Exception {
this.sqlSessionFactory = this.buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory(){
// 构建Configuration....
Configuration configuration;
if (this.configuration != null) {
configuration = this.configuration;
if (configuration.getVariables() == null) {
configuration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
configuration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property `configuration` or 'configLocation' not specified, using default MyBatis Configuration");
}
configuration = new Configuration();
configuration.setVariables(this.configurationProperties);
}
/// 其它code...
return this.sqlSessionFactoryBuilder.build(configuration);
}
}
配置文件的解析,最终会生成一个Configuration对象。
private void parseConfiguration(XNode root) {
try {
Properties settings = this.settingsAsPropertiess(root.evalNode("settings"));
this.propertiesElement(root.evalNode("properties"));
this.loadCustomVfs(settings);
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.reflectionFactoryElement(root.evalNode("reflectionFactory"));
this.settingsElement(settings);
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlerElement(root.evalNode("typeHandlers"));
// 解析mappers节点
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
前期的准备已就绪,关键的配置已解析且构建并初始化了SqlSessionFactory了。接下来就是创建数据库连接并执行业务的CRUD。在第二阶段的OpenSession方法负责创建并打开数据库链接。
public SqlSession openSession(Connection connection) {
return this.openSessionFromConnection(this.configuration.getDefaultExecutorType(), connection);
}
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;
}
最后就是调用Mapper接口的业务方法,返回业务数据。
//SqlSession.getMapper()
public <T> T getMapper(Class<T> type) {
return this.configuration.getMapper(type, this);
}
// configuration.getMapper()
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
// mapperRegistry.getMapper()
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);
}
}
}
// mapperProxyFactory.newInstance()
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);
}
最后,打完收工,示例代码所涉及到的关键代码就这些。
反思
上面的示例是比较简单的,那么其实现思路到底是什么样的?首先就有几个问题:
- Mapper接口中的方法没有实现,那客户端调用接口的方法时,返回的数据是从哪来的?
- Mapper文件与Mapper接口是怎么关联绑定上的?
第一个问题,绝对离不开动态代理,因为只有接口的时候,那么一定会有动态代理生成代理类同时有拦截处理器(InvocationHandler)来增强其执行逻辑。
第二个问题,Mapper文件中有一个<mapper>节点,其namespace就是接口的全限定名称,而其下节点<select>|<update>|..都有一个id值,该值与接口的方法是一致的。因此从这里就可以看出来,业务的crud操作节点是通过namespace+id来对应mapper接口及其方法的。那么我们就可以考虑到,在第一个问题中的拦截处理器执行方法method时,我们就可以通过此关联关系找到要执行的SQL。
如上,这么一分析来看,其实大概的实现思路已经出来了。就是动态代理+<mapper>节点解析实现了接口方法的调用与业务SQL的执行。
因此在第一阶段的解析时,Mapper文件里的<Mapper>节点解析出来的对象就起到了关键的作用。如下,有几个关键的抽象:
MapperRegistry类的knownMappers属性保存着接口及其代理对象的关系。
// type = interface
knownMappers.put(type, new MapperProxyFactory(type));
MappedStatement类对应着<mapper>节点下的CRUD节点。
public void parseStatementNode() {
String id = this.context.getStringAttribute("id");
if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
Integer fetchSize = this.context.getIntAttribute("fetchSize");
Integer timeout = this.context.getIntAttribute("timeout");
String parameterMap = this.context.getStringAttribute("parameterMap");
String parameterType = this.context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = this.resolveClass(parameterType);
String resultMap = this.context.getStringAttribute("resultMap");
String resultType = this.context.getStringAttribute("resultType");
// 解析其他属性...
this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
}
如上是抽象出来整个执行过程的简单流程。实际上还有动态参数绑定与事务等,这些都是在动态代理的拦截处理器中的增强逻辑;下篇再阐述。
聊聊Mybatis的实现原理的更多相关文章
- 图解 | 聊聊 MyBatis 缓存
首发公众号-悟空聊架构:图解 | 聊聊 MyBatis 缓存 你好,我是悟空. 本文主要内容如下: 一.MyBatis 缓存中的常用概念 MyBatis 缓存:它用来优化 SQL 数据库查询的,但是可 ...
- Mybatis接口编程原理分析(三)
前面两篇博客Mybatis接口编程原理分析(一)和Mybatis接口编程原理分析(二)我们介绍了MapperProxyFactory.MapperProxy和MapperMethod的操作及源码分析, ...
- Mybatis接口编程原理分析(二)
在上一篇博客中 Mybatis接口编程原理分析(一)中我们介绍了MapperProxyFactory和MapperProxy,接下来我们要介绍的是MapperMethod MapperMethod:它 ...
- 深入理解Mybatis技术与原理
目录 第1章 Mybatis简介 1.1 传统的JDBC编程 1.2 ORM模型 1.4 MyBatis 1.5 什么时候用MyBatis 第2章 MyBatis入门 2.2 MyBatis构成 2. ...
- mybatis(一、原理,一对多,多对一查询)
MyBatis框架及原理分析 MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架,其主要就完成2件事情: 封装JDBC操作 利用反射打通Java类与SQL语句之间的相互转换 ...
- Mybatis之工作原理
1.Mybatis的架构 1.1 Mybatis的框架分层 1.2 MyBatis的实现原理 mybatis底层还是采用原生jdbc来对数据库进行操作的,它支持定制化 SQL.存储过程以及高级映射的优 ...
- 聊聊MyBatis缓存机制【美团-推荐】
聊聊MyBatis缓存机制 2018年01月19日 作者: 凯伦 文章链接 18778字 38分钟阅读 前言 MyBatis是常见的Java数据库访问层框架.在日常工作中,开发人员多数情况下是使用My ...
- mybatis 插件的原理-责任链和动态代理的体现
目录 1 拦截哪些方法 2 如何代理 3 代理对象 4 责任链设计模式 @ 如果没有自定义过拦截器, 可以看我前面的文章.如果不知道 JDK 动态代理怎么使用的, 可以看我这文章. 责任链设计模式理解 ...
- Mybatis简介与原理
经常面试别人或者被面试,对Mybatis简介与原理这个问题的回答千差万别,为了更好的服务与以后,来个原理介绍. 什么是Mybatis MyBatis 本是apache的一个开源项目iBatis, 20 ...
- MyBatis整合Spring原理分析
目录 MyBatis整合Spring原理分析 MapperScan的秘密 简单总结 假如不结合Spring框架,我们使用MyBatis时的一个典型使用方式如下: public class UserDa ...
随机推荐
- Android笔记--视图显示
视图显示 视图的宽高设置 方式一:在.xml文件中设置视图的宽和高 通过调用android:layout_width设置视图的宽 通过调用android:layout_height设置视图的高 宽和高 ...
- Python学习笔记--列表来啦!
列表的定义格式 示例: 列表的下标索引(千万别超出范围!!!) 示例: 正向和反向 取出嵌套列表的元素:(二维数组) 列表的常用的操作方法 查找某元素的下标 示例: 修改元素 插入元素 示例: 追加元 ...
- Javaweb基础复习------JSON相关知识
JSON(JavaScript对象表示法) 首先,我们需要知道的是,要使用json语法的话,就需要在Maven项目中导入相关的包,可以参考我之前发过的那个Maven导包那个网址,在里面找到这个页面: ...
- 如何快速弄懂Java线程池
Java线程池是一种高效的多线程编程技术,它可以帮助程序员有效地控制多线程的并发执行.它可以提高应用程序的性能.降低内存消耗和减少延迟. 线程池的原理是,程序员可以将每个任务放入线程池中,然后由线程池 ...
- MySQL笔记之一致性视图与MVCC实现
一致性读视图是InnoDB在实现MVCC用到的虚拟结构,用于读提交(RC)和可重复度(RR)隔离级别的实现. 一致性视图没有物理结构,主要是在事务执行期间用来定义该事物可以看到什么数据. 一.Read ...
- Springboot集成MongoDB存储文件、读取文件
一.前言和开发环境及配置 可以转载,但请注明出处. 之前自己写的SpringBoot整合MongoDB的聚合查询操作,感兴趣的可以点击查阅. https://www.cnblogs.com/zaoyu ...
- 前端获取后端设置的自定义头,前端获取不到后端设置的response headers
需要后端设置 Access-Control-Expose-Headers 例如:后端在返回头中设置 user-name: 张三 但是前端直接response.headers['user-name']是 ...
- 【深入浅出Spring原理及实战】「源码调试分析」深入源码探索Spring底层框架的的refresh方法所出现的问题和异常
学习Spring源码的建议 阅读Spring官方文档,了解Spring框架的基本概念和使用方法. 下载Spring源码,可以从官网或者GitHub上获取. 阅读Spring源码的入口类,了解Sprin ...
- 从 API 网关聊到 API 管理
在 API 管理中,通常会有这些痛点: 1.企业不清楚到底有多少个API,无法形成API资产管理等问题. 2.API在不同集群的生命周期问题. 3.API运行状态监控和告警问题. 4.API请求限流. ...
- [C++提高编程] 2、STL初识
文章目录 2 STL初识 2.1 STL的诞生 2.2 STL基本概念 2.3 STL六大组件 2.4 STL中容器.算法.迭代器 2.5 容器算法迭代器初识 2.5.1 vector存放内置数据类型 ...