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

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

  先来看一个简单的Demo:

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

  这个是官网中入门的一段代码,我根据自己的情况做了一些参数上的改动。这段代码很容易理解,解析一个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。
  1. <!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>
  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE configuration
  3. PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-config.dtd">
  5. <configuration>
  6. <environments default="development">
  7. <environment id="development">
  8. <transactionManager type="JDBC"/>
  9. <dataSource type="POOLED">
  10. <property name="driver" value="com.mysql.jdbc.Driver"/>
  11. <property name="url" value="jdbc:mysql://localhost:3306/mytest"/>
  12. <property name="username" value="root"/>
  13. <property name="password" value="root"/>
  14. </dataSource>
  15. </environment>
  16. </environments>
  17. <mappers>
  18. <mapper resource="mybatis/MethodInfoMapper.xml"/>
  19. <!--<mapper class="com.ycdhz.mybatis.dao.MethodInfoMapper" />-->
  20. <!--<package name="com.ycdhz.mybatis.dao" />-->
  21. </mappers>
  22. </configuration>

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

  1. public class SqlSessionFactoryBuilder {
  2. public SqlSessionFactory build(InputStream inputStream) {
  3. return build(inputStream, null, null);
  4. }
  5.  
  6. public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  7. try {
  8.        // SqlSessionFactoryBuilde拿到输入流后,构建了一个XmlConfigBuilder的实例。通过parse()进行解析
  9. XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
  10. return build(parser.parse());
  11. } catch (Exception e) {
  12. throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  13. } finally {
  14. ErrorContext.instance().reset();
  15. try {
  16. inputStream.close();
  17. } catch (IOException e) {
  18. // Intentionally ignore. Prefer previous error.
  19. }
  20. }
  21. }
  22.  
  23. public SqlSessionFactory build(Configuration config) {
  24.      //XmlConfigBuilder.parse()解析完后将数据传给DefaultSqlSessionFactory
  25. return new DefaultSqlSessionFactory(config);
  26. }
  27. }

  

  1. public class XMLConfigBuilder extends BaseBuilder {
  2.  
  3. private boolean parsed;
  4.  
  5. private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
  6. super(new Configuration());
  7. ErrorContext.instance().resource("SQL Mapper Configuration");
  8. this.parsed = false;
  9. }
  10.  
  11. public Configuration parse() {
  12. if (parsed) {
  13. throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  14. }
  15. parsed = true;
         //这个方法主要就是解析xml文件了
  16. parseConfiguration(parser.evalNode("/configuration"));
  17. return configuration;
  18. }
  19.  
  20. private void parseConfiguration(XNode root) {
  21. try {
  22. //issue #117 read properties first
  23. propertiesElement(root.evalNode("properties"));
  24. Properties settings = settingsAsProperties(root.evalNode("settings"));
  25. loadCustomVfs(settings);
  26. typeAliasesElement(root.evalNode("typeAliases"));
  27. pluginElement(root.evalNode("plugins"));
  28. objectFactoryElement(root.evalNode("objectFactory"));
  29. objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
  30. reflectorFactoryElement(root.evalNode("reflectorFactory"));
  31. settingsElement(settings);
  32. // read it after objectFactory and objectWrapperFactory issue #631
  33. environmentsElement(root.evalNode("environments"));
  34. databaseIdProviderElement(root.evalNode("databaseIdProvider"));
  35. typeHandlerElement(root.evalNode("typeHandlers"));
  36. mapperElement(root.evalNode("mappers"));
  37. } catch (Exception e) {
  38. throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  39. }
  40. }
  41. }

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

  1. private void mapperElement(XNode parent) throws Exception {
  2. if (parent != null) {
  3. for (XNode child : parent.getChildren()) {
  4. if ("package".equals(child.getName())) {
  5. String mapperPackage = child.getStringAttribute("name");
  6. configuration.addMappers(mapperPackage);
  7. } else {
  8. String resource = child.getStringAttribute("resource");
  9. String url = child.getStringAttribute("url");
  10. String mapperClass = child.getStringAttribute("class");
  11. if (resource != null && url == null && mapperClass == null) {
  12. ErrorContext.instance().resource(resource);
  13. InputStream inputStream = Resources.getResourceAsStream(resource);
  14. XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
  15. mapperParser.parse();
  16. } else if (resource == null && url != null && mapperClass == null) {
  17. ErrorContext.instance().resource(url);
  18. InputStream inputStream = Resources.getUrlAsStream(url);
  19. XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
  20. mapperParser.parse();
  21. } else if (resource == null && url == null && mapperClass != null) {
  22. Class<?> mapperInterface = Resources.classForName(mapperClass);
  23. configuration.addMapper(mapperInterface);
  24. } else {
  25. throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
  26. }
  27. }
  28. }
  29. }
  30. }

  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 的异常

  1. public class MapperAnnotationBuilder {
  2. public void parse() {
  3. String resource = type.toString();
  4. if (!configuration.isResourceLoaded(resource)) {
  5. loadXmlResource();
  6. configuration.addLoadedResource(resource);
  7. assistant.setCurrentNamespace(type.getName());
  8. parseCache();
  9. parseCacheRef();
  10. Method[] methods = type.getMethods();
  11. for (Method method : methods) {
  12. try {
  13. // issue #237
  14. if (!method.isBridge()) {
  15. parseStatement(method);
  16. }
  17. } catch (IncompleteElementException e) {
  18. configuration.addIncompleteMethod(new MethodResolver(this, method));
  19. }
  20. }
  21. }
  22. parsePendingMethods();
  23. }
  24.  
  25. private void loadXmlResource() {
  26. // Spring may not know the real resource name so we check a flag
  27. // to prevent loading again a resource twice
  28. // this flag is set at XMLMapperBuilder#bindMapperForNamespace
  29. if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
  30. String xmlResource = type.getName().replace('.', '/') + ".xml";
  31. InputStream inputStream = null;
  32. try {
  33. inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
  34. } catch (IOException e) {
  35. // ignore, resource is not required
  36. }
  37. if (inputStream != null) {
  38. XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
  39. xmlParser.parse();
  40. }
  41. }
  42. }
  43. }

  

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

  1. public class DefaultSqlSessionFactory implements SqlSessionFactory{
  2. private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  3. Transaction tx = null;
  4. try {
  5. final Environment environment = configuration.getEnvironment();
  6. final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
  7. tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
  8. final Executor executor = configuration.newExecutor(tx, execType);
  9. return new DefaultSqlSession(configuration, executor, autoCommit);
  10. } catch (Exception e) {
  11. closeTransaction(tx); // may have fetched a connection so lets call close()
  12. throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
  13. } finally {
  14. ErrorContext.instance().reset();
  15. }
  16. }
  17. }
  18.  
  19. public class Configuration {
  20. protected boolean cacheEnabled = true;
  21. protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  22. protected final InterceptorChain interceptorChain = new InterceptorChain();
  23.  
  24. public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  25. executorType = executorType == null ? defaultExecutorType : executorType;
  26. executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
  27. Executor executor;
  28. if (ExecutorType.BATCH == executorType) {
  29. executor = new BatchExecutor(this, transaction);
  30. } else if (ExecutorType.REUSE == executorType) {
  31. executor = new ReuseExecutor(this, transaction);
  32. } else {
  33. executor = new SimpleExecutor(this, transaction);
  34. }
  35. if (cacheEnabled) {
  36. executor = new CachingExecutor(executor);
  37. }
  38. executor = (Executor) interceptorChain.pluginAll(executor);
  39. return executor;
  40. }
  41. }

  

  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. canvas交互部分

    mousemove let mouse = { x: undefined, y: undefined, } // 鼠标监听事件,获取鼠标移动的相应坐标 window.addEventListener( ...

  2. [翻译] 扩张卷积 (Dilated Convolution)

    英文原文: Dilated Convolution 简单来说,扩张卷积只是运用卷积到一个指定间隔的输入.按照这个定义,给定我们的输入是一个2维图片,扩张率 k=1 是通常的卷积,k=2 的意思是每个输 ...

  3. java笔记--线程的插队行为

    对线程的插队行为的理解 在编写多线程时,会遇到让一个线程优先于其他线程运行的情况, 此时除了可以设置其优先级高于其他线程外,更直接的方式是使用Thread类的join()方法 --如果朋友您想转载本文 ...

  4. Oracle诊断工具 - ORA-4030 Troubleshooting Tool

    ORA-4030 说明Oracle服务器进程(server process)无法在操作系统(OS)上分配到足够的内存.   导致ORA-4030 的主要原因有: -物理内存不足 -OS kernel/ ...

  5. [翻译] iOSSharedViewTransition

    iOSSharedViewTransition iOS 7 based transition library for View Controllers having a Common View 基于i ...

  6. [控件] AngleGradientView

    AngleGradientView 效果 说明 1. 用源码产生带环形渐变色的view 2. 可以配合maskView一起使用 (上图中的右下角图片的效果) 源码 https://github.com ...

  7. [翻译] GCDiscreetNotificationView

    GCDiscreetNotificationView GCDiscreetNotificationView is a discreet, non-modal, notification view fo ...

  8. qt的共享内存

    https://blog.csdn.net/gdutlyp/article/details/50468677

  9. 3种web会话管理方式

    一.基于server端的session管理 在早期web应用中,通常使用服务端session来管理用户的会话.快速了解服务端session: 1) 服务端session是用户第一次访问应用时,服务器就 ...

  10. 【洛谷】【lca+结论】P3398 仓鼠找sugar

    [题目描述:] 小仓鼠的和他的基(mei)友(zi)sugar住在地下洞穴中,每个节点的编号为1~n.地下洞穴是一个树形结构.这一天小仓鼠打算从从他的卧室(a)到餐厅(b),而他的基友同时要从他的卧室 ...