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. 你写的什么垃圾代码让Vsync命令不能及时处理呢?(1)

    想想自己写的什么垃圾代码导致Vsync不能及时处理#(不高兴) 想不开? 实际开发中性能问题不好复现?这你就可能需要一些工具来帮你检测这种情况. 首先是Android系统自带的工具(4.1之后的版本) ...

  2. 初次使用git就遭遇不测,提示没有这个服务连接和需要配置git的一个http参数 NO network connection,SSl host could not be verified ...

    第一次使用git 拉取服务上的项目到本地,结果,在拿到访问的url地址后,输入用户名密码,失败了. --eclispe  4.5.3 继承了git客户端插件的版本 ------下一步后,报错 NO n ...

  3. 产品经理都知道MVP,但是它可能不再是产品研发最好的模型了

    产品经理都知道MVP,但是它可能不再是产品研发最好的模型了 孟小白Aspire • 2017-09-01 • 汽车交通 要简单.讨喜.完整,不要最小可行性产品.这对创业公司的第一个产品来说很重要. M ...

  4. asp.net 一般处理程序实现网站验证码

    使用VerifyCode.ashx一般处理程序生成验证码,实现如下: using System; using System.Drawing; using System.Web; using Syste ...

  5. Webapi通过报文获取post上来的数据

    public HttpResponseMessage Post(HttpRequestMessage req) { var content = req.Content.ReadAsStringAsyn ...

  6. REST Framework 的用户认证组件

    用户认证流程: 我们要知道这个流程是怎么走的? 认证之后做的什么? 怎么认证?这三个条件 认证流程:就是使用BaseAuthentication这个模块来做认证,判断你登陆成功传递过来的随机字符串是否 ...

  7. [翻译] M13ProgressSuite

    M13ProgressSuite https://github.com/Marxon13/M13ProgressSuite A set of classes used to display progr ...

  8. 铁乐学Python_day09_函数

    今天我们来学习函数. 产生的原由,若没有函数,会显得重复代码多,可读性差,也会造成重复造轮子的情景. 故产生了函数,用来封装好一个功能,它是以功能为导向的. 1.[函数的样式] 例如自己定义一个函数: ...

  9. September 20th 2017 Week 38th Wednesday

    All our dreams can come true if we have the courage to pursue them. 如果我们有勇气去追求梦想,我们的梦想一定可以成为现实. If y ...

  10. TeamViewer app案例分析

    产品 产品名 TeamViewer远程app 选择原因 远程连接软件是不时之需,当有时私人电脑没有在身边而又需要操作电脑时,远程控制TeamViewer这个软件能帮我们大忙. 调研与评测 1.第一次上 ...