mybatis 學習四 源碼分析 mybatis如何執行的一條sql


總體三部分,創建sessionfactory,創建session,執行sql獲取結果

1,創建sessionfactory

     這里其實主要做的事情就是將xml的所有配置信息轉換成一個Configuration對象,然后用這個對象組裝成factory返回。
      
     //mybatis配置文件
String resource = "conf.xml"; InputStream is = TestMybatis.class.getClassLoader().getResourceAsStream(resource); SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);

 

     1,xml轉換成Configuration對象

          根據方法找到源碼,這里可以多接受兩個參數,一會有用到
           
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
    //environment表示要使用哪個db,properties 表示資源信息 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. } } }

 

          繼續往里找源碼,找到這里,這里就是將xml中所有配置信息轉換成對象的具體方法了
           
  //將xml中的所有節點都解析成對象信息
private void parseConfiguration(XNode root) { try { propertiesElement(root.evalNode("properties")); //issue #117 read properties first typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); settingsElement(root.evalNode("settings")); environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631 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); } }

 

          主要看三個吧
          properties的轉換,從這里的邏輯可以看出來資源信息的加載順序,在之前的章節里有說過。
           
  private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
    //獲取xml里面設置的 Properties defaults
= context.getChildrenAsProperties();
    //獲取指定的資源文件里的 String resource
= context.getStringAttribute("resource"); String url = context.getStringAttribute("url"); if (resource != null && url != null) { throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other."); }
    //覆蓋掉了xml里配置的
if (resource != null) { defaults.putAll(Resources.getResourceAsProperties(resource)); } else if (url != null) { defaults.putAll(Resources.getUrlAsProperties(url)); } Properties vars = configuration.getVariables();
    //覆蓋掉了上面的
if (vars != null) { defaults.putAll(vars); } parser.setVariables(defaults); configuration.setVariables(defaults); } }

 

          environments的轉換,這里首先會看是否傳進來了一個id,如果么有,那么用默認的。
    
private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
    //如果沒有指定,那么直接使用默認的
if (environment == null) { environment = context.getStringAttribute("default"); } for (XNode child : context.getChildren()) { String id = child.getStringAttribute("id"); if (isSpecifiedEnvironment(id)) {
      //獲取事物factory TransactionFactory txFactory
= transactionManagerElement(child.evalNode("transactionManager"));
      //獲取datasourcefactory DataSourceFactory dsFactory
= dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource
= dsFactory.getDataSource(); Environment.Builder environmentBuilder = new Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); configuration.setEnvironment(environmentBuilder.build()); } } } }

 

          其次,獲取事物factory的時候,會根據配置的transactionManager屬性來獲取看是用JdbcTransactionFactory還是ManagedTransactionFactory,這里會涉及到事物的一些處理機制,兩種類處理方法不一樣的。
    
  private TransactionFactory transactionManagerElement(XNode context) throws Exception {
    if (context != null) {
    //根據type來決定要實例化哪個factory出來 String type
= context.getStringAttribute("type"); Properties props = context.getChildrenAsProperties(); TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance(); factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a TransactionFactory."); }

 

          再次,獲取datasourcefactory的時候,會根據配置的dataSource屬性來看是使用UnpooledDataSourceFactory 還是pooledDataSourceFactory ,還是JndiDataSourceFactory。這里會涉及到數據源的獲取方式,是連接池,還是單次鏈接,還是jndi。

    

  private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    if (context != null) {
    //根據type來決定實例化哪個factory String type
= context.getStringAttribute("type"); Properties props = context.getChildrenAsProperties(); DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance(); factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a DataSourceFactory."); }

 


          最重要的是mappers的轉換,這里的方法就把所有的sql語句就轉換過來了,將來要找的話,也是這里的源泉。

     2,將這個轉換好的configuration對象組裝成一個DefaultSqlSessionFactory,就可以供外部使用了。

           
    //返回sessionfactory
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

 

 

2,創建sqlsession

     1,首先從confinguration對象中,找到對應的事物factory,這里有兩種facroty。     

     一種JdbcTransactionFactory,直觀地講,就是JdbcTransaction是使用的java.sql.Connection 上的commit和rollback功能,JdbcTransaction只          是相當於對java.sql.Connection事務處理進行了一次包裝(wrapper),Transaction的事務管理都是通過java.sql.Connection實現的。
     
public void commit() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Committing JDBC Connection [" + connection + "]");
      }
    //這里就是直接使用的jdbc的提交 connection.commit(); } }
public void rollback() throws SQLException { if (connection != null && !connection.getAutoCommit()) { if (log.isDebugEnabled()) { log.debug("Rolling back JDBC Connection [" + connection + "]"); }
    //這里就是直接使用的jdbc的回滾
  connection.rollback(); } }

 

     一種是ManagedTransactionFactory, ManagedTransaction讓容器來管理事務Transaction的整個生命周期,意思就是說,使用ManagedTransaction的commit和rollback功能不會對事務有任何的影響,它什么都不會做,它將事務管理的權利移交給了容器來實現。
      
  //提交和回滾什么都不做,讓別人來管理
public void commit() throws SQLException { // Does nothing } public void rollback() throws SQLException { // Does nothing }

 

     2,然后從configuration中獲取到對應的datasource,這里就可能有三種了

     一種是UnpooledDataSourceFactory,這種的話,每次調用都會產生一次連接。耗費資源。
      
  private Connection doGetConnection(Properties properties) throws SQLException {
  //初始化驅動 initializeDriver();
  //jdbc直接獲取一個連接 Connection connection
= DriverManager.getConnection(url, properties); configureConnection(connection); return connection; }

 

     一種是pooledDataSourceFactory,這種的話,會有一個連接池的東西在里面,使用的時候每次就從連接池里用就行,
      
  //方法內容過多,可以自己去看  這里是從一個集合中pop出來一個連接直接使用。
private PooledConnection popConnection(String username, String password) throws SQLException {

 

     一種是JndiDataSourceFactory, 對於JNDI類型的數據源DataSource的獲取就比較簡單,MyBatis定義了一個JndiDataSourceFactory工廠來創建通過JNDI形式生成的DataSource。
      
    //從jndi上下文中直接獲取數據源並返回
if (properties.containsKey(INITIAL_CONTEXT) && properties.containsKey(DATA_SOURCE)) { Context ctx = (Context) initCtx.lookup(properties.getProperty(INITIAL_CONTEXT)); dataSource = (DataSource) ctx.lookup(properties.getProperty(DATA_SOURCE)); } else if (properties.containsKey(DATA_SOURCE)) { dataSource = (DataSource) initCtx.lookup(properties.getProperty(DATA_SOURCE)); }

 

     3,將得到的datasource和事物facroty一起用來,獲取一個tx。

然后用這個tx重新組裝成mybatis自己的一個執行器Executor,這里需要注意,如果二級緩存的開關開啟了,那么得到的會是一個CachingExecutor,將來執行查詢的時候,就會使用這個去執行,會先使用二級緩存,然后在一級緩存,然后再db查詢。
      
  public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {
    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, autoCommit); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }

 

     4,得到這個執行器之后,組裝成sqlsession然后返回供外部使用。

      
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
    //得到db信息
final Environment environment = configuration.getEnvironment();
    //得到事物factory
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    //根據數據源得到事物 tx
= transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    //得到執行器
final Executor executor = configuration.newExecutor(tx, execType, autoCommit);
    //執行器和配置信息對象一起組裝了session返回。
return new DefaultSqlSession(configuration, executor); } 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(); } }

 

3,執行sql查詢。

     1,首先會根據sql的id去configuration對象中找到對應的sql,返回的是一個 MappedStatement,這里面有所有這個sql的信息,

      
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
    //根據id找到對應的sql的封裝對象 statement MappedStatement ms
= configuration.getMappedStatement(statement);
    //這里的執行器有可能是緩存執行器也可能是默認執行器 List
<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); return result; } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }

 

     2,執行執行器的查詢方法,如果二級緩存開啟了那么會使用CachingExecutor的query,這里會先去這個MappedStatement里的緩存里找,如果找不到,在去執行sql,

      
 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) { 
        ensureNoOutParams(ms, parameterObject, boundSql);
        if (!dirty) {
          cache.getReadWriteLock().readLock().lock();
          try {
        //這里從二級緩存中找一下 @SuppressWarnings(
"unchecked") List<E> cachedList = (List<E>) cache.getObject(key); if (cachedList != null) return cachedList; } finally { cache.getReadWriteLock().readLock().unlock(); } }
    //執行sql查詢 List
<E> list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks return list; } } return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }

 

     3,二級緩存里如果找不到

也不會直接執行sql,會先從一級緩存里找,除非這里專門設置了flush,如果找到了,那么直接使用,找不到,執行sql查找,然后將結果設置到緩存中。這里查到數據之后,會根據自己定義的映射關系,來組裝對象並返回。
     
    //一級緩存查找
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else {
    //sqldb查找 list
= queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); }

 

 
這樣一個簡單的流程就走完了,當然還有其他很多東西,需要繼續往源碼深處研究。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM