MyBatis的原理

MyBatis核心類

SqlSessionFactory

每個MyBatis應用都是以一個SqlSessionFactory的實例爲核心構建的。SqlSessionFactory的核心做用是什麼?sql

從類的名稱上能夠看出來, SqlSessionFactory是產生 SqlSession的工廠。 SqlSessionFactory是經過 SqlSessionFactoryBuilder這個構建器來構建的。

SqlSessionFactory是一個接口,其中定義了獲取SqlSession的方法。數據庫

public interface SqlSessionFactory {
    //獲取一個SqlSession
    SqlSession openSession();
    //獲取一個SqlSession,參數設置事務是否自動提交
    SqlSession openSession(boolean var1);
    //經過指定Connection中的參數獲取
    SqlSession openSession(Connection var1);
    //獲取SqlSession,設置事務的隔離級別,NONE(0),READ_COMMITTED(2),READ_UNCOMMITTED(1),REPEATABLE_READ(4),SERIALIZABLE(8);
    SqlSession openSession(TransactionIsolationLevel var1);
    //獲取SqlSession,同時制定執行的類別,支持三種SIMPLE(這種模式下,將爲每一個語句建立一個PreparedStatement),REUSE(這個模式下重複使用preparedStatment),BATCH(批量更新,insert時候,若是沒有提交,沒法獲取自增id);
    SqlSession openSession(ExecutorType var1);
    SqlSession openSession(ExecutorType var1, boolean var2);
    SqlSession openSession(ExecutorType var1, TransactionIsolationLevel var2);
    SqlSession openSession(ExecutorType var1, Connection var2);
    //獲取全部的配置項
    Configuration getConfiguration();
}

SqlSessionFactory包含兩個實現:DefaultSqlSessionFactorySqlSessionManager
SqlSessionFactory的實例如何建立呢?
一、一般咱們是在只有MyBatis的項目中是使用下面的代碼段來建立的:設計模式

String resource = "org/mybatis/example/mybatis-config.xml";
//讀取mybatis配置的問題
InputStream inputStream = Resources.getResourceAsStream(resource);
//經過SqlSessionFactoryBuilder的build方式建立
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

二、在Spring boot和MyBatis結合的項目中我會使用下面的代碼段來建立:緩存

MyBatis-Spring-Boot-Starter依賴將會提供以下
  • 自動檢測現有的DataSource
  • 將建立並註冊SqlSessionFactory的實例,該實例使用SqlSessionFactoryBean將該DataSource做爲輸入進行傳遞
  • 將建立並註冊從SqlSessionFactory中獲取的SqlSessionTemplate的實例。
  • 自動掃描您的mappers,將它們連接到SqlSessionTemplate並將其註冊到Spring上下文,以便將它們注入到您的bean中。
  • 就是說,使用了該Starter以後,只須要定義一個DataSource便可(application.properties中可配置),它會自動建立使用該DataSource的SqlSessionFactory Bean以及SqlSessionTemplate。會自動掃描你的Mappers,鏈接到SqlSessionTemplate,並註冊到Spring上下文中.
@Bean
public SqlSessionFactory sqlSessionFactory(@Qualifier("druidDataSource") DataSource druidDataSource) throws Exception {
    //先建立一個SqlSessionFactoryBean
    SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
    //在這個bean裏設置須要的參數
    fb.setDataSource(this.dynamicDataSource(druidDataSource));
    fb.setTypeAliasesPackage(env.getProperty("mybatis.type-aliases-package"));
    fb.setTypeHandlersPackage(env.getProperty("mybatis.type-handlers-package"));
    fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.mapperLocations")));
    //經過這個方法獲取一個SqlSessionFactory
    return fb.getObject();
}

在代碼裏一直向下查找的時候就會發現:這個過程其實和上面的過程同樣。
SqlSessionFactoryBean會將一系列的屬性封裝成一個Configuration對象,而後調用
this.sqlSessionFactoryBuilder.build(configuration) 來建立。而在sqlSessionFactoryBuilder裏主要就是解析資源的內容,而後進行建立。安全

在這裏有分別使用了兩個設計模式:session

  • 工廠模式

SqlSessionFactory就是一個工廠模式——簡單工廠模式的變形實現。
經過SqlSessionFactory中重載的不一樣openSession()方法來獲取不一樣類型的實例。mybatis

  • 建造者模式(build模式)

SqlSessionFactoryBuilder建立SqlSessionFactory就是建造者模式的實現。在建立的過程當中須要解析不少的文件,生成對象,進行緩存等操做,因此一個方法是很難直接寫完,因此其中應用了大量的build模式:多線程

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    XMLConfigBuilder xmlConfigBuilder = null;
    Configuration configuration;
    if (this.configuration != null) {
       ......
    } else if (this.configLocation != null) {
        xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
        configuration = xmlConfigBuilder.getConfiguration();
    } else {
        ......
    }
    ......

    if (xmlConfigBuilder != null) {
        try {
            //builder解析
            xmlConfigBuilder.parse();
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
            }
        } catch (Exception var22) {
            throw new NestedIOException("Failed to parse config resource: " + this.configLocation, var22);
        } finally {
            ErrorContext.instance().reset();
        }
    }
    ......
    if (!ObjectUtils.isEmpty(this.mapperLocations)) {
        Resource[] var29 = this.mapperLocations;
        var27 = var29.length;

        for(var5 = 0; var5 < var27; ++var5) {
            Resource mapperLocation = var29[var5];
            if (mapperLocation != null) {
                try {
                    //Mapper文件build
                    XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments());
                    xmlMapperBuilder.parse();
                } catch (Exception var20) {
                    
                } finally {
                    //單例模式
                    ErrorContext.instance().reset();
                }
            }
        }
    } 
    //build一個對象返回
    return this.sqlSessionFactoryBuilder.build(configuration);
}
SqlSession

SqlSessionFactory建立完成以後,就能夠經過SqlSessionFactory對象來獲取一個SqlSession實例.SqlSession是一次與數據庫的會話.在他的接口中定義了一些列的CRUD和事務的操做接口。SqlSession是暴露給用戶使用的API,一個SqlSession對應着一次數據庫會話。SqlSession不是線程安全的,因此在使用的時候必定要保證他是局部變量。
他對應的類圖以下:
圖片描述
SqlSession有幾種常見的實現:DefaultSqkSession是默認的非線程安全的實現,SqlSessionManager是Mybatis中對SqlSession的線程安全實現,在內部是使用的private ThreadLocal<SqlSession> localSqlSession = new ThreadLocal();的形式來保證線程安全的,SqlSessionTemplate是MyBatis-Spring 的核心。做爲 SqlSession 的一個實現,這意味着可使用它無縫代替你代碼中已經在使用的 SqlSession。SqlSessionTemplate 是線程安全的,能夠被多個 DAO 或映射器所共享使用。app

由於SqlSessionTemplate是線程安全的,因此當SqlSessionTemplate是單例的時候,多線程調用SqlSessionTemplate仍然使用的是同一個SqlSession,接下來看一下SqlSessionTemplate是如何保證線程安全的呢?函數

首先咱們看一下SqlSessionTemplate的建立過程:

/**
 * 構造函數1,須要傳入參數SqlSessionFactory
 */
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
    this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
    Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    Assert.notNull(executorType, "Property 'executorType' is required");
    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    //建立代理類的實例,該代理類實現了SqlSession接口,這裏使用的是基於JDK的動態代理,SqlSessionInterceptor也是實現了InvocationHandler接口,最後代理對象的操做都會通過invoke執行
    //class SqlSessionInterceptor implements InvocationHandler
    this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}

invoke的實現是實現線程安全的核心:

private class SqlSessionInterceptor implements InvocationHandler {
    private SqlSessionInterceptor() {
    }
  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     //獲取真實的SqlSession,這個是真實使用的,是MyBatis生成的DefaultSqlSession,獲取是線程安全實現的核心
        SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

        Object unwrapped;
        try {
            //執行操做
            Object result = method.invoke(sqlSession, args);
            //若是不是事務類型的,那麼設置爲自動提交
            if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                sqlSession.commit(true);
            }
            //執行結果包裝
            unwrapped = result;
        } catch (Throwable var11) {
            unwrapped = ExceptionUtil.unwrapThrowable(var11);
            if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                sqlSession = null;
                Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
                if (translated != null) {
                    unwrapped = translated;
                }
            }

            throw (Throwable)unwrapped;
        } finally {
            if (sqlSession != null) {
                SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            }

        }
        //返回執行結果
        return unwrapped;
    }
}

接着看getSqlSession()的代碼:

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
    Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
    Assert.notNull(executorType, "No ExecutorType specified");
    //SqlSessionHolder是對SqlSession的一個功能包裝,TransactionSynchronizationManager是一個事務同步管理器,維護當前線程事務資源,信息以及TxSync集合,getResource會從 ThreadLocal<Map<Object, Object>> resources 中獲取當前線程SqlSessionHolder實例
    SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
        return session;
    } else {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Creating a new SqlSession");
        }
        //若是沒有獲取成功,那麼開啓一個SqlSession
        session = sessionFactory.openSession(executorType);
        //註冊SessionHolder
        registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
        return session;
    }
}

registerSessionHolder()實現?

好難啊~~~~~

相關文章
相關標籤/搜索