<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 6662字,需瀏覽 14分鐘

           ·

          2021-12-28 04:35

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


          注意. 正常情況下一定要給Spring和Mybatis設(shè)置相同的數(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需要一個(gè)SqlSessionFactory, 因此向容器中注入一個(gè)SqlSessionFactory @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
          SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); return factoryBean.getObject();
          }
          // 事務(wù)管理器, 用于事務(wù)管理 @Bean public DataSourceTransactionManager druidTransactionManager(DataSource druidDataSource) { return new DataSourceTransactionManager(druidDataSource); }
          }




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


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

          // 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(); }
          }


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


          程序運(yùn)行之后,看一下,Spring容器中存在的UserServiceImpl實(shí)例和UserMapper實(shí)例`長啥樣`.




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



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



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


          開始運(yùn)行程序



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


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


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

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

          在配置事務(wù)管理器的時(shí)候,給它設(shè)置了一個(gè)數(shù)據(jù)源, 那么事務(wù)管理器就從這個(gè)數(shù)據(jù)源中得到一個(gè)數(shù)據(jù)庫連接. 而且它是通過ThreadLocal實(shí)現(xiàn)的. 如果一個(gè)線程在執(zhí)行的過程使用了多個(gè)數(shù)據(jù)庫數(shù)據(jù)源, 那么一個(gè)數(shù)據(jù)源對應(yīng)一條數(shù)據(jù)庫連接的關(guān)系會被保存到ThreadLocal中, 保證線程在操作一個(gè)數(shù)據(jù)庫的時(shí)候只會使用一條相同的數(shù)據(jù)庫連接. 具體實(shí)現(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()) { // 拿到事務(wù)管理器中設(shè)置的數(shù)據(jù)源,并根據(jù)這個(gè)數(shù)據(jù)源創(chuàng)建一個(gè)數(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); // 開啟事務(wù) 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的代理對象放入容器中, 當(dāng)調(diào)用代理對象的方法時(shí), 首先會調(diào)用到事務(wù)攔截器TransactionInterceptor中,這個(gè)事務(wù)攔截器會拿到容器中的事務(wù)管理器, 事務(wù)管理器會根據(jù)設(shè)置的數(shù)據(jù)源, 創(chuàng)建一個(gè)數(shù)據(jù)庫連接, 并開啟事務(wù). 同時(shí)也會把數(shù)據(jù)源->數(shù)據(jù)庫連接保存到ThreadLocal.


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



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



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

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



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


          因此,在配置的時(shí)候,需要將SqlSessionFactory和事務(wù)管理器設(shè)置成相同的數(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ù)庫連接的時(shí)候, 發(fā)現(xiàn)ThreadLocal中已經(jīng)有對應(yīng)數(shù)據(jù)源的數(shù)據(jù)庫連接了, 因?yàn)樵谑聞?wù)管理器的時(shí)候, 由事務(wù)管理器已經(jīng)把數(shù)據(jù)源對應(yīng)的數(shù)據(jù)庫連接放入到ThreadLocal中了.



          瀏覽 59
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  涩涩99| 国产视频高清在线 | 色噜噜狠狠一区二区三区Av蜜芽 | 99精品操| 又黄又爽一区二区三区 |