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

          SpringBoot+MyBatis+MySQL讀寫分離(實(shí)例)

          共 10800字,需瀏覽 22分鐘

           ·

          2020-12-11 11:04

          上一篇:
          程序員的高光時(shí)刻!因代碼貢獻(xiàn)受 Linux 之父親自點(diǎn)名贊賞


          1. 引言

          讀寫分離要做的事情就是對(duì)于一條SQL該選擇哪個(gè)數(shù)據(jù)庫去執(zhí)行,至于誰來做選擇數(shù)據(jù)庫這件事兒,無非兩個(gè),要么中間件幫我們做,要么程序自己做。因此,一般來講,讀寫分離有兩種實(shí)現(xiàn)方式。第一種是依靠中間件(比如:MyCat),也就是說應(yīng)用程序連接到中間件,中間件幫我們做SQL分離;第二種是應(yīng)用程序自己去做分離。這里我們選擇程序自己來做,主要是利用Spring提供的路由數(shù)據(jù)源,以及AOP。

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


          2. AbstractRoutingDataSource

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


          3. 實(shí)踐

          關(guān)于配置請(qǐng)參考:

          https://www.cnblogs.com/cjsblog/p/9706370.html

          3.1. maven依賴


          <project?xmlns="http://maven.apache.org/POM/4.0.0"?xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          ????xsi:schemaLocation="http://maven.apache.org/POM/4.0.0?http://maven.apache.org/xsd/maven-4.0.0.xsd">

          ????<modelVersion>4.0.0modelVersion>

          ????<groupId>com.cjs.examplegroupId>
          ????<artifactId>cjs-datasource-demoartifactId>
          ????<version>0.0.1-SNAPSHOTversion>
          ????<packaging>jarpackaging>

          ????<name>cjs-datasource-demoname>
          ????<description>description>

          ????<parent>
          ????????<groupId>org.springframework.bootgroupId>
          ????????<artifactId>spring-boot-starter-parentartifactId>
          ????????<version>2.0.5.RELEASEversion>
          ????????<relativePath/>?
          ????parent>

          ????<properties>
          ????????<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
          ????????<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
          ????????<java.version>1.8java.version>
          ????properties>

          ????<dependencies>
          ????????<dependency>
          ????????????<groupId>org.springframework.bootgroupId>
          ????????????<artifactId>spring-boot-starter-aopartifactId>
          ????????dependency>
          ????????<dependency>
          ????????????<groupId>org.springframework.bootgroupId>
          ????????????<artifactId>spring-boot-starter-jdbcartifactId>
          ????????dependency>
          ????????<dependency>
          ????????????<groupId>org.springframework.bootgroupId>
          ????????????<artifactId>spring-boot-starter-webartifactId>
          ????????dependency>
          ????????<dependency>
          ????????????<groupId>org.mybatis.spring.bootgroupId>
          ????????????<artifactId>mybatis-spring-boot-starterartifactId>
          ????????????<version>1.3.2version>
          ????????dependency>
          ????????<dependency>
          ????????????<groupId>org.apache.commonsgroupId>
          ????????????<artifactId>commons-lang3artifactId>
          ????????????<version>3.8version>
          ????????dependency>

          ????????<dependency>
          ????????????<groupId>mysqlgroupId>
          ????????????<artifactId>mysql-connector-javaartifactId>
          ????????????<scope>runtimescope>
          ????????dependency>
          ????????<dependency>
          ????????????<groupId>org.springframework.bootgroupId>
          ????????????<artifactId>spring-boot-starter-testartifactId>
          ????????????<scope>testscope>
          ????????dependency>
          ????dependencies>

          ????<build>
          ????????<plugins>
          ????????????<plugin>
          ????????????????<groupId>org.springframework.bootgroupId>
          ????????????????<artifactId>spring-boot-maven-pluginartifactId>
          ????????????plugin>


          ????????????

          ????????plugins>
          ????build>
          project>

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

          application.yml

          spring:
          ??datasource:
          ????master:
          ??????jdbc-url:?jdbc:mysql://192.168.102.31:3306/test
          ??????username:?root
          ??????password:?123456
          ??????driver-class-name:?com.mysql.jdbc.Driver
          ????slave1:
          ??????jdbc-url:?jdbc:mysql://192.168.102.56:3306/test
          ??????username:?pig???#?只讀賬戶
          ??????password:?123456
          ??????driver-class-name:?com.mysql.jdbc.Driver
          ????slave2:
          ??????jdbc-url:?jdbc:mysql://192.168.102.36:3306/test
          ??????username:?pig???#?只讀賬戶
          ??????password:?123456
          ??????driver-class-name:?com.mysql.jdbc.Driver
          多數(shù)據(jù)源配置
          package?com.cjs.example.config;

          import?com.cjs.example.bean.MyRoutingDataSource;
          import?com.cjs.example.enums.DBTypeEnum;
          import?org.springframework.beans.factory.annotation.Qualifier;
          import?org.springframework.boot.context.properties.ConfigurationProperties;
          import?org.springframework.boot.jdbc.DataSourceBuilder;
          import?org.springframework.context.annotation.Bean;
          import?org.springframework.context.annotation.Configuration;

          import?javax.sql.DataSource;
          import?java.util.HashMap;
          import?java.util.Map;

          /**
          ?*?關(guān)于數(shù)據(jù)源配置,參考SpringBoot官方文檔第79章《Data?Access》
          ?*?79.?Data?Access
          ?*?79.1?Configure?a?Custom?DataSource
          ?*?79.2?Configure?Two?DataSources
          ?*/


          @Configuration
          public?class?DataSourceConfig?{

          ????@Bean
          ????@ConfigurationProperties("spring.datasource.master")
          ????public?DataSource?masterDataSource()?{
          ????????return?DataSourceBuilder.create().build();
          ????}

          ????@Bean
          ????@ConfigurationProperties("spring.datasource.slave1")
          ????public?DataSource?slave1DataSource()?{
          ????????return?DataSourceBuilder.create().build();
          ????}

          ????@Bean
          ????@ConfigurationProperties("spring.datasource.slave2")
          ????public?DataSource?slave2DataSource()?{
          ????????return?DataSourceBuilder.create().build();
          ????}

          ????@Bean
          ????public?DataSource?myRoutingDataSource(@Qualifier("masterDataSource")?DataSource?masterDataSource,
          ??????????????????????????????????????????@Qualifier("slave1DataSource")?DataSource?slave1DataSource,
          ??????????????????????????????????????????@Qualifier("slave2DataSource")?DataSource?slave2DataSource)?{
          ????????Map?targetDataSources?=?new?HashMap<>();
          ????????targetDataSources.put(DBTypeEnum.MASTER,?masterDataSource);
          ????????targetDataSources.put(DBTypeEnum.SLAVE1,?slave1DataSource);
          ????????targetDataSources.put(DBTypeEnum.SLAVE2,?slave2DataSource);
          ????????MyRoutingDataSource?myRoutingDataSource?=?new?MyRoutingDataSource();
          ????????myRoutingDataSource.setDefaultTargetDataSource(masterDataSource);
          ????????myRoutingDataSource.setTargetDataSources(targetDataSources);
          ????????return?myRoutingDataSource;
          ????}

          }

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

          MyBatis配置

          package?com.cjs.example.config;

          import?org.apache.ibatis.session.SqlSessionFactory;
          import?org.mybatis.spring.SqlSessionFactoryBean;
          import?org.springframework.context.annotation.Bean;
          import?org.springframework.context.annotation.Configuration;
          import?org.springframework.core.io.support.PathMatchingResourcePatternResolver;
          import?org.springframework.jdbc.datasource.DataSourceTransactionManager;
          import?org.springframework.transaction.PlatformTransactionManager;
          import?org.springframework.transaction.annotation.EnableTransactionManagement;

          import?javax.annotation.Resource;
          import?javax.sql.DataSource;

          @EnableTransactionManagement
          @Configuration
          public?class?MyBatisConfig?{

          ????@Resource(name?=?"myRoutingDataSource")
          ????private?DataSource?myRoutingDataSource;

          ????@Bean
          ????public?SqlSessionFactory?sqlSessionFactory()?throws?Exception?{
          ????????SqlSessionFactoryBean?sqlSessionFactoryBean?=?new?SqlSessionFactoryBean();
          ????????sqlSessionFactoryBean.setDataSource(myRoutingDataSource);
          ????????sqlSessionFactoryBean.setMapperLocations(new?PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
          ????????return?sqlSessionFactoryBean.getObject();
          ????}

          ????@Bean
          ????public?PlatformTransactionManager?platformTransactionManager()?{
          ????????return?new?DataSourceTransactionManager(myRoutingDataSource);
          ????}
          }

          由于Spring容器中現(xiàn)在有4個(gè)數(shù)據(jù)源,所以我們需要為事務(wù)管理器和MyBatis手動(dòng)指定一個(gè)明確的數(shù)據(jù)源。

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

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

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

          package?com.cjs.example.enums;

          public?enum?DBTypeEnum?{

          ????MASTER,?SLAVE1,?SLAVE2;

          }
          接下來,通過ThreadLocal將數(shù)據(jù)源設(shè)置到每個(gè)線程上下文中
          package?com.cjs.example.bean;

          import?com.cjs.example.enums.DBTypeEnum;

          import?java.util.concurrent.atomic.AtomicInteger;

          public?class?DBContextHolder?{

          ????private?static?final?ThreadLocal?contextHolder?=?new?ThreadLocal<>();

          ????private?static?final?AtomicInteger?counter?=?new?AtomicInteger(-1);

          ????public?static?void?set(DBTypeEnum?dbType)?{
          ????????contextHolder.set(dbType);
          ????}

          ????public?static?DBTypeEnum?get()?{
          ????????return?contextHolder.get();
          ????}

          ????public?static?void?master()?{
          ????????set(DBTypeEnum.MASTER);
          ????????System.out.println("切換到master");
          ????}

          ????public?static?void?slave()?{
          ????????//??輪詢
          ????????int?index?=?counter.getAndIncrement()?%?2;
          ????????if?(counter.get()?>?9999)?{
          ????????????counter.set(-1);
          ????????}
          ????????if?(index?==?0)?{
          ????????????set(DBTypeEnum.SLAVE1);
          ????????????System.out.println("切換到slave1");
          ????????}else?{
          ????????????set(DBTypeEnum.SLAVE2);
          ????????????System.out.println("切換到slave2");
          ????????}
          ????}

          }
          獲取路由key
          package?com.cjs.example.bean;

          import?org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
          import?org.springframework.lang.Nullable;

          public?class?MyRoutingDataSource?extends?AbstractRoutingDataSource?{
          ????@Nullable
          ????@Override
          ????protected?Object?determineCurrentLookupKey()?{
          ????????return?DBContextHolder.get();
          ????}

          }
          設(shè)置路由key
          默認(rèn)情況下,所有的查詢都走從庫,插入/修改/刪除走主庫。我們通過方法名來區(qū)分操作類型(CRUD)
          package?com.cjs.example.aop;

          import?com.cjs.example.bean.DBContextHolder;
          import?org.apache.commons.lang3.StringUtils;
          import?org.aspectj.lang.JoinPoint;
          import?org.aspectj.lang.annotation.Aspect;
          import?org.aspectj.lang.annotation.Before;
          import?org.aspectj.lang.annotation.Pointcut;
          import?org.springframework.stereotype.Component;

          @Aspect
          @Component
          public?class?DataSourceAop?{

          ????@Pointcut("!@annotation(com.cjs.example.annotation.Master)?"?+
          ????????????"&&?(execution(*?com.cjs.example.service..*.select*(..))?"?+
          ????????????"||?execution(*?com.cjs.example.service..*.get*(..)))")
          ????public?void?readPointcut()?{

          ????}

          ????@Pointcut("@annotation(com.cjs.example.annotation.Master)?"?+
          ????????????"||?execution(*?com.cjs.example.service..*.insert*(..))?"?+
          ????????????"||?execution(*?com.cjs.example.service..*.add*(..))?"?+
          ????????????"||?execution(*?com.cjs.example.service..*.update*(..))?"?+
          ????????????"||?execution(*?com.cjs.example.service..*.edit*(..))?"?+
          ????????????"||?execution(*?com.cjs.example.service..*.delete*(..))?"?+
          ????????????"||?execution(*?com.cjs.example.service..*.remove*(..))")
          ????public?void?writePointcut()?{

          ????}

          ????@Before("readPointcut()")
          ????public?void?read()?{
          ????????DBContextHolder.slave();
          ????}

          ????@Before("writePointcut()")
          ????public?void?write()?{
          ????????DBContextHolder.master();
          ????}


          ????/**
          ?????*?另一種寫法:if...else... ?判斷哪些需要讀從數(shù)據(jù)庫,其余的走主數(shù)據(jù)庫
          ?????*/

          //????@Before("execution(*?com.cjs.example.service.impl.*.*(..))")
          //????public?void?before(JoinPoint?jp)?{
          //????????String?methodName?=?jp.getSignature().getName();
          //
          //????????if?(StringUtils.startsWithAny(methodName,?"get",?"select",?"find"))?{
          //????????????DBContextHolder.slave();
          //????????}else?{
          //????????????DBContextHolder.master();
          //????????}
          //????}
          }
          有一般情況就有特殊情況,特殊情況是某些情況下我們需要強(qiáng)制讀主庫,針對(duì)這種情況,我們定義一個(gè)主鍵,用該注解標(biāo)注的就讀主庫
          package?com.cjs.example.annotation;

          public?@interface?Master?{
          }

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

          package?com.cjs.example.service.impl;

          import?com.cjs.example.annotation.Master;
          import?com.cjs.example.entity.Member;
          import?com.cjs.example.entity.MemberExample;
          import?com.cjs.example.mapper.MemberMapper;
          import?com.cjs.example.service.MemberService;
          import?org.springframework.beans.factory.annotation.Autowired;
          import?org.springframework.stereotype.Service;
          import?org.springframework.transaction.annotation.Transactional;

          import?java.util.List;

          @Service
          public?class?MemberServiceImpl?implements?MemberService?{

          ????@Autowired
          ????private?MemberMapper?memberMapper;

          ????@Transactional
          ????@Override
          ????public?int?insert(Member?member)?{
          ????????return?memberMapper.insert(member);
          ????}

          ????@Master
          ????@Override
          ????public?int?save(Member?member)?{
          ????????return?memberMapper.insert(member);
          ????}

          ????@Override
          ????public?List?selectAll()?{
          ????????return?memberMapper.selectByExample(new?MemberExample());
          ????}

          ????@Master
          ????@Override
          ????public?String?getToken(String?appId)?{
          ????????//??有些讀操作必須讀主數(shù)據(jù)庫
          ????????//??比如,獲取微信access_token,因?yàn)楦叻鍟r(shí)期主從同步可能延遲
          ????????//??這種情況下就必須強(qiáng)制從主數(shù)據(jù)讀
          ????????return?null;
          ????}
          }

          4. 測(cè)試

          package?com.cjs.example;

          import?com.cjs.example.entity.Member;
          import?com.cjs.example.service.MemberService;
          import?org.junit.Test;
          import?org.junit.runner.RunWith;
          import?org.springframework.beans.factory.annotation.Autowired;
          import?org.springframework.boot.test.context.SpringBootTest;
          import?org.springframework.test.context.junit4.SpringRunner;

          @RunWith(SpringRunner.class)
          @SpringBootTest
          public?class?CjsDatasourceDemoApplicationTests?{

          ????@Autowired
          ????private?MemberService?memberService;

          ????@Test
          ????public?void?testWrite()?{
          ????????Member?member?=?new?Member();
          ????????member.setName("zhangsan");
          ????????memberService.insert(member);
          ????}

          ????@Test
          ????public?void?testRead()?{
          ????????for?(int?i?=?0;?i?4;?i++)?{
          ????????????memberService.selectAll();
          ????????}
          ????}

          ????@Test
          ????public?void?testSave()?{
          ????????Member?member?=?new?Member();
          ????????member.setName("wangwu");
          ????????memberService.save(member);
          ????}

          ????@Test
          ????public?void?testReadFromMaster()?{
          ????????memberService.getToken("1234");
          ????}

          }

          查看控制臺(tái)



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



          6. 參考

          www.jianshu.com/p/f2f4256a2310
          www.cnblogs.com/gl-developer/p/6170423.html
          www.cnblogs.com/huangjuncong/p/8576935.html
          blog.csdn.net/liu976180578/article/details/77684583


          作者:狂亂的貴公子

          來源:cnblogs.com/cjsblog/p/9712457.htm



          最近熬夜給大家準(zhǔn)備了515套Java代碼,有一些是業(yè)務(wù)類的小項(xiàng)目,比如Java博客項(xiàng)目,也有腳手架、也有平時(shí)用一些的工具類、21套小程序代碼,也有一些游戲類的項(xiàng)目。

          掃以下二維碼并回復(fù)“828”即可獲取


          或者在本公眾號(hào)對(duì)話框回復(fù)【828】馬上獲取

          瀏覽 51
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  日本免费一级片 | 国产三级片视频 | 91精品国产综合久久蜜芽的特点 | 午夜福利国产 | 13一16女处被毛片视频 |