1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Spring 和 Mybatis 使用不同的數(shù)據(jù)源會怎樣?

        共 6662字,需瀏覽 14分鐘

         ·

        2021-12-28 04:35

        本篇文章要討論的一個問題點, 給Spring和Mybatis設置不同的數(shù)據(jù)庫數(shù)據(jù)源會怎樣?


        注意. 正常情況下一定要給Spring和Mybatis設置相同的數(shù)據(jù)庫數(shù)據(jù)源.


        案例代碼位置?

        https://github.com/infuq/spring-framework/tree/main/infuq-t/src/main/java/com/infuq/mybatis


        案例代碼結(jié)構(gòu)


        //AppConfig.java
        import com.alibaba.druid.pool.DruidDataSource;import org.apache.ibatis.session.SqlSessionFactory;import org.mybatis.spring.SqlSessionFactoryBean;import org.mybatis.spring.annotation.MapperScan;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import org.springframework.jdbc.datasource.DriverManagerDataSource;import org.springframework.transaction.annotation.EnableTransactionManagement;
        import javax.sql.DataSource;
        @EnableTransactionManagement@MapperScan("com.infuq.mybatis.mapper")@ComponentScanpublic class AppConfig {

        // 數(shù)據(jù)源 @Bean public DataSource druidDataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/test_0?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true"); dataSource.setUsername("root"); dataSource.setPassword("9527");
        return dataSource; }
        // 數(shù)據(jù)源 @Bean public DataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/test_1?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true"); dataSource.setUsername("root"); dataSource.setPassword("9527");
        return dataSource; }
        // Mybatis需要一個SqlSessionFactory, 因此向容器中注入一個SqlSessionFactory @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); return factoryBean.getObject();
        }
        // 事務管理器, 用于事務管理 @Bean public DataSourceTransactionManager druidTransactionManager(DataSource druidDataSource) { return new DataSourceTransactionManager(druidDataSource); }
        }




        通過圖形的方式, 描述上面AppConfig.java代碼的結(jié)構(gòu)


        據(jù)庫數(shù)據(jù)源分別設置到SqlSessionFactory和事務管理器.
        SqlSessionFactory用于Mybatis操作數(shù)據(jù)庫時使用,比如insert,update等.
        事務管理器用于Spring開啟事務等操作.

        // UserServiceImpl.java
        import com.infuq.mybatis.mapper.UserMapper;import org.springframework.beans.BeansException;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.transaction.annotation.Transactional;
        public class UserServiceImpl implements UserService {
        @Autowired private UserMapper userMapper; @Transactional(transactionManager = "druidTransactionManager") @Override public void getList() { userMapper.getList(); }
        }


        代碼中使用了事務管理器, 但使用了select作為案例講解,并沒有使用insert/update作為案例講解,讀者不要太在意.


        程序運行之后,看一下,Spring容器中存在的UserServiceImpl實例和UserMapper實例`長啥樣`.




        在容器中存放的是Service的代理對象, 代理對象中存在真正的被代理對象(即真正的UserServiceImpl實例), 在被代理對象內(nèi)部, 又有mapper代理對象, mapper代理對象持有sqlSessionFactory對象, sqlSessionFactory持有數(shù)據(jù)源.



        Service的代理對象內(nèi)部還有一個事務攔截器TransactionInterceptor



        在調(diào)用鏈路上,在Service代理對象和Service被代理對象之間, 還有一個事務攔截器會被調(diào)用到.


        開始運行程序



        運行程序之后,首先調(diào)用到service代理對象, 在調(diào)用到事務攔截器TransactionInterceptor, 就在這個事務攔截器中拿到了容器中的事務管理器TransactionManager, 而這個事務管理器就是我們之前配置的.


        //AppConfig.java@Beanpublic DataSourceTransactionManager druidTransactionManager(DataSource druidDataSource) {    return new DataSourceTransactionManager(druidDataSource);}


        這個事務管理器有一個很重要的事情需要做. 它需要獲取一個數(shù)據(jù)庫連接, 并開啟事務.

        那么這個數(shù)據(jù)庫連接從哪里得到呢?

        在配置事務管理器的時候,給它設置了一個數(shù)據(jù)源, 那么事務管理器就從這個數(shù)據(jù)源中得到一個數(shù)據(jù)庫連接. 而且它是通過ThreadLocal實現(xiàn)的. 如果一個線程在執(zhí)行的過程使用了多個數(shù)據(jù)庫數(shù)據(jù)源, 那么一個數(shù)據(jù)源對應一條數(shù)據(jù)庫連接的關(guān)系會被保存到ThreadLocal中, 保證線程在操作一個數(shù)據(jù)庫的時候只會使用一條相同的數(shù)據(jù)庫連接. 具體實現(xiàn)在?

        org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin


        @Overrideprotected void doBegin(Object transaction, TransactionDefinition definition) {  DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;  Connection con = null;
        try { if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) { // 拿到事務管理器中設置的數(shù)據(jù)源,并根據(jù)這個數(shù)據(jù)源創(chuàng)建一個數(shù)據(jù)庫連接 Connection newCon = obtainDataSource().getConnection(); txObject.setConnectionHolder(new ConnectionHolder(newCon), true); }
        txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
        con = txObject.getConnectionHolder().getConnection();
        Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition); txObject.setPreviousIsolationLevel(previousIsolationLevel); txObject.setReadOnly(definition.isReadOnly());

        if (con.getAutoCommit()) { txObject.setMustRestoreAutoCommit(true); // 開啟事務 con.setAutoCommit(false); }
        prepareTransactionalConnection(con, definition); txObject.getConnectionHolder().setTransactionActive(true);
        int timeout = determineTimeout(definition); if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { txObject.getConnectionHolder().setTimeoutInSeconds(timeout); }
        if (txObject.isNewConnectionHolder()) { // 將 dataSource -> connection 關(guān)系存到ThreadLocal中 TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder()); } }
        }


        總結(jié). Spring會將Service的代理對象放入容器中, 當調(diào)用代理對象的方法時, 首先會調(diào)用到事務攔截器TransactionInterceptor中,這個事務攔截器會拿到容器中的事務管理器, 事務管理器會根據(jù)設置的數(shù)據(jù)源, 創(chuàng)建一個數(shù)據(jù)庫連接, 并開啟事務. 同時也會把數(shù)據(jù)源->數(shù)據(jù)庫連接保存到ThreadLocal.


        接下來看Mybatis層面的代碼邏輯.



        經(jīng)過層層調(diào)用, Mybatis也需要拿到數(shù)據(jù)庫連接,為接下來的操作數(shù)據(jù)庫. 那么它這個連接是怎么拿到的呢?



        Mybatis本來是想從ThreadLocal中拿到一個數(shù)據(jù)庫連接的, 但是Mybatis持有的這個數(shù)據(jù)源在ThreadLocal中沒有對應的數(shù)據(jù)庫連接, 而ThreadLocal中已存在的數(shù)據(jù)源是在事務管理器的時候放入的, 它們不是同一個數(shù)據(jù)源.

        因此, Mybatis 需要根據(jù)自己拿到的數(shù)據(jù)源自己去創(chuàng)建一個數(shù)據(jù)庫連接了. 并把它也放到ThreadLocal中.



        如上圖, 由于文章開頭, 在配置事務管理器和SqlSessionFactory時,分別設置了不同的數(shù)據(jù)源, 最終就導致, 事務管理器開啟事務的時候, 使用的數(shù)據(jù)源A創(chuàng)建的一個數(shù)據(jù)庫連接. 而Mybatis在進行實際操作數(shù)據(jù)庫的時候, 使用的數(shù)據(jù)源B創(chuàng)建的一個數(shù)據(jù)庫連接. 造成了開啟事務和進行實際數(shù)據(jù)庫操作的連接不是同一個連接.


        因此,在配置的時候,需要將SqlSessionFactory和事務管理器設置成相同的數(shù)據(jù)源.


        @Beanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); return factoryBean.getObject();
        }
        @Beanpublic DataSourceTransactionManager druidTransactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource);}


        這樣的話, mybatis 根據(jù)數(shù)據(jù)源在拿取數(shù)據(jù)庫連接的時候, 發(fā)現(xiàn)ThreadLocal中已經(jīng)有對應數(shù)據(jù)源的數(shù)據(jù)庫連接了, 因為在事務管理器的時候, 由事務管理器已經(jīng)把數(shù)據(jù)源對應的數(shù)據(jù)庫連接放入到ThreadLocal中了.



        瀏覽 59
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            日本羞羞无码一区二区视频网站 | 久久久精品观看 | 久久网国产 | 白峰美羽美艳人妻在线 | 国产精品久久久久永久免费观看 | 俺去俺来也www色官网黑人 | 护士脱了裙子坐上去好爽 | 九色导航| 波多野结衣操逼 | 日韩无码 中文字幕 |