JDBC介紹和Mybatis運行原理及事務處理

本博客內容非自創,轉載自如下三位,侵刪:java

https://juejin.im/post/5ab7bd11f265da23906bfbc5mysql

https://my.oschina.net/fifadxj/blog/785621spring

https://www.jianshu.com/p/b864aecc0de1sql

JDBC相關概念

Java程序都是經過JDBC鏈接數據庫的,經過SQL對數據庫編程,JDBC是由SUN公司提出的一些列規範,只定義了接口規範,具體實現由各個數據庫廠商去實現,它是一種典型的橋接模式。ps:橋接模式是一種結構型設計模式,它的主要特色是把抽象與行爲實現分離開來,分別定義接口,能夠保持各部分的獨立性以及應對他們的功能擴展。數據庫

JDBC規範 

所謂規範,就是本身定義了標準接口,作了以下抽象:用Connection表明和數據庫的鏈接,用Statement執行SQL,用ResultSet表示SQL返回的結果,提供了對數據的便利。從Connection能夠建立Statement,Statement執行查詢獲得ResultSet。apache

上面說的Connection、Statement、ResultSet都應該是接口,具體實現由各個數據庫提供商提供。有了規範,能夠經過統一的接口,訪問多種類型的數據庫,可隨便切換數據庫。編程

數據庫驅動

上面提到,接口的實現由各個廠商提供,那麼實現類的類名就會不統一,去建立Connection對象時,代碼就會寫死某個實現類,切換數據庫時,就須要修改代碼,這樣不太好。爲了解決這個問題,抽象了Driver驅動的概念。設計模式

Connection con=MySqlConnectionImpl("127.0.0.1",3306,"mi_user",userName,pwd);

每一個數據庫都須要實現Driver接口,經過Driver可得到數據庫鏈接Connection,經過反射機制動態建立。api

Class.forName("com.mysql.jdbc.Drier");

同一個程序可能訪問不一樣的數據庫,經過DriverManager來管理驅動,Driver在初始化的時候,須要註冊到DriverManager中。緩存

DriverManager提供了一個getConnection方法,用於創建數據庫Connection:

Connection con=DriverManager.getConnection("127.0.0.1",3306,"mi_user",userName,pwd);

若是有多個數據庫驅動,DriverManager如何區分呢,須要在數據庫鏈接url中指定,好比mysql須要添加jdbc:mysql前綴:

String url= "jdbc:mysql://127.0.0.1:3306/mi_user";
Connection con=DriverManager.getConnection(url,userName,pwd)

數據源

數據源DataSource包含鏈接池和鏈接池管理2個部分,習慣上稱爲鏈接池。在系統初始化的時候,將數據庫鏈接做爲對象存儲在內存中,當須要訪問數據庫時,從鏈接池中取出一個已創建的空閒鏈接對象。

使用數據源,獲取其DataSource對象,經過該對象動態的獲取數據庫鏈接。另外,DataSource對象能夠註冊到名字服務(JNDI)中,能夠經過名字服務得到DataSource對象,無需硬性編碼驅動。

DriverManager是JDBC1提供的,DataSource是JDBC2新增的功能,提供了更好的鏈接數據源的方法。

Mybatis核心組件:

  • SqlSessionFactoryBuilder:會根據配置信息或代碼來生成SqlSessionFactory;
  • SqlSessionFactory:依靠工廠來生成SqlSession;
  • SqlSession:是一個既能夠發送SQL去執行並返回結果,也能夠獲取Mapper的接口;
  • SQL Mapper:是MyBatis新設計的組件,由一個Java接口和XML文件構成,須要給出對應的SQL和映射規則。它負責發送SQL去執行,並返回結果。

Mybatis核心流程:

經過SqlSessionFactory獲取SqlSession

獲取MapperProxy

獲取Excutor

 

Mybatis事務:

mybatis-spring的實現很大程度上依賴spring jdbc的事務管理,因此咱們先看一下在spring中直接使用jdbc訪問數據庫時是如何處理事務的。不管你是使用@Transactional註解這樣的AOP配置方式,仍是TransactionTemplate這樣的編碼方式,最終執行的操做事務的代碼都會是相似下面這樣:
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
PlatformTransactionManager txManager = new DataSourceTransactionManager(dataSource);

TransactionStatus status = txManager.getTransaction(def);
try {
    //get jdbc connection...
    //execute sql...
}
catch (Exception e) {
    txManager.rollback(status);
    throw e;
}
txManager.commit(status);

能夠看到PlatformTransactionManager的getTransaction(), rollback(), commit()是spring處理事務的核心api,分別對應事務的開始,提交和回滾。

spring事務處理的一個關鍵是保證在整個事務的生命週期裏全部執行sql的jdbc connection和處理事務的jdbc connection始終是同一個。而後執行sql的業務代碼通常都分散在程序的不一樣地方,如何讓它們共享一個jdbc connection呢?這裏spring作了一個前提假設:即一個事務的操做必定是在一個thread中執行,且一個thread中若是有多個不一樣jdbc connection生成的事務的話,他們必須順序執行,不能同時存在。(這個假設在絕大多數狀況下都是成立的)。基於這個假設,spring在transaction建立時,會用ThreadLocal把建立這個事務的jdbc connection綁定到當前thread,接下來在事務的整個生命週期中都會從ThreadLocal中獲取同一個jdbc connection。

咱們看一下詳細調用過程

  • TransactionSynchronizationManager負責從ThreadLocal中存取jdbc connection
  • 建立事務的時候會經過dataSource.getConnection()獲取一個新的jdbc connection,而後綁定到ThreadLocal
  • 在業務代碼中執行sql時,經過DataSourceUtils.getConnection()從ThreadLocal中獲取當前事務的jdbc connection, 而後在該jdbc connection上執行sql
  • commit和rollback事務時,從ThreadLocal中獲取當前事務的jdbc connection,而後對該jdbc connection進行commit和rollback

對spring jdbc的事務處理有了瞭解後,咱們來看mybatis是如何經過spring處理事務的。

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource" />
</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="transactionFactory">
    <bean class="org.apache.ibatis.spring.transaction.SpringManagedTransactionFactory" />
  </property> 
</bean>

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
  <constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
  • mybatis-spring依賴DataSourceTransactionManager來處理事務,並無建立本身的PlatformTransactionManager實現。
  • mybatis經過SqlSessionFactoryBuilder建立SqlSessionFactory,而mybatis-spring經過SqlSessionFactoryBean建立SqlSessionFactory。
  • 配置使用SpringManagedTransactionFactory來建立MyBatis的Transaction實現SpringManagedTransaction
  • 配置使用SqlSessionTemplate代替經過SqlSessionFactory.openSession()獲取SqlSession

而後看其調用過程

能夠看到mybatis-spring處理事務的主要流程和spring jdbc處理事務並無什麼區別,都是經過DataSourceTransactionManager的getTransaction(), rollback(), commit()完成事務的生命週期管理,並且jdbc connection的建立也是經過DataSourceTransactionManager.getTransaction()完成,mybatis並無參與其中,mybatis只是在執行sql時經過DataSourceUtils.getConnection()得到當前thread的jdbc connection,而後在其上執行sql。

下面結合代碼來看

<SqlSessionUtils>:  

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Creating a new SqlSession");
    }

    session = sessionFactory.openSession(executorType);

    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }


  private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
    SqlSessionHolder holder;
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
      Environment environment = sessionFactory.getConfiguration().getEnvironment();

      if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]");
        }

        holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
        TransactionSynchronizationManager.bindResource(sessionFactory, holder);
        TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
        holder.setSynchronizedWithTransaction(true);
        holder.requested();
      } else {
        if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
          if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional");
          }
        } else {
          throw new TransientDataAccessResourceException(
              "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
        }
      }
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active");
      }
    }
}

執行sql時調用sqlSessionTemplate的insert,update,delete方法,sqlSessionTemplate是DefaultSqlSession的一個代理類,它經過SqlSessionUtils.getSqlSession()試圖從ThreadLocal獲取當前事務所使用的SqlSession。若是是第一次獲取時會調用SqlSessionFactory.openSession()建立一個SqlSession並綁定到ThreadLocal,同時還會經過TransactionSynchronizationManager註冊一個SqlSessionSynchronization。

<SqlSessionSynchronization>:

 public void beforeCommit(boolean readOnly) {
      // Connection commit or rollback will be handled by ConnectionSynchronization or
      // DataSourceTransactionManager.
      // But, do cleanup the SqlSession / Executor, including flushing BATCH statements so
      // they are actually executed.
      // SpringManagedTransaction will no-op the commit over the jdbc connection
      // TODO This updates 2nd level caches but the tx may be rolledback later on! 
      if (TransactionSynchronizationManager.isActualTransactionActive()) {
        try {
          if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Transaction synchronization committing SqlSession [" + this.holder.getSqlSession() + "]");
          }
          this.holder.getSqlSession().commit();
        } catch (PersistenceException p) {
          if (this.holder.getPersistenceExceptionTranslator() != null) {
            DataAccessException translated = this.holder
                .getPersistenceExceptionTranslator()
                .translateExceptionIfPossible(p);
            if (translated != null) {
              throw translated;
            }
          }
          throw p;
        }
      }

SqlSessionSynchronization是一個事務生命週期的callback接口,mybatis-spring經過SqlSessionSynchronization在事務提交和回滾前分別調用DefaultSqlSession.commit()和DefaultSqlSession.rollback()

<BaseExecutor>:

public void commit(boolean required) throws SQLException {
    if (closed) throw new ExecutorException("Cannot commit, transaction is already closed");
    clearLocalCache();
    flushStatements();
    if (required) {
      transaction.commit();
    }
  }

  public void rollback(boolean required) throws SQLException {
    if (!closed) {
      try {
        clearLocalCache();
        flushStatements(true);
      } finally {
        if (required) {
          transaction.rollback();
        }
      }
    }
  }

  public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }
<SpringManagedTransaction>:

this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);  

  public void commit() throws SQLException {
    if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Committing JDBC Connection [" + this.connection + "]");
      }
      this.connection.commit();
    }
  }

  public void rollback() throws SQLException {
    if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Rolling back JDBC Connection [" + this.connection + "]");
      }
      this.connection.rollback();
    }
  }
<DataSourceUtils>:

    /**
     * Determine whether the given JDBC Connection is transactional, that is,
     * bound to the current thread by Spring's transaction facilities.
     * @param con the Connection to check
     * @param dataSource the DataSource that the Connection was obtained from
     * (may be {@code null})
     * @return whether the Connection is transactional
     */
    public static boolean isConnectionTransactional(Connection con, DataSource dataSource) {
        if (dataSource == null) {
            return false;
        }
        ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
        return (conHolder != null && connectionEquals(conHolder, con));
    }

這裏的DefaultSqlSession只會進行一些自身緩存的清理工做,並不會真正提交事務給數據庫,緣由是這裏的DefaultSqlSession使用的Transaction實現爲SpringManagedTransaction,SpringManagedTransaction在提交事務前會檢查當前事務是否應該由spring控制,若是是,則不會本身提交事務,而將提交事務的任務交給spring,因此DefaultSqlSession並不會本身處理事務。

<SpringManagedTransaction>: 

 public Connection getConnection() throws SQLException {
    if (this.connection == null) {
      openConnection();
    }
    return this.connection;
  }

  /**
   * Gets a connection from Spring transaction manager and discovers if this
   * {@code Transaction} should manage connection or let it to Spring.
   * <p>
   * It also reads autocommit setting because when using Spring Transaction MyBatis
   * thinks that autocommit is always false and will always call commit/rollback
   * so we need to no-op that calls.
   */
  private void openConnection() throws SQLException {
    this.connection = DataSourceUtils.getConnection(this.dataSource);
    this.autoCommit = this.connection.getAutoCommit();
    this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug(
          "JDBC Connection ["
              + this.connection
              + "] will"
              + (this.isConnectionTransactional ? " " : " not ")
              + "be managed by Spring");
    }
  }

DefaultSqlSession執行sql時,會經過SpringManagedTransaction調用DataSourceUtils.getConnection()從ThreadLocal中獲取jdbc connection並在其上執行sql。

總結:mybatis-spring處理事務的主要流程和spring jdbc處理事務並無什麼區別,都是經過DataSourceTransactionManager的getTransaction(), rollback(), commit()完成事務的生命週期管理,並且jdbc connection的建立也是經過DataSourceTransactionManager.getTransaction()完成,mybatis並無參與其中,mybatis只是在執行sql時經過DataSourceUtils.getConnection()得到當前thread的jdbc connection,而後在其上執行sql。

mybatis-spring作的最主要的事情是:

  1. 在SqlSession執行sql時經過用SpringManagedTransaction代替mybatis的JdbcTransaction,讓SqlSession從spring的ThreadLocal中獲取jdbc connection。
  2. 經過註冊事務生命週期callback接口SqlSessionSynchronization,讓SqlSession有機會在spring管理的事務提交或回滾時清理本身的內部緩存。
相關文章
相關標籤/搜索