日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
SpringBoot+MyBatis+MySQL實現(xiàn)讀寫分離!

 1、引言

讀寫分離要做的事情就是對于一條SQL該選擇哪個數(shù)據(jù)庫去執(zhí)行,至于誰來做選擇數(shù)據(jù)庫這件事兒,無非兩個,要么中間件幫我們做,要么程序自己做。

成都創(chuàng)新互聯(lián)公司是一家專業(yè)提供曲松企業(yè)網(wǎng)站建設(shè),專注與網(wǎng)站設(shè)計、成都做網(wǎng)站成都h5網(wǎng)站建設(shè)、小程序制作等業(yè)務(wù)。10年已為曲松眾多企業(yè)、政府機構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站設(shè)計公司優(yōu)惠進行中。

因此,一般來講,讀寫分離有兩種實現(xiàn)方式。第一種是依靠中間件(比如:MyCat),也就是說應(yīng)用程序連接到中間件,中間件幫我們做SQL分離;第二種是應(yīng)用程序自己去做分離。這里我們選擇程序自己來做,主要是利用Spring提供的路由數(shù)據(jù)源,以及AOP

然而,應(yīng)用程序?qū)用嫒プ鲎x寫分離最大的弱點(不足之處)在于無法動態(tài)增加數(shù)據(jù)庫節(jié)點,因為數(shù)據(jù)源配置都是寫在配置中的,新增數(shù)據(jù)庫意味著新加一個數(shù)據(jù)源,必然改配置,并重啟應(yīng)用。當(dāng)然,好處就是相對簡單。

2、AbstractRoutingDataSource

基于特定的查找key路由到特定的數(shù)據(jù)源。它內(nèi)部維護了一組目標(biāo)數(shù)據(jù)源,并且做了路由key與目標(biāo)數(shù)據(jù)源之間的映射,提供基于key查找數(shù)據(jù)源的方法。

3、實踐

3.1. maven依賴 

 
 
 
 
  1.  
  2.     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 
  3.     4.0.0 
  4.     com.cjs.example 
  5.     cjs-datasource-demo 
  6.     0.0.1-SNAPSHOT 
  7.     jar 
  8.     cjs-datasource-demo 
  9.      
  10.      
  11.         org.springframework.boot 
  12.         spring-boot-starter-parent 
  13.         2.0.5.RELEASE 
  14.           
  15.      
  16.      
  17.         UTF-8 
  18.         UTF-8 
  19.         1.8 
  20.      
  21.      
  22.          
  23.             org.springframework.boot 
  24.             spring-boot-starter-aop 
  25.          
  26.          
  27.             org.springframework.boot 
  28.             spring-boot-starter-jdbc 
  29.          
  30.          
  31.             org.springframework.boot 
  32.             spring-boot-starter-web 
  33.          
  34.          
  35.             org.mybatis.spring.boot 
  36.             mybatis-spring-boot-starter 
  37.             1.3.2 
  38.          
  39.          
  40.             org.apache.commons 
  41.             commons-lang3 
  42.             3.8 
  43.          
  44.          
  45.             mysql 
  46.             mysql-connector-java 
  47.             runtime 
  48.          
  49.          
  50.             org.springframework.boot 
  51.             spring-boot-starter-test 
  52.             test 
  53.          
  54.      
  55.      
  56.          
  57.              
  58.                 org.springframework.boot 
  59.                 spring-boot-maven-plugin 
  60.              
  61.              
  62.          
  63.      

3.2. 數(shù)據(jù)源配置

application.yml 

 
 
 
 
  1. spring: 
  2.   datasource: 
  3.     master: 
  4.       jdbc-url: jdbc:mysql://192.168.102.31:3306/test 
  5.       username: root 
  6.       password: 123456 
  7.       driver-class-name: com.mysql.jdbc.Driver 
  8.     slave1: 
  9.       jdbc-url: jdbc:mysql://192.168.102.56:3306/test 
  10.       username: pig   # 只讀賬戶 
  11.       password: 123456 
  12.       driver-class-name: com.mysql.jdbc.Driver 
  13.     slave2: 
  14.       jdbc-url: jdbc:mysql://192.168.102.36:3306/test 
  15.       username: pig   # 只讀賬戶 
  16.       password: 123456 
  17.       driver-class-name: com.mysql.jdbc.Driver

多數(shù)據(jù)源配置

 
 
 
 
  1. package com.cjs.example.config; 
  2. import com.cjs.example.bean.MyRoutingDataSource; 
  3. import com.cjs.example.enums.DBTypeEnum; 
  4. import org.springframework.beans.factory.annotation.Qualifier; 
  5. import org.springframework.boot.context.properties.ConfigurationProperties; 
  6. import org.springframework.boot.jdbc.DataSourceBuilder; 
  7. import org.springframework.context.annotation.Bean; 
  8. import org.springframework.context.annotation.Configuration; 
  9. import javax.sql.DataSource; 
  10. import java.util.HashMap; 
  11. import java.util.Map; 
  12. /** 
  13.  * 關(guān)于數(shù)據(jù)源配置,參考SpringBoot官方文檔第79章《Data Access》 
  14.  * 79. Data Access 
  15.  * 79.1 Configure a Custom DataSource 
  16.  * 79.2 Configure Two DataSources 
  17.  */ 
  18. @Configuration 
  19. public class DataSourceConfig { 
  20.     @Bean 
  21.     @ConfigurationProperties("spring.datasource.master") 
  22.     public DataSource masterDataSource() { 
  23.         return DataSourceBuilder.create().build(); 
  24.     } 
  25.     @Bean 
  26.     @ConfigurationProperties("spring.datasource.slave1") 
  27.     public DataSource slave1DataSource() { 
  28.         return DataSourceBuilder.create().build(); 
  29.     } 
  30.     @Bean 
  31.     @ConfigurationProperties("spring.datasource.slave2") 
  32.     public DataSource slave2DataSource() { 
  33.         return DataSourceBuilder.create().build(); 
  34.     } 
  35.     @Bean 
  36.     public DataSource myRoutingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource, 
  37.                                           @Qualifier("slave1DataSource") DataSource slave1DataSource, 
  38.                                           @Qualifier("slave2DataSource") DataSource slave2DataSource) { 
  39.         Map targetDataSources = new HashMap<>(); 
  40.         targetDataSources.put(DBTypeEnum.MASTER, masterDataSource); 
  41.         targetDataSources.put(DBTypeEnum.SLAVE1, slave1DataSource); 
  42.         targetDataSources.put(DBTypeEnum.SLAVE2, slave2DataSource); 
  43.         MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource(); 
  44.         myRoutingDataSource.setDefaultTargetDataSource(masterDataSource); 
  45.         myRoutingDataSource.setTargetDataSources(targetDataSources); 
  46.         return myRoutingDataSource; 
  47.     } 
  48. }

這里,我們配置了4個數(shù)據(jù)源,1個master,2兩個slave,1個路由數(shù)據(jù)源。前3個數(shù)據(jù)源都是為了生成第4個數(shù)據(jù)源,而且后續(xù)我們只用這最后一個路由數(shù)據(jù)源。

Spring Boot 最新基礎(chǔ)教程和示例源碼:https://github.com/javastacks/spring-boot-best-practice

MyBatis配置

 
 
 
 
  1. package com.cjs.example.config; 
  2. import org.apache.ibatis.session.SqlSessionFactory; 
  3. import org.mybatis.spring.SqlSessionFactoryBean; 
  4. import org.springframework.context.annotation.Bean; 
  5. import org.springframework.context.annotation.Configuration; 
  6. import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
  7. import org.springframework.jdbc.datasource.DataSourceTransactionManager; 
  8. import org.springframework.transaction.PlatformTransactionManager; 
  9. import org.springframework.transaction.annotation.EnableTransactionManagement; 
  10. import javax.annotation.Resource; 
  11. import javax.sql.DataSource; 
  12. @EnableTransactionManagement 
  13. @Configuration 
  14. public class MyBatisConfig { 
  15.     @Resource(name = "myRoutingDataSource") 
  16.     private DataSource myRoutingDataSource; 
  17.     @Bean 
  18.     public SqlSessionFactory sqlSessionFactory() throws Exception { 
  19.         SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); 
  20.         sqlSessionFactoryBean.setDataSource(myRoutingDataSource); 
  21.         sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml")); 
  22.         return sqlSessionFactoryBean.getObject(); 
  23.     } 
  24.     @Bean 
  25.     public PlatformTransactionManager platformTransactionManager() { 
  26.         return new DataSourceTransactionManager(myRoutingDataSource); 
  27.     } 
  28. }

由于Spring容器中現(xiàn)在有4個數(shù)據(jù)源,所以我們需要為事務(wù)管理器和MyBatis手動指定一個明確的數(shù)據(jù)源。另外,Spring 系列面試題和答案全部整理好了,微信搜索Java技術(shù)棧,在后臺發(fā)送:面試,可以在線閱讀。

3.3. 設(shè)置路由key / 查找數(shù)據(jù)源

目標(biāo)數(shù)據(jù)源就是那前3個這個我們是知道的,但是使用的時候是如果查找數(shù)據(jù)源的呢?

首先,我們定義一個枚舉來代表這三個數(shù)據(jù)源

 
 
 
 
  1. package com.cjs.example.enums; 
  2. public enum DBTypeEnum { 
  3.     MASTER, SLAVE1, SLAVE2; 
  4. }

接下來,通過ThreadLocal將數(shù)據(jù)源設(shè)置到每個線程上下文中

 
 
 
 
  1. package com.cjs.example.bean; 
  2. import com.cjs.example.enums.DBTypeEnum; 
  3. import java.util.concurrent.atomic.AtomicInteger;
  4. public class DBContextHolder { 
  5.     private static final ThreadLocal contextHolder = new ThreadLocal<>(); 
  6.     private static final AtomicInteger counter = new AtomicInteger(-1); 
  7.     public static void set(DBTypeEnum dbType) { 
  8.         contextHolder.set(dbType); 
  9.     }
  10.     public static DBTypeEnum get() { 
  11.         return contextHolder.get(); 
  12.     } 
  13.     public static void master() { 
  14.         set(DBTypeEnum.MASTER); 
  15.         System.out.println("切換到master"); 
  16.     } 
  17.     public static void slave() {
  18.         //  輪詢 
  19.         int index = counter.getAndIncrement() % 2; 
  20.         if (counter.get() > 9999) { 
  21.             counter.set(-1); 
  22.         } 
  23.         if (index == 0) { 
  24.             set(DBTypeEnum.SLAVE1); 
  25.             System.out.println("切換到slave1"); 
  26.         }else { 
  27.             set(DBTypeEnum.SLAVE2); 
  28.             System.out.println("切換到slave2"); 
  29.         } 
  30.     } 
  31. }

獲取路由key

 
 
 
 
  1. package com.cjs.example.bean; 
  2. import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; 
  3. import org.springframework.lang.Nullable; 
  4. public class MyRoutingDataSource extends AbstractRoutingDataSource { 
  5.     @Nullable 
  6.     @Override 
  7.     protected Object determineCurrentLookupKey() { 
  8.         return DBContextHolder.get(); 
  9.     } 
  10. }

設(shè)置路由key

默認(rèn)情況下,所有的查詢都走從庫,插入/修改/刪除走主庫。我們通過方法名來區(qū)分操作類型(CRUD)

 
 
 
 
  1. package com.cjs.example.aop; 
  2. import com.cjs.example.bean.DBContextHolder; 
  3. import org.apache.commons.lang3.StringUtils; 
  4. import org.aspectj.lang.JoinPoint;
  5. import org.aspectj.lang.annotation.Aspect; 
  6. import org.aspectj.lang.annotation.Before; 
  7. import org.aspectj.lang.annotation.Pointcut; 
  8. import org.springframework.stereotype.Component; 
  9. @Aspect 
  10. @Component 
  11. public class DataSourceAop { 
  12.     @Pointcut("!@annotation(com.cjs.example.annotation.Master) " + 
  13.             "&& (execution(* com.cjs.example.service..*.select*(..)) " + 
  14.             "|| execution(* com.cjs.example.service..*.get*(..)))") 
  15.     public void readPointcut() { 
  16.     } 
  17.     @Pointcut("@annotation(com.cjs.example.annotation.Master) " + 
  18.             "|| execution(* com.cjs.example.service..*.insert*(..)) " + 
  19.             "|| execution(* com.cjs.example.service..*.add*(..)) " + 
  20.             "|| execution(* com.cjs.example.service..*.update*(..)) " +
  21.             "|| execution(* com.cjs.example.service..*.edit*(..)) " + 
  22.             "|| execution(* com.cjs.example.service..*.delete*(..)) " + 
  23.             "|| execution(* com.cjs.example.service..*.remove*(..))") 
  24.     public void writePointcut() { 
  25.     } 
  26.     @Before("readPointcut()") 
  27.     public void read() { 
  28.         DBContextHolder.slave(); 
  29.     } 
  30.     @Before("writePointcut()") 
  31.     public void write() { 
  32.         DBContextHolder.master(); 
  33.     } 
  34.     /** 
  35.      * 另一種寫法:if...else...  判斷哪些需要讀從數(shù)據(jù)庫,其余的走主數(shù)據(jù)庫 
  36.      */ 
  37. //    @Before("execution(* com.cjs.example.service.impl.*.*(..))") 
  38. //    public void before(JoinPoint jp) { 
  39. //        String methodName = jp.getSignature().getName(); 
  40. // 
  41. //        if (StringUtils.startsWithAny(methodName, "get", "select", "find")) { 
  42. //            DBContextHolder.slave(); 
  43. //        }else { 
  44. //            DBContextHolder.master(); 
  45. //        } 
  46. //    } 
  47. }

有一般情況就有特殊情況,特殊情況是某些情況下我們需要強制讀主庫,針對這種情況,我們定義一個主鍵,用該注解標(biāo)注的就讀主庫

 
 
 
 
  1. package com.cjs.example.annotation; 
  2. public @interface Master {
  3. }

例如,假設(shè)我們有一張表member

 
 
 
 
  1. package com.cjs.example.service.impl; 
  2. import com.cjs.example.annotation.Master; 
  3. import com.cjs.example.entity.Member; 
  4. import com.cjs.example.entity.MemberExample; 
  5. import com.cjs.example.mapper.MemberMapper; 
  6. import com.cjs.example.service.MemberService; 
  7. import org.springframework.beans.factory.annotation.Autowired; 
  8. import org.springframework.stereotype.Service; 
  9. import org.springframework.transaction.annotation.Transactional; 
  10. import java.util.List; 
  11. @Service 
  12. public class MemberServiceImpl implements MemberService {
  13.     @Autowired 
  14.     private MemberMapper memberMapper; 
  15.     @Transactional 
  16.     @Override 
  17.     public int insert(Member member) { 
  18.         return memberMapper.insert(member); 
  19.     }
  20.     @Master 
  21.     @Override 
  22.     public int save(Member member) { 
  23.         return memberMapper.insert(member); 
  24.     } 
  25.     @Override
  26.     public List selectAll() { 
  27.         return memberMapper.selectByExample(new MemberExample()); 
  28.     } 
  29.     @Master 
  30.     @Override 
  31.     public String getToken(String appId) { 
  32.         //  有些讀操作必須讀主數(shù)據(jù)庫 
  33.         //  比如,獲取微信access_token,因為高峰時期主從同步可能延遲 
  34.         //  這種情況下就必須強制從主數(shù)據(jù)讀 
  35.         return null; 
  36.     } 
  37. }

4、測試 

 
 
 
 
  1. package com.cjs.example; 
  2. import com.cjs.example.entity.Member; 
  3. import com.cjs.example.service.MemberService; 
  4. import org.junit.Test; 
  5. import org.junit.runner.RunWith; 
  6. import org.springframework.beans.factory.annotation.Autowired; 
  7. import org.springframework.boot.test.context.SpringBootTest; 
  8. import org.springframework.test.context.junit4.SpringRunner; 
  9. @RunWith(SpringRunner.class) 
  10. @SpringBootTest 
  11. public class CjsDatasourceDemoApplicationTests { 
  12.     @Autowired 
  13.     private MemberService memberService; 
  14.     @Test 
  15.     public void testWrite() { 
  16.         Member member = new Member(); 
  17.         member.setName("zhangsan"); 
  18.         memberService.insert(member); 
  19.     } 
  20.     @Test 
  21.     public void testRead() { 
  22.         for (int i = 0; i < 4; i++) { 
  23.             memberService.selectAll(); 
  24.         } 
  25.     } 
  26.     @Test 
  27.     public void testSave() { 
  28.         Member member = new Member(); 
  29.         member.setName("wangwu"); 
  30.         memberService.save(member); 
  31.     } 
  32.     @Test 
  33.     public void testReadFromMaster() { 
  34.         memberService.getToken("1234"); 
  35.     } 
  36. }

查看控制臺

5、工程結(jié)構(gòu)


文章題目:SpringBoot+MyBatis+MySQL實現(xiàn)讀寫分離!
文章出自:http://www.5511xx.com/article/djpdesi.html