天天看點

MyBatis原理分析之擷取SqlSession

流程原理分析系列:

MyBatis原理分析之擷取SqlSessionFactory

MyBatis原理分析之擷取SqlSessionMyBatis原理分析之擷取

Mapper接口的代理對象MyBatis原理分析之查詢單個對象

擷取sqlsession主要是通過SqlSessionFactory的幾個重載方法,從configuration中的environment擷取datasource與transactionFactory來得到Transaction。然後得到Transaction、Executor與DefaultSqlSession。

mybatis全局配置檔案中environments 結點配置如下

<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/mybatis" />
        <property name="username" value="root" />
        <property name="password" value="123456" />
      </dataSource>
    </environment>
  </environments>      

時序圖如下所示:

MyBatis原理分析之擷取SqlSession

【1】DefaultSqlSessionFactory

SqlSessionFactory 有六個方法建立 SqlSession 執行個體。通常來說,當你選擇其中一個方法時,你需要考慮以下幾點:

  • 事務處理:你希望在 session 作用域中使用事務作用域,還是使用自動送出(auto-commit)?(對很多資料庫和/或 JDBC 驅動來說,等同于關閉事務支援)
  • 資料庫連接配接:你希望 MyBatis 幫你從已配置的資料源擷取連接配接,還是使用自己提供的連接配接?
  • 語句執行:你希望 MyBatis 複用 PreparedStatement 和/或批量更新語句(包括插入語句和删除語句)嗎?

基于以上需求,有下列已重載的多個 openSession() 方法供使用。

SqlSession openSession()
SqlSession openSession(boolean autoCommit)
SqlSession openSession(Connection connection)
SqlSession openSession(TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType)
SqlSession openSession(ExecutorType execType, boolean autoCommit)
SqlSession openSession(ExecutorType execType,TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType, Connection connection)
Configuration getConfiguration();      

預設的 openSession() 方法沒有參數,它會建立具備如下特性的 SqlSession:

  • 事務作用域将會開啟(也就是不自動送出)。
  • 将由目前環境配置的 DataSource 執行個體中擷取 Connection 對象。
  • 事務隔離級别将會使用驅動或資料源的預設設定。
  • 預處理語句不會被複用,也不會批量處理更新。
相信你已經能從方法簽名中知道這些方法的差別。向 autoCommit 可選參數傳遞 true 值即可開啟自動送出功能。若要使用自己的 Connection 執行個體,傳遞一個 Connection 執行個體給 connection 參數即可。注意,我們沒有提供同時設定 Connection 和 autoCommit 的方法,這是因為 MyBatis 會依據傳入的 Connection 來決定是否啟用 autoCommit。對于事務隔離級别,MyBatis 使用了一個 Java 枚舉包裝器來表示,稱為 TransactionIsolationLevel,事務隔離級别支援 JDBC 的五個隔離級别(NONE、READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ 和 SERIALIZABLE),并且與預期的行為一緻。

你可能對 ExecutorType 參數感到陌生。這個枚舉類型定義了三個值:

  • ExecutorType.SIMPLE:該類型的執行器沒有特别的行為。它為每個語句的執行建立一個新的預處理語句。
  • ExecutorType.REUSE:該類型的執行器會複用預處理語句。
  • ExecutorType.BATCH:該類型的執行器會批量執行所有更新語句,如果 SELECT 在多個更新中間執行,将在必要時将多條更新語句分隔開來,以友善了解。
提示 在 SqlSessionFactory 中還有一個方法我們沒有提及,就是 getConfiguration()。這個方法會傳回一個 Configuration 執行個體,你可以在運作時使用它來檢查 MyBatis 的配置。

提示 如果你使用過 MyBatis 的舊版本,可能還記得 session、事務和批量操作是互相獨立的。在新版本中則不是這樣。上述三者都包含在 session 作用域内。你不必分别處理事務或批量操作就能得到想要的全部效果。

關于SqlSessionFactory和SqlSession更多詳情參考博文:MyBatis中SqlSessionFactory和SqlSession簡解

下面我們分析​

​openSessionFromDataSource​

​。

① 核心方法openSessionFromDataSource

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);
     // 根據不同的execTYPE建立對應的executor執行個體
     final Executor executor = configuration.newExecutor(tx, execType);
     
     // 根據configuration executor以及autoCommit 建立DefaultSqlSession執行個體
     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();
   }
 }      

代碼解釋如下:

  • ① 從configuration擷取environment執行個體,主要有屬性datasource、id、transactionFactory
    MyBatis原理分析之擷取SqlSession
  • ②​

    ​transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit)​

    ​擷取事務執行個體對象Transaction
  • ③​

    ​Executor executor = configuration.newExecutor(tx, execType)​

    ​擷取執行器執行個體對象;
  • ④ 建立DefaultSqlSession執行個體對象,​

    ​new DefaultSqlSession(configuration, executor, autoCommit)​

② 核心方法getTransactionFactoryFromEnvironment

​configuration​

​​執行個體的​

​TypeAliasRegistry​

​​中關于​

​DataSource​

​​與​

​TransactionFactory​

​預設配置如下:

jdbc=class org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory, 
managed=class org.apache.ibatis.transaction.managed.ManagedTransactionFactory, 

unpooled=class org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory, 
pooled=class org.apache.ibatis.datasource.pooled.PooledDataSourceFactory, 
jndi=class org.apache.ibatis.datasource.jndi.JndiDataSourceFactory,      

如下方法所示,如果mybatis全局配置檔案中​

​environments​

​​結點沒有配置​

​transactionManager​

​​則會預設建立一個​

​ManagedTransactionFactory​

​執行個體對象。

private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
   if (environment == null || environment.getTransactionFactory() == null) {
     return new ManagedTransactionFactory();
   }
   return environment.getTransactionFactory();
}      

③ JdbcTransactionFactory 和JdbcTransaction

public class JdbcTransactionFactory implements TransactionFactory {

  @Override
  public Transaction newTransaction(Connection conn) {
    return new JdbcTransaction(conn);
  }

  @Override
  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    return new JdbcTransaction(ds, level, autoCommit);
  }
}      

JdbcTransaction 主要屬性與構造方法

public class JdbcTransaction implements Transaction {

  private static final Log log = LogFactory.getLog(JdbcTransaction.class);

  protected Connection connection;
  protected DataSource dataSource;
  //失物隔離級别
  protected TransactionIsolationLevel level; 
  //是否自動送出
  protected boolean autoCommit;

  public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
    dataSource = ds;
    level = desiredLevel;
    autoCommit = desiredAutoCommit;
  }
  //...
}      

【2】configuration.newExecutor(tx, execType)

這裡是很關鍵的一步,會建立目前sqlsession對應的執行器Executor,并對其進行攔截器鍊包裝(mybatis提供的插件支援)。

四大對象:Executor,StatementHandler,ParameterHandler,ResultSetHandler

四大對象的每個對象在建立時,都會執行interceptorChain.pluginAll(),會經過每個插件對象的 plugin()方法,目的是為目前的四大對象建立代理。代理對象就可以攔截到四大對象相關方法的執行,因為要執行四大對象的方法需要經過代理 。

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;
  }      

代碼解釋如下:

  • ① 擷取執行器類型,預設為​

    ​SIMPLE​

    ​​ (有​

    ​SIMPLE, REUSE, BATCH​

    ​三種);
  • ② 根據executorType建立對應的Executor執行個體對象;
  • ③ 如果開啟了二級緩存配置,則建立CachingExecutor;
CachingExecutor有成員屬性Executor delegate,主要工作交給delegate來做。CachingExecutor是在②中建立的Executor執行個體對象基礎上做了緩存處理。這裡是委派模式的應用
  • ④​

    ​interceptorChain.pluginAll(executor)​

    ​對最後得到的Executor執行個體對象進行攔截器鍊包裝

BatchExecutor|ReuseExecutor|SimpleExecutor|CachingExecutor

SimpleExecutor主要屬性與構造方法

public class SimpleExecutor extends BaseExecutor {

  public SimpleExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }
//...
}      

BatchExecutor 主要屬性與構造方法

public class BatchExecutor extends BaseExecutor {

  public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002;

  private final List<Statement> statementList = new ArrayList<>();
  private final List<BatchResult> batchResultList = new ArrayList<>();
  private String currentSql;
  private MappedStatement currentStatement;

  public BatchExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }
//...
}      

ReuseExecutor 主要屬性與構造方法

public class ReuseExecutor extends BaseExecutor {
  private final Map<String, Statement> statementMap = new HashMap<>();
  public ReuseExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }
  //...
}      

可以發現上述幾個​

​Exceutor​

​​的構造方法都調用了​

​super(configuration, transaction);​

​,其父類是抽象類BaseExecutor:

public abstract class BaseExecutor implements Executor {

  private static final Log log = LogFactory.getLog(BaseExecutor.class);
  //事務
  protected Transaction transaction;
  //執行器包裝器,比如CachingExecutor 可能是SimpleExecutor的包裝對象(也可能是其他)
  protected Executor wrapper;

  protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
  //本地緩存-一級緩存
  protected PerpetualCache localCache;
  //OutputParameter該類型的本地緩存
  protected PerpetualCache localOutputParameterCache;
  //全局配置執行個體對象configuration
  protected Configuration configuration;

  protected int queryStack;
  private boolean closed;

  protected BaseExecutor(Configuration configuration, Transaction transaction) {
    this.transaction = transaction;
    this.deferredLoads = new ConcurrentLinkedQueue<>();
    this.localCache = new PerpetualCache("LocalCache");
    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
    this.closed = false;
    this.configuration = configuration;
    this.wrapper = this;
  }
 //...
}      

CachingExecutor 主要屬性與構造方法

public class CachingExecutor implements Executor {

  private final Executor delegate;
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();

  //委派模式
  public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
  }
  //...
}      

CachingExecutor是在其他Executor 執行個體對象的基礎上做了包裝,執行個體了自己的事務管理器TransactionalCacheManager。CachingExecutor​

​通常将資料行為**委派**給其成員執行個體Executor delegate處理​

​。

這裡CachingExecutor 應用了設計模式中的裝飾者模式(Decorator Pattern)/包裝器模式(Wrapper Pattern)

interceptorChain.pluginAll(executor)

InterceptorChain 攔截器鍊對象,源碼如下

public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<>();

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }
}      

其核心方法​

​Object pluginAll(Object target)​

​ 就是對target進行攔截器鍊的層層代理,最後傳回一個代理對象。如下代碼所示就是對executor進行攔截器鍊包裝然後傳回,這也是mybatis對插件開發的支援。

executor = (Executor) interceptorChain.pluginAll(executor);      

不隻是executor執行個體化時進行了插件包裝,如下圖所示ParameterHandler、ResultSetHandler和StatementHandler執行個體化時都進行了插件包裝。

MyBatis原理分析之擷取SqlSession

​target = interceptor.plugin(target);​

這裡以mybatisplus的分頁插件PaginationInterceptor 為例說明。

如下所示,可以看到PaginationInterceptor 标明了對​

​StatementHandler.class​

​​的​

​prepare​

​​方法感興趣。那麼在執行個體化StatementHandler時觸發​

​Object pluginAll(Object target)​

​就會調用PaginationInterceptor 的plugin方法對StatementHandler進行代理。

@Setter
@Accessors(chain = true)
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class PaginationInterceptor extends AbstractSqlParserHandler implements Interceptor {
//...
}      

PaginationInterceptor 的plugin方法

public Object plugin(Object target) {
     if (target instanceof StatementHandler) {
         return Plugin.wrap(target, this);
     }
     return target;
 }      

可以看到,如果target類型是StatementHandler,将會執行​

​Plugin.wrap(target, this);​

​。

這裡面target了解為StatementHandler執行個體,this為目前PaginationInterceptor 執行個體

Plugin的wrap方法

Plugin主要屬性和構造方法

public class Plugin implements InvocationHandler {

  //目标對象  
  private final Object target;
  //攔截器對象
  private final Interceptor interceptor;
  //攔截器感興趣的類型與方法
  private final Map<Class<?>, Set<Method>> signatureMap;
 
  private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
    this.target = target;
    this.interceptor = interceptor;
    this.signatureMap = signatureMap;
  }      

wrap方法擷取代理對象

//假設這裡面target了解為StatementHandler執行個體,interceptor為目前PaginationInterceptor 執行個體
public static Object wrap(Object target, Interceptor interceptor) {
  Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
  Class<?> type = target.getClass();
  //type 是target類型  signatureMap是攔截器的
  //擷取interceptor感興趣的類型,該類型與type的接口類型是同一類型
  Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
  if (interfaces.length > 0) {
    return Proxy.newProxyInstance(
        type.getClassLoader(),
        interfaces,
        new Plugin(target, interceptor, signatureMap));
  }
  return target;
}      

代碼解釋如下:

  • ① 根據interceptor類上​

    ​@Intercepts​

    ​​注解擷取​

    ​Map<Class<?>, Set<Method>> signatureMap​

    ​;
  • ② 擷取target的Class類型;
  • ③ 從①和②中确定interceptor支援的接口類型;
  • ④​

    ​Proxy.newProxyInstance​

    ​建立代理對象