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

          從 0 開始寫了個讀寫分離,不難嘛!

          共 9131字,需瀏覽 19分鐘

           ·

          2020-09-28 21:34

          Java技術棧

          www.javastack.cn

          關注閱讀更多優(yōu)質文章



          寫了個讀寫分離,并未照搬網文,而是獨立思考后的成果,寫完以后發(fā)現從零開始寫讀寫分離并不難!

          我最初的想法是:讀方法走讀庫,寫方法走寫庫(一般是主庫),保證在Spring提交事務之前確定數據源。

          保證在Spring提交事務之前確定數據源,這個簡單,利用AOP寫個切換數據源的切面,讓他的優(yōu)先級高于Spring事務切面的優(yōu)先級。

          至于讀,寫方法的區(qū)分可以用2個注解。

          但是如何切換數據庫呢?我完全不知道!多年經驗告訴我

          當完全不了解一個技術時,先搜索學習必要知識,之后再動手嘗試。

          我搜索了一些網文,發(fā)現都提到了一個AbstractRoutingDataSource類。查看源碼注釋如下:

          /**
          Abstract?{@link?javax.sql.DataSource}?implementation?that?routes?{@link?#getConnection()}
          ?*?calls?to?one?of?various?target?DataSources?based?on?a?lookup?key.?The?latter?is?usually
          ?*?(but?not?necessarily)?determined?through?some?thread-bound?transaction?context.
          ?*
          ?*?@author?Juergen?Hoeller
          ?*?@since?2.0.1
          ?*?@see?#setTargetDataSources
          ?*?@see?#setDefaultTargetDataSource
          ?*?@see?#determineCurrentLookupKey()
          ?*/

          AbstractRoutingDataSource就是DataSource的抽象,基于lookup key的方式在多個數據庫中進行切換。

          重點關注setTargetDataSources,setDefaultTargetDataSource,determineCurrentLookupKey三個方法。那么AbstractRoutingDataSource就是Spring讀寫分離的關鍵了。

          仔細閱讀了三個方法,基本上跟方法名的意思一致。setTargetDataSources設置備選的數據源集合。setDefaultTargetDataSource設置默認數據源,determineCurrentLookupKey決定當前數據源的對應的key。

          但是我很好奇這3個方法都沒有包含切換數據庫的邏輯??!我仔細閱讀源碼發(fā)現一個方法,determineTargetDataSource方法,其實它才是獲取數據源的實現。源碼如下:

          //切換數據庫的核心邏輯
          protected?DataSource?determineTargetDataSource()?{
          ??Assert.notNull(this.resolvedDataSources,?"DataSource?router?not?initialized");
          ??Object?lookupKey?=?determineCurrentLookupKey();
          ??DataSource?dataSource?=?this.resolvedDataSources.get(lookupKey);
          ??if?(dataSource?==?null?&&?(this.lenientFallback?||?lookupKey?==?null))?{
          ????dataSource?=?this.resolvedDefaultDataSource;
          ??}
          ??if?(dataSource?==?null)?{
          ????throw?new?IllegalStateException
          ????????????("Cannot?determine?target?DataSource?for?lookup?key?["?+?lookupKey?+?"]");
          ??}
          ??return?dataSource;
          }

          //之前的2個核心方法
          public?void?setTargetDataSources(Map?targetDataSources)?{
          ??this.targetDataSources?=?targetDataSources;
          }

          public?void?setDefaultTargetDataSource(Object?defaultTargetDataSource)?{
          ??this.defaultTargetDataSource?=?defaultTargetDataSource;
          }

          簡單說就是,根據determineCurrentLookupKey獲取的key,在resolvedDataSources這個Map中查找對應的datasource!,注意determineTargetDataSource方法竟然不使用的targetDataSources!

          那一定存在resolvedDataSources與targetDataSources的對應關系。我接著翻閱代碼,發(fā)現一個afterPropertiesSet方法(Spring源碼中InitializingBean接口中的方法),這個方法將targetDataSources的值賦予了resolvedDataSources。

          源碼如下:

          @Override
          public?void?afterPropertiesSet()?{
          ??if?(this.targetDataSources?==?null)?{
          ???throw?new?IllegalArgumentException("Property?'targetDataSources'?is?required");
          ??}
          ??this.resolvedDataSources?=?new?HashMap(this.targetDataSources.size());
          ??for?(Map.Entry?entry?:?this.targetDataSources.entrySet())?{
          ???Object?lookupKey?=?resolveSpecifiedLookupKey(entry.getKey());
          ???DataSource?dataSource?=?resolveSpecifiedDataSource(entry.getValue());
          ???this.resolvedDataSources.put(lookupKey,?dataSource);
          ??}
          ??if?(this.defaultTargetDataSource?!=?null)?{
          ???this.resolvedDefaultDataSource?=?resolveSpecifiedDataSource(this.defaultTargetDataSource);
          ??}
          }

          afterPropertiesSet 方法,熟悉Spring的都知道,它在bean實例已經創(chuàng)建好,且屬性值和依賴的其他bean實例都已經注入以后執(zhí)行。

          也就是說調用,targetDataSources,defaultTargetDataSource的賦值一定要在afterPropertiesSet前邊執(zhí)行。

          現在都是 Spring Boot 天下了,這個倉庫推給大家學習下:https://github.com/javastacks/spring-boot-best-practice

          AbstractRoutingDataSource簡單總結:

          1. AbstractRoutingDataSource,內部有一個Map的域resolvedDataSources

          2. determineTargetDataSource方法通過determineCurrentLookupKey方法獲得key,進而從map中取得對應的DataSource。

          3. setTargetDataSources 設置 targetDataSources

          4. setDefaultTargetDataSource 設置 defaultTargetDataSource,

          5. targetDataSources和defaultTargetDataSource 在afterPropertiesSet分別轉換為resolvedDataSources和resolvedDefaultDataSource。

          6. targetDataSources,defaultTargetDataSource的賦值一定要在afterPropertiesSet前邊執(zhí)行。

          進一步了解理論后,讀寫分離的方式則基本上出現在眼前了。(“下列方法不唯一”)

          先寫一個類繼承AbstractRoutingDataSource,實現determineCurrentLookupKey方法,和afterPropertiesSet方法。afterPropertiesSet方法中調用setDefaultTargetDataSource和setTargetDataSources方法之后調用super.afterPropertiesSet。

          之后定義一個切面在事務切面之前執(zhí)行,確定真實數據源對應的key。Spring事務失效的 8 大原因,這篇推薦看下。

          但是這又出現了一個問題,如何線程安全的情況下傳遞每個線程獨立的key呢?沒錯使用ThreadLocal傳遞真實數據源對應的key。

          ThreadLocal,Thread的局部變量,確保每一個線程都維護變量的一個副本,詳細教程可以關注公眾號Java技術棧搜索閱讀。

          到這里基本邏輯就想通了,之后就是寫了。

          DataSourceContextHolder 使用ThreadLocal存儲真實數據源對應的key

          public?class?DataSourceContextHolder?{??
          ????private?static?Logger?log?=?LoggerFactory.getLogger(DataSourceContextHolder.class);?//線程本地環(huán)境??
          ????private?static?final?ThreadLocal?local?=?new?ThreadLocal();???
          ????public?static?void?setRead()?{??
          ????????local.set(DataSourceType.read.name());??
          ????????log.info("數據庫切換到讀庫...");??
          ????}??
          ????public?static?void?setWrite()?{??
          ????????local.set(DataSourceType.write.name());??
          ????????log.info("數據庫切換到寫庫...");??
          ????}??
          ????public?static?String?getReadOrWrite()?{??
          ????????return?local.get();??
          ????}??
          }

          DataSourceAopAspect 切面切換真實數據源對應的key,并設置優(yōu)先級保證高于事務切面

          @Aspect??
          @EnableAspectJAutoProxy(exposeProxy=true,proxyTargetClass=true)??
          @Component??
          public?class?DataSourceAopAspect?implements?PriorityOrdered{

          ??@Before("execution(*?com.springboot.demo.mybatis.service.readorwrite..*.*(..))?"??
          ????????????+?"?and?@annotation(com.springboot.demo.mybatis.readorwrite.annatation.ReadDataSource)?")??
          ????public?void?setReadDataSourceType()?{??
          ????????//如果已經開啟寫事務了,那之后的所有讀都從寫庫讀??
          ????????????DataSourceContextHolder.setRead();????
          ????}??
          ????@Before("execution(*?com.springboot.demo.mybatis.service.readorwrite..*.*(..))?"??
          ????????????+?"?and?@annotation(com.springboot.demo.mybatis.readorwrite.annatation.WriteDataSource)?")??
          ????public?void?setWriteDataSourceType()?{??
          ????????DataSourceContextHolder.setWrite();??
          ????}??
          ?@Override
          ?public?int?getOrder()?{
          ??/**?
          ?????????*?值越小,越優(yōu)先執(zhí)行?要優(yōu)于事務的執(zhí)行?
          ?????????*?在啟動類中加上了@EnableTransactionManagement(order?=?10)??
          ?????????*/??
          ??return?1;
          ?}
          }

          RoutingDataSouceImpl實現AbstractRoutingDataSource的邏輯

          @Component
          public?class?RoutingDataSouceImpl?extends?AbstractRoutingDataSource?{
          ?
          ?@Override
          ?public?void?afterPropertiesSet()?{
          ??//初始化bean的時候執(zhí)行,可以針對某個具體的bean進行配置
          ??//afterPropertiesSet?早于init-method
          ??//將datasource注入到targetDataSources中,可以為后續(xù)路由用到的key
          ??this.setDefaultTargetDataSource(writeDataSource);
          ??MaptargetDataSources=new?HashMap();
          ??targetDataSources.put(?DataSourceType.write.name(),?writeDataSource);
          ??targetDataSources.put(?DataSourceType.read.name(),??readDataSource);
          ??this.setTargetDataSources(targetDataSources);
          ??//執(zhí)行原有afterPropertiesSet邏輯,
          ??//即將targetDataSources中的DataSource加載到resolvedDataSources
          ??super.afterPropertiesSet();
          ?}
          ?@Override
          ?protected?Object?determineCurrentLookupKey()?{
          ??//這里邊就是讀寫分離邏輯,最后返回的是setTargetDataSources保存的Map對應的key
          ??String?typeKey?=?DataSourceContextHolder.getReadOrWrite();??
          ??Assert.notNull(typeKey,?"數據庫路由發(fā)現typeKey?is?null,無法抉擇使用哪個庫");
          ??log.info("使用"+typeKey+"數據庫.............");??
          ??return?typeKey;
          ?}
          ???private?static?Logger?log?=?LoggerFactory.getLogger(RoutingDataSouceImpl.class);?
          ?@Autowired??
          ?@Qualifier("writeDataSource")??
          ?private?DataSource?writeDataSource;??
          ?@Autowired??
          ?@Qualifier("readDataSource")??
          ?private?DataSource?readDataSource;??
          }

          基本邏輯實現完畢了就進行,通用設置,設置數據源,事務,SqlSessionFactory等

          @Primary
          @Bean(name?=?"writeDataSource",?destroyMethod?=?"close")
          @ConfigurationProperties(prefix?=?"test_write")
          public?DataSource?writeDataSource()?{
          ??return?new?DruidDataSource();
          }

          @Bean(name?=?"readDataSource",?destroyMethod?=?"close")
          @ConfigurationProperties(prefix?=?"test_read")
          public?DataSource?readDataSource()?{
          ??return?new?DruidDataSource();
          }

          @Bean(name?=?"writeOrReadsqlSessionFactory")
          public?SqlSessionFactory?
          ?????????sqlSessionFactorys(RoutingDataSouceImpl?roundRobinDataSouceProxy)?
          ?????????????????????????????????????????????????????????throws?Exception?{
          ??try?{
          ????SqlSessionFactoryBean?bean?=?new?SqlSessionFactoryBean();
          ????bean.setDataSource(roundRobinDataSouceProxy);
          ????ResourcePatternResolver?resolver?=?new?PathMatchingResourcePatternResolver();
          ????//?實體類對應的位置
          ????bean.setTypeAliasesPackage("com.springboot.demo.mybatis.model");
          ????//?mybatis的XML的配置
          ????bean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
          ????return?bean.getObject();
          ??}?catch?(IOException?e)?{
          ????log.error(""?+?e);
          ????return?null;
          ??}?catch?(Exception?e)?{
          ????log.error(""?+?e);
          ????return?null;
          ??}
          }

          @Bean(name?=?"writeOrReadTransactionManager")
          public?DataSourceTransactionManager?transactionManager(RoutingDataSouceImpl?
          ????????????roundRobinDataSouceProxy)?{
          ??//Spring?的jdbc事務管理器
          ??DataSourceTransactionManager?transactionManager?=?new?
          ????????????????DataSourceTransactionManager(roundRobinDataSouceProxy);
          ??return?transactionManager;
          }

          其他代碼,就不在這里贅述了。現在都是 Spring Boot 天下了,這個倉庫推給大家學習下:https://github.com/javastacks/spring-boot-best-practice

          使用Spring寫讀寫分離,其核心就是AbstractRoutingDataSource,源碼不難,讀懂之后,寫個讀寫分離就簡單了!。

          AbstractRoutingDataSource重點回顧:

          1. AbstractRoutingDataSource,內部有一個Map的域resolvedDataSources

          2. determineTargetDataSource方法通過determineCurrentLookupKey方法獲得key,進而從map中取得對應的DataSource。

          3. setTargetDataSources 設置 targetDataSources

          4. setDefaultTargetDataSource 設置 defaultTargetDataSource,

          5. targetDataSources和defaultTargetDataSource 在afterPropertiesSet分別轉換為resolvedDataSources和resolvedDefaultDataSource。

          6. targetDataSources,defaultTargetDataSource的賦值一定要在afterPropertiesSet前邊執(zhí)行。

          這周確實有點忙,周五花費了些時間不過總算實現了自己的諾言。

          完成承諾不容易,喜歡您就點個贊!

          作者:溫安適
          來源:https://my.oschina.net/floor/blog/1632565





          關注Java技術??锤喔韶?/strong>



          戳原文,獲取精選面試題!
          瀏覽 53
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  国产在线天堂 | 在线伊人成人网 | 欧美操比图 | 豆花视频A片无码资源 | 麻豆一级久久久 |