mybatis執行流程原理

工作流程:

public static void main(String[] args) throws IOException {
    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    List<User> userList = userMapper.getUserByUsername("admin");
}

1、獲取配置文件mybatis-config.xml,由SqlSessionFactoryBuilder對象,讀取並解析配置文件,返回SqlSessionFactory對象

2、由SqlSessionFactory創建SqlSession 對象,沒有手動設置的話事務默認開啓

3、調用SqlSession中的api,獲取指定UserMapper.class文件

4、由jdk動態代理,獲取到UserMapper.xml文件,內部進行復雜的處理,最後調用jdbc執行SQL語句,封裝結果返回。

具體實現:

1.1、由SqlSessionFactoryBuilder讀取xml配置文件,解析成Configuration對象

//在創建XMLConfigBuilder時,它的構造方法中解析器XPathParser已經讀取了配置文件
public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //parser是XPathParser解析器對象,讀取節點內數據,<configuration>是MyBatis配置文件中的頂層標籤
    parseConfiguration(parser.evalNode("/configuration"));
    //最後返回的是Configuration 對象
    return configuration;
}

//此方法中讀取了各個標籤內容並封裝到Configuration中的屬性中。
private void parseConfiguration(XNode root) {
    try {
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      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);
    }
}

1.2、根據Configuration創建SqlSessionFactory

public SqlSessionFactory build(Configuration config) {
	//創建了DefaultSqlSessionFactory對象,傳入Configuration對象。
    return new DefaultSqlSessionFactory(config);
  }

2.1、根據SqlSessionFactory創建會話DefaultSqlSession對象

// 創建SqlSession會話
public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

/** 
 * 
 * ExecutorType:執行器的類型
 * level:事務級別
 * autoCommit:是否自動提交事務
 */
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();
    }
}

2.2、事務創建:

// getTransactionFactoryFromEnvironment,根據配置的數據源驅動獲取相對應的事務
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment){
    if (environment == null || environment.getTransactionFactory() == null) {
        return new ManagedTransactionFactory();
    }
    return environment.getTransactionFactory();
}
// 比如配置mysql數據源,在初始化Configuration時,指定了jdbc事務工廠。
public Configuration() {
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    // ....省略N個配置項
}


// transactionFactory.newTransaction創建事務,存儲連接、數據源、事務級別等信息
public enum TransactionIsolationLevel {
 	NONE(Connection.TRANSACTION_NONE),	// 無事務
    READ_COMMITTED(Connection.TRANSACTION_READ_COMMITTED), // 不可重複讀
    READ_UNCOMMITTED(Connection.TRANSACTION_READ_UNCOMMITTED),	// 讀未提交
    REPEATABLE_READ(Connection.TRANSACTION_REPEATABLE_READ),	// 可重複讀
    SERIALIZABLE(Connection.TRANSACTION_SERIALIZABLE);	// 串行化
}

2.3、執行機創建

// 根據事務和執行機類型創建執行機(默認不指定則爲SimpleExecutor)
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;
}

// 執行機加入到每個攔截器executor = (Executor) interceptorChain.pluginAll(executor);
// 說明這個執行器需要被各個攔截器攔截處理
private final List<Interceptor> interceptors = new ArrayList<>(); // 所有的攔截器
public Object pluginAll(Object target) {// target執行機
    for (Interceptor interceptor : interceptors) {
        target = interceptor.plugin(target);
    }
    return target;
}

2.3.1、執行機區別

a、SimpleExecutor:每執行一次update或select,就開啓一個Statement對象,用完立刻關閉Statement對象    
b、ReuseExecutor:執行update或select,以sql作爲key查找Statement對象,存在就使用,不存在就創建,用完後,不關閉Statement對象,而是放置於Map內,供下一次使用。簡言之,就是重複使用Statement對象  

c、BatchExecutor:執行update(沒有select,JDBC批處理不支持select),將所有sql都添加到批處理中addBatch(),等待統一執行executeBatch(),它緩存了多個Statement對象,每個Statement對象都是addBatch()完畢後,等待逐一執行executeBatch()批處理。與JDBC批處理相同
作用範圍:SqlSession生命週期範圍內

d、CachingExecutor:默認開啓,同個作用域下的緩存執行機,使用內存的,將數據保存到緩存中,這樣可以有效的解決增刪改查性能

2.3.2、攔截器

// 攔截器可以自定義,但是隻能定義以下幾種類,因爲Configuration只加載這幾類
// 默認的攔截器順序:Executor -> ParameterHandler -> StatementHandler -> ResultSetHandler
// 第一種:源碼對ParameterHandler類的處理,對參數的攔截
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
}
// 第二種:源碼對ResultSetHandler類的處理,對結果集的攔截,可以對返回的結果進行處理
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
                                            ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
}
// 第三種:源碼對StatementHandler類的處理,對查詢前的攔截,可以對最終sql作修改
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
}

自定義實現攔截器

@Intercepts({@Signature(
    type = StatementHandler.class, 
    method = "prepare", 
    args = {Connection.class, Integer.class})})
public class SqlStatusInterceptor implements Interceptor {// 實現接口方法}

2.4、創建DefaultSqlSession

// 默認爲DefaultSqlSession,頂級接口SqlSession,作用是操作sql命令、獲取mapper和管理事務
new DefaultSqlSession(configuration, executor, autoCommit);

3.1、根據jdk動態代理獲取指定Mapper

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

// 會根據mapperRegistry這個map獲取對應的UserMapper.xml
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
}
// 而mapperRegistry內部的關鍵代碼
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    // ....
	mapperProxyFactory.newInstance(sqlSession);
    // ....
}
// mapperProxyFactory.newInstance(sqlSession); 
// 此處內部由MapperProxyFactory創建jdk的代理對象(Proxy對象),
protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
// 而knownMappers對象則是在加載配置文件的時候動態addMapper進去的
public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
        addMapper(mapperClass);
    }
}

至此,根據SqlSession,可以由getMapper獲取指定的代理對象。

而代理對象怎麼調用sql命令呢?原理則是根據MappedStatement對象。

4.1、根據代理對象查詢

List<User> userList = userMapper.getUserByUsername("admin");

// 該代理對象先獲取MappedStatement對象,獲取UserMapper.xml信息
// 根據該xml信息去執行相應的sql語句
@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    // ...
    MappedStatement ms = configuration.getMappedStatement(statement);
    executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    // ...
}

4.2、MappedStatement對象

protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());

// 該對象的值是在初始化配置文件時加載的
// 過程:XMLConfigBuilder->XMLMapperBuilder->XMLStatementBuilder->MapperBuilderAssistant
// key:全包名,value:
private void parseConfiguration(XNode root) {
    // ...
    mapperElement(root.evalNode("mappers"));
    // ...
}
// 解析UserMapper.xml的mapper元素
private void mapperElement(XNode parent) throws Exception {
    // ...
    for (XNode child : parent.getChildren()) {
        XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
    }
    // ...
}
// 解析每個節點
public void parseStatementNode() {
    // ...
    builderAssistant.addMappedStatement(...);
    // ...
}
// 增加到MappedStatement對象中
public MappedStatement addMappedStatement(...) {
    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(...);
    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }
    MappedStatement statement = statementBuilder.build();
    // 關鍵代碼
    configuration.addMappedStatement(statement);
    return statement;
}

4.3、Excutor對象執行查詢

獲取了MappedStatement對象,則由執行機將該對象xml信息轉換成BoundSql(最終SQL語句),繼而執行sql命令

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
// query方法內部調用-->queryFromDatabase方法內部調用-->(SimpleExcutor)doQuery方法爲jdbc實現
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
        Configuration configuration = ms.getConfiguration();
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        stmt = prepareStatement(handler, ms.getStatementLog());
        return handler.query(stmt, resultHandler);
    } finally {
        closeStatement(stmt);
    }
} 

5、總結(核心代碼總結)

1、獲取配置文件mybatis-config.xml

2、由SqlSessionFactoryBuilder對象,讀取配置文件

3、根據配置文件設置MapperRegistry對象(存放接口代理對象)

4、根據配置文件設置MappedStatement對象(存放接口對應的xml信息)

5、根據配置文件解析TransactionFactory事務對象(如mysql爲JdbcTransactionFactory)

6、根據配置文件解析Excutor對象(默認SimpleExcutor執行機)

7、將Excutor加入攔截器(攔截器順序Executor\ParameterHandler\StatementHandler\ResultSetHandler)

8、返回SqlSessionFactory對象,由SqlSessionFactory創建SqlSession 對象

9、調用SqlSession中的api,獲取指定的接口代理對象(根據MapperRegistry對象獲取)

3、調用SqlSession中的api,獲取指定UserMapper.class文件

10、該代理對象調用查詢方法,由攔截器攔截,獲取指定的接口對應xml信息,組裝BoundSql(SQL語句)對象

11、根據BoundSql對象,獲取SQL語句,由驅動(如mysql爲jdbc)調用sql命令,查詢數據庫。

參考資料:

https://blog.csdn.net/weixin_43184769/article/details/91126687

https://zhuanlan.zhihu.com/p/59844703

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章