Mybatis的基础使用以及与Spring的相关集成在官方文档都写的非常详细,但无论我们采用xml还是注解方式在使用的过程中经常会出现各种奇怪的问题,需要花费大量的时间解决。

抽空了解一下Mybatis的相关源码还是很有必要。

  先来看一个简单的Demo:

@Test
public void test() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
MethodInfo info = (MethodInfo) session.selectOne("com.ycdhz.mybatis.dao.MethodInfoMapper.selectById", 1);
System.out.println(info.toString());
}

  这个是官网中入门的一段代码,我根据自己的情况做了一些参数上的改动。这段代码很容易理解,解析一个xml文件,通过SqlSessionFactoryBuilder构建一个SqlSessionFactory实例。

拿到了SqlSessionFactory我们就可以获取SqlSession。SqlSession 包含了面向数据库执行 SQL 命令所需的所有方法,所以我们可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。

代码很简单的展示了Mybatis到底是什么,有什么作用。

  Mybatis主线流程:解析Configuration返回SqlSessionFactory;拿到SqlSession对执行器进行初始化 SimpleExecutor;操作数据库;

  

  我们先来看一下mybatis-config.xml,在这个xml中包含了Mybatis的核心设置,有获取数据库连接实例的数据源(DataSource)和决定事务作用域和控制方式的事务管理器(TransactionManager)等等。

具体的配置信息可以参考官方文档。需要注意的是Xml的属性配置有一定的顺序要求,具体的可以查看http://mybatis.org/dtd/mybatis-3-config.dtd。
<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<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://localhost:3306/mytest"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mybatis/MethodInfoMapper.xml"/>
<!--<mapper class="com.ycdhz.mybatis.dao.MethodInfoMapper" />-->
<!--<package name="com.ycdhz.mybatis.dao" />-->
</mappers>
</configuration>

  test()前两行主要是通过流来读取配置文件,我们直接从new SqlSessionFactoryBuilder().build(inputStream)这段代码开始:

public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
} public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
       // SqlSessionFactoryBuilde拿到输入流后,构建了一个XmlConfigBuilder的实例。通过parse()进行解析
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
} public SqlSessionFactory build(Configuration config) {
     //XmlConfigBuilder.parse()解析完后将数据传给DefaultSqlSessionFactory
return new DefaultSqlSessionFactory(config);
}
}

  

public class XMLConfigBuilder extends BaseBuilder {

    private boolean parsed;

    private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.parsed = false;
} public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
     //这个方法主要就是解析xml文件了
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
} private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
}

  mybatis-config.xml 文件中的mapper属性支持四种配置方式,但是只有package,class这两种发式支持通过注解来配置和映射原生信息(原因在于configuration.addMappers()这个方法)。

private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}

  addMappers()调用了MapperAnnotationBuilder.parse()这样一段代码。我们发现当resource不为空的时候,代码首先会调用loadXmlResource()去Resource文件夹下查找(com/ycdhz/mybatis/dao/MethodInfoMapper.xml),

如果发现当前文件就加载。但实际这个时候type信息来自注解,MethodInfoMapper.xml在容器中被加载两次。所以Configruation下的静态类StrictMap.put()时会抛出一个 Mapped Statements collection already contains value for com.ycdhz.mybatis.dao.MethodInfoMapper.selectById 的异常

public class MapperAnnotationBuilder {
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
} private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e) {
// ignore, resource is not required
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
}

  

  这个时候我们已经完成了xml文件的解析过程,拿到了DefaultSqlSessionFactory。下面我们再来看一下sqlSessionFactory.openSession()的过程:

public class DefaultSqlSessionFactory implements SqlSessionFactory{
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);
final Executor executor = configuration.newExecutor(tx, execType);
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();
}
}
} public class Configuration {
protected boolean cacheEnabled = true;
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
protected final InterceptorChain interceptorChain = new InterceptorChain(); public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
}

  

  openSession()方法会调用DefaultSqlSessionFactory.openSessionFromDataSource()方法,在这个方法中会开启事务、创建了执行器Executor:

  MyBatis的事务管理分为两种形式(配置mybatis-config.xml文件的transactionManager属性):

    1)使用JDBC的事务管理机制:即利用java.sql.Connection对象完成对事务的提交(commit())、回滚(rollback())、关闭(close())等

    2)使用MANAGED的事务管理机制:这种机制MyBatis自身不会去实现事务管理,而是让程序的容器如(JBOSS,Weblogic)来实现对事务的管理

     

  执行器ExecutorType分为三类(默认使用的是ExecutorType.SIMPLE):

    1)ExecutorType.SIMPLE: 这个执行器类型不做特殊的事情。它为每个语句的执行创建一个新的预处理语句。

    2)ExecutorType.REUSE: 这个执行器类型会复用预处理语句。

    3)ExecutorType.BATCH: 这个执行器会批量执行所有更新语句,如果 SELECT 在它们中间执行还会标定它们是 必须的,来保证一个简单并易于理解的行为。

        因为Mybatis的一级缓存是默认开启的,查看newExecutor()不难发现,最后通过CachingExecutor对SimpleExecutor进行了装饰(详细代码可以查看https://www.cnblogs.com/jiangyaxiong1990/p/9236764.html)

    

  Mybatis缓存设计成两级结构,分为一级缓存、二级缓存:(参考 https://blog.csdn.net/luanlouis/article/details/41280959 )

    1)一级缓存是Session会话级别的缓存,位于表示一次数据库会话的SqlSession对象之中,又被称之为本地缓存。默认情况下自动开启,用户没有定制它的权利(不过这也不是绝对的,可以通过开发插件对它进行修改);

      实际上MyBatis的一级缓存是使用PerpetualCache来维护的,PerpetualCache实现原理其实很简单,其内部就是通过一个简单的HashMap<k,v> 来实现的,没有其他的任何限制。

    2)二级缓存是Application应用级别的缓存,它的是生命周期很长,跟Application的声明周期一样,也就是说它的作用范围是整个Application应用。

     MyBatis的二级缓存设计得比较灵活,你可以使用MyBatis自己定义的二级缓存实现;你也可以通过实现org.apache.ibatis.cache.Cache接口自定义缓存;也可以使用第三方内存缓存库,如Redis等

  

    

  

Mybatis主线流程源码解析的更多相关文章

  1. Spring IOC容器启动流程源码解析(四)——初始化单实例bean阶段

    目录 1. 引言 2. 初始化bean的入口 3 尝试从当前容器及其父容器的缓存中获取bean 3.1 获取真正的beanName 3.2 尝试从当前容器的缓存中获取bean 3.3 从父容器中查找b ...

  2. Hadoop中Yarnrunner里面submit Job以及AM生成 至Job处理过程源码解析

    参考 http://blog.csdn.net/caodaoxi/article/details/12970993 Hadoop中Yarnrunner里面submit Job以及AM生成 至Job处理 ...

  3. Dubbo服务调用过程源码解析④

    目录 0.服务的调用 1.发送请求 2.请求编码 3.请求的解码 4.调用具体服务 5.返回调用结果 6.接收调用结果 Dubbo SPI源码解析① Dubbo服务暴露源码解析② Dubbo服务引用源 ...

  4. Android开发——View绘制过程源码解析(二)

    0. 前言   View的绘制流程从ViewRoot的performTraversals开始,经过measure,layout,draw三个流程,之后就可以在屏幕上看到View了.上一篇已经介绍了Vi ...

  5. Spring bean 创建过程源码解析

    在上一篇文件 Spring 中 bean 注册的源码解析 中分析了 Spring 中 bean 的注册过程,就是把配置文件中配置的 bean 的信息加载到内存中,以 BeanDefinition 对象 ...

  6. Spark作业执行流程源码解析

    目录 相关概念 概述 源码解析 作业提交 划分&提交调度阶段 提交任务 执行任务 结果处理 Reference 本文梳理一下Spark作业执行的流程. Spark作业和任务调度系统是其核心,通 ...

  7. java架构之路-(源码)mybatis执行流程源码解析

    这次我们来说说Mybatis的源码,这里只说执行的流程,内部细节太多了,这里只能授之以渔了.还是最近的那段代码,我们来回顾一下. package mybatis; import mybatis.bea ...

  8. mybatis源码/mybatis执行流程源码解析

    https://www.cnblogs.com/cxiaocai/tag/%E9%9D%A2%E8%AF%95%E9%A2%98/public SqlSession session; public S ...

  9. redis启动过程源码解析

    redis整个程序的入口函数在server.c中的main函数,函数调用关系如下图1,调用顺序为从上到下,从左至右. 图1 redis启动函数调用图 main函数源码如下,1-55行根据配置文件和启动 ...

随机推荐

  1. 数组转换list集合问题

    1问题的起因: 项目总要使用一个方法把数组转换成list集合,百度说有这个方法Arrays.asList可以实现 2结果报错 抛出java.lang.UnsupportedOperationExcep ...

  2. centos 安装glide工具(golang)笔记

    参照官网 https://glide.sh/ 我是在官网(https://glide.sh/)中下载的release包,因为使用命令:curl https://glide.sh/get | sh 无法 ...

  3. leetcode 刷题

    176:第二高的薪水 offset ) as secondhighestsalary; ---去掉第一个,再从第一个开始 177:第N高的薪水 ------相关子查询:子查询中引用了外层查询所引用表的 ...

  4. VC++下使用ADO操作数据库

    VC++下使用ADO操作数据库主要要用到 _ConnectionPtr,_CommandPtr,_RecordsetPtr三个ADO对象指针,我查找了相关资料,发现网上源码很多,但是都相对凌乱,于是自 ...

  5. Common administrative commands in Red Hat Enterprise Linux 5, 6, and 7

    https://access.redhat.com/articles/1189123 Common administrative commands in Red Hat Enterprise Linu ...

  6. show tables from information_schema/performance_schema/sys;

    root@localhost:3306.sock [performance_schema]>select version();+------------+| version()  |+----- ...

  7. Ubuntu安装 和 python开发

    在ubuntu上安装pycharm 可以好几种下载办法 1.pycharm之linux版本下载地址: https://download.jetbrains.8686c.com/python/pycha ...

  8. POST请求上传多张图片并携带参数

    POST请求上传多张图片并携带参数 在iOS中,用POST请求携带参数上传图片是非常恶心的事情,HTTPBody部分完全需要我们自己来配置,这个HTTPBody分为3个部分,头部分可以携带参数,中间部 ...

  9. [C++] 用Xcode来写C++程序[1] 新建C++项目工程

    用Xcode来写C++程序[1] 新建C++项目工程 第一节从新建工程并编译C++源码开始 新建工程 源码: // // main.cpp // YeHelloWorld // // Created ...

  10. playfair

    又是一道实验吧的题,哈哈,我比较弱. 因为题目写了play我首先想到的是playfair,好,下面先看下百科 好了,已知了密钥: 所以有: s n f m th b g o ui c j p vy d ...