Mybatis主線流程源碼解析

   Mybatis的基礎使用以及與Spring的相關集成在官方文檔都寫的很是詳細,但不管咱們採用xml仍是註解方式在使用的過程當中常常會出現各類奇怪的問題,須要花費大量的時間解決。html

抽空了解一下Mybatis的相關源碼仍是頗有必要。java

  先來看一個簡單的Demo:mysql

@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實例。sql

拿到了SqlSessionFactory咱們就能夠獲取SqlSession。SqlSession 包含了面向數據庫執行 SQL 命令所需的全部方法,因此咱們能夠經過 SqlSession 實例來直接執行已映射的 SQL 語句。數據庫

代碼很簡單的展現了Mybatis究竟是什麼,有什麼做用。apache

 

  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)這段代碼開始:session

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()這個方法)。mybatis

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),app

若是發現當前文件就加載。但實際這個時候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等

  

相關文章
相關標籤/搜索