1. 程式人生 > >02-下篇-SpringBoot下MySQL的讀寫分離

02-下篇-SpringBoot下MySQL的讀寫分離

前言:關於MySQL讀寫主從實現,分兩步:第一步,需要現有主從的環境,利用docker快速實現; -----上篇第二步,利用已有的環境進行JavaEE的Web專案配置。 -----下篇,基於SpringBoot的SpringDataJpa的實現!即本文環境:SpringBoot:2.0.3 DB:MySQL5.7.20 主從模式持久化框架:SpringDataJpa1.多資料來源的配置application.yml:#####MySQL資料庫的主從配置開始#####mysql: datasource: readSize: 1 #讀庫個數,可以有多個type: com.alibaba.druid.pool.DruidDataSourcewrite: url: jdbc:mysql://192.168.1.121:3306/db_frms?useUnicode=true&characterEncoding=utf-8username: rootpassword: 123456driver-class-name: com.mysql.jdbc.DriverminIdle:
5maxActive: 100initialSize: 10maxWait: 60000timeBetweenEvictionRunsMillis: 60000minEvictableIdleTimeMillis: 300000validationQuery: select 'x'testWhileIdle: truetestOnBorrow: falsetestOnReturn: falsepoolPreparedStatements: truemaxPoolPreparedStatementPerConnectionSize: 50removeAbandoned: truefilters: statread01: url:
jdbc:mysql://192.168.1.121:3307/db_frms?useUnicode=true&characterEncoding=utf-8username: rootpassword: 123456driver-class-name: com.mysql.jdbc.DriverminIdle: 5maxActive: 100initialSize: 10maxWait: 60000timeBetweenEvictionRunsMillis: 60000minEvictableIdleTimeMillis: 300000validationQuery: select 'x'testWhileIdle: true
testOnBorrow: falsetestOnReturn: falsepoolPreparedStatements: truemaxPoolPreparedStatementPerConnectionSize: 50removeAbandoned: truefilters: stat# read02: #因為我只用docker配置了一個slave,所以沒有第二個slave,故這段配置註釋掉!# url: jdbc:mysql://192.168.1.121:3308/test_02?useUnicode=true&characterEncoding=utf-8# username: root# password: root# driver-class-name: com.mysql.jdbc.Driver# minIdle: 5# maxActive: 100# initialSize: 10# maxWait: 60000# timeBetweenEvictionRunsMillis: 60000# minEvictableIdleTimeMillis: 300000# validationQuery: select 'x'# testWhileIdle: true# testOnBorrow: false# testOnReturn: false# poolPreparedStatements: true# maxPoolPreparedStatementPerConnectionSize: 50# removeAbandoned: true# filters: stat#####MySQL資料庫的主從配置結束#####
2.定義資料庫的型別列舉類DataSourceType
package com.ddbin.frms.config.datasource;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * Description:資料來源型別的列舉類
*
 * @param
* @author dbdu
 * @date 18-7-14 上午8:05
 */
@Getter
@AllArgsConstructor
public enum DataSourceType {

    read("read", "從庫"), write("write", "");
/**
     * Description:型別,是讀還是寫
*
     * @author dbdu
     * @date 18-7-14 上午8:14
     */
private String type;
/**
     * Description:資料來源的名稱
*
     * @author dbdu
     * @date 18-7-14 上午8:15
     */
private String name;
}
要注意的是:列舉例項小寫,大寫會報錯!!read("read", "從庫"), write("write", "主庫");3.多個數據源的例項化配置類DataSourceConfiguration有多少個數據源,就配置多少個對應的Bean。
package com.ddbin.frms.config.datasource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
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 org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
/**
 * Description:MySQL讀寫主從資料庫源配置
*
 * @param
* @author dbdu
 * @date 18-7-14 上午7:51
 * @return
*/
@Configuration
@Slf4j
public class DataSourceConfiguration {

    @Value("${mysql.datasource.type}")
    private Class<? extends DataSource> dataSourceType;
/**
     * 寫庫 資料來源配置
*
     * @return
*/
@Bean(name = "writeDataSource")
    @Primary
    @ConfigurationProperties(prefix = "mysql.datasource.write")
    public DataSource writeDataSource() {
        log.info("writeDataSource init ...");
        return DataSourceBuilder.create().type(dataSourceType).build();
}

    /**
     * 有多少個從庫就要配置多少個
*
     * @return
*/
@Bean(name = "readDataSource01")
    @ConfigurationProperties(prefix = "mysql.datasource.read01")
    public DataSource readDataSourceOne() {
        log.info("read01 DataSourceOne init ...");
        return DataSourceBuilder.create().type(dataSourceType).build();
}

//    @Bean(name = "readDataSource02")
//    @ConfigurationProperties(prefix = "mysql.datasource.read02")
//    public DataSource readDataSourceTwo() {
//        log.info("read02 DataSourceTwo init ...");
//        return DataSourceBuilder.create().type(dataSourceType).build();
//    }
}

4.配置資料來源的切換類DataSourceContextHolder設定這個類的對應的read和write,就被內部用來讀取不同的資料來源
package com.ddbin.frms.config.datasource;
import lombok.extern.slf4j.Slf4j;
/**
 * Description:本地執行緒,資料來源上下文切換
*
 * @author dbdu
 * @date 18-7-14 上午8:17
 */
@Slf4j
public class DataSourceContextHolder {
    //程本地private static final ThreadLocal<String> local = new ThreadLocal<String>();
    public static ThreadLocal<String> getLocal() {
        return local;
}

    /**
     * 讀庫
*/
public static void setRead() {
        local.set(DataSourceType.read.getType());
//log.info("READ...");
}

    /**
     * 寫庫
*/
public static void setWrite() {
        local.set(DataSourceType.write.getType());
// log.info("WRITE...");
}

    public static String getReadOrWrite() {
        return local.get();
}

    public static void clear() {
        local.remove();
}
}
5.資料來源的代理路由配置DatasourceAgentConfig---請讀者特別注意這個類,網上很多說的都是mybatis框架的,這裡是SpringDataJpa框架對應的關聯代理資料來源路由的配置,此處配置出錯就會失敗!
package com.ddbin.frms.config.datasource;
import com.ddbin.frms.FrmsApplication;
import com.ddbin.frms.util.SpringContextsUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
@AutoConfigureAfter(DataSourceConfiguration.class)
@EnableTransactionManagement(order = 10)
@Slf4j
public class DatasourceAgentConfig {

    @Value("${mysql.datasource.readSize}")
    private String readDataSourceSize;
    private AbstractRoutingDataSource proxy;
@Autowired
    @Qualifier("writeDataSource")
    private DataSource writeDataSource;
@Autowired
    @Qualifier("readDataSource01")
    private DataSource readDataSource01;
//    @Autowired
//    @Qualifier("readDataSource02")
//    private DataSource readDataSource02;
/**
     * 把所有資料庫都放在路由中
* 重點是roundRobinDataSouceProxy()方法,它把所有的資料庫源交給AbstractRoutingDataSource類,
* 並由它的determineCurrentLookupKey()進行決定資料來源的選擇,其中讀庫進行了簡單的負載均衡(輪詢)。
*
     * @return
*/
@Bean(name = "roundRobinDataSouceProxy")
    public AbstractRoutingDataSource roundRobinDataSouceProxy() {

        /**
         * Description:把所有資料庫都放在targetDataSources,注意key值要和determineCurrentLookupKey()中程式碼寫的一至,
*     否則切換資料來源時找不到正確的資料來源
*/
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
targetDataSources.put(DataSourceType.write.getType(), writeDataSource);
targetDataSources.put(DataSourceType.read.getType() + "1", readDataSource01);
//targetDataSources.put(DataSourceType.read.getType() + "2", readDataSource02);
        //路由對應據源
final int readSize = Integer.parseInt(readDataSourceSize);
MyAbstractRoutingDataSource proxy = new MyAbstractRoutingDataSource(readSize);
proxy.setTargetDataSources(targetDataSources);
//認庫
proxy.setDefaultTargetDataSource(writeDataSource);
        this.proxy = proxy;
        return proxy;
}

    /**
     * Description:要特別注意,這個Bean是配置讀寫分離成敗的關鍵,
*
     * @param []
* @return org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean
     * @author dbdu
     * @date 18-7-15 下午5:08
     */
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.MYSQL);
//是否生成表
vendorAdapter.setGenerateDdl(true);
//是否sqlvendorAdapter.setShowSql(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
//配置描的位置
factory.setPackagesToScan(FrmsApplication.class.getPackage().getName());
// 這個數據源代理的據源,----關鍵性配置!!!
factory.setDataSource(proxy);
        return factory;
}


    @Bean(name = "transactionManager")
    public MyJpaTransactionManager transactionManager() {

        MyJpaTransactionManager transactionManager = new MyJpaTransactionManager();
transactionManager.setDataSource(proxy);
transactionManager.setEntityManagerFactory((EntityManagerFactory) SpringContextsUtil.getBean("entityManagerFactory"));
        return transactionManager;
}
}
說明:entityManagerFactory是關鍵配置,網上很多說的都是mybatis的方式sqlSessionFactory的Bean會關聯代理資料來源,SpringDataJpa的方式使用entityManagerFactory來關聯代理資料來源,否則讀寫分離是假的,這個可以通過主從庫資料不同查詢可以知道!/** * Description:要特別注意,這個Bean是配置讀寫分離成敗的關鍵, * * @param []* @return org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean * @author dbdu * @date 18-7-15 下午5:08 */@Beanpublic LocalContainerEntityManagerFactoryBean entityManagerFactory() { HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();vendorAdapter.setDatabase(Database.MYSQL);//是否生成表vendorAdapter.setGenerateDdl(true);//是否顯示sql語句vendorAdapter.setShowSql(true);LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();factory.setJpaVendorAdapter(vendorAdapter);//配置掃描的位置factory.setPackagesToScan(FrmsApplication.class.getPackage().getName());// 這個資料來源設定為代理的資料來源,----這是關鍵性配置!!!factory.setDataSource(proxy); return factory;}6.自定義的路由資料來源及事務管理器的子類:
package com.ddbin.frms.config.datasource;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
 * Description: 抽象資料來源的路由的子類
* Created at:2018-07-15 13:37,
 * by dbdu
 */
@Getter
@Setter
@AllArgsConstructor
public class MyAbstractRoutingDataSource extends AbstractRoutingDataSource {

    /**
     * Description:讀庫的數量,可以用來實現負載均衡
*/
private int readSize;
//private AtomicLong count = new AtomicLong(0);
/**
     * 這是AbstractRoutingDataSource類中的一個抽象方法,
* 而它的返回值是你所要用的資料來源dataSourcekey值,有了這個key值,
* targetDataSources就從中取出對應的DataSource,如果找不到,就用配置預設的資料來源。
*/
@Override
protected Object determineCurrentLookupKey() {
        String typeKey = DataSourceContextHolder.getReadOrWrite();
        if (typeKey == null || typeKey.equals(DataSourceType.write.getType())) {
            System.err.println("使用write.............");
            return DataSourceType.write.getType();
} else {
            //讀庫簡單負載均衡
//                    int number = count.getAndAdd(1);
//                    int lookupKey = number % readSize;
//                    System.err.println("使用read-" + (lookupKey + 1));
//                    return DataSourceType.read.getType() + (lookupKey + 1);
return DataSourceType.read.getType() + "1";
}
    }
}
package com.ddbin.frms.config.datasource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.DefaultTransactionStatus;
@SuppressWarnings("serial")
@Slf4j
public class MyJpaTransactionManager extends JpaTransactionManager {

    @Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
        if (definition.isReadOnly()) {
            DataSourceContextHolder.setRead();
} else {
            DataSourceContextHolder.setWrite();
}
        log.info("jpa-transaction:begin-----now dataSource is [" + DataSourceContextHolder.getReadOrWrite() + "]");
        super.doBegin(transaction, definition);
}

    @Override
protected void doCommit(DefaultTransactionStatus status) {
        log.info("jpa-transaction:commit-----now dataSource is [" + DataSourceContextHolder.getReadOrWrite() + "]");
        super.doCommit(status);
}
} 
說明:如果方法命名不符合規則,也沒有加註解,則typeKey會有可能為null,下面的邏輯是typeKey為空使用寫庫!---也就是主庫。/** * 這是AbstractRoutingDataSource類中的一個抽象方法, * 而它的返回值是你所要用的資料來源dataSource的key值,有了這個key值, * targetDataSources就從中取出對應的DataSource,如果找不到,就用配置預設的資料來源。 */@Overrideprotected Object determineCurrentLookupKey() { String typeKey = DataSourceContextHolder.getReadOrWrite(); if (typeKey == null || typeKey.equals(DataSourceType.write.getType())) { System.err

相關推薦

02-下篇-SpringBootMySQL分離

前言:關於MySQL讀寫主從實現,分兩步:第一步,需要現有主從的環境,利用docker快速實現; -----上篇第二步,利用已有的環境進行JavaEE的Web專案配置。 -----下篇,基於SpringBoot的SpringDataJpa的實現!即本文環境:Spring

springboot+ssm+mysql 分離+動態修改資料來源

一.我們最開始先實現讀寫分離(其實和多資料來源差不多,只是多資料來源的定義更加廣泛,讀寫分離只是其中的一個應用而已) 這裡就不怎麼探討mysql的主從的一個原理了,我直接貼出一個部落格,可以去看看,大致瞭解一下mysql主從。 我學東西喜歡先跑一次,如果成功了,我就再深入研究了,其實大體的邏

SpringBoot+MyBatis+MySQL分離

spring: datasource: master: jdbc-url: jdbc:mysql://192.168.102.31:3306/test username: root password: 123456 driver-cl

windows MySQL分離、主從複製、通過amoeba代理實現分離 配置全過程

配置環境: 1.mysql5.6 2.windowsXP 主從複製配置 主伺服器配置 配置my.ini檔案 查詢my.ini地址 my.ini檔案在MySQL Server 5.6目錄下 我的my.ini路徑: C:\Documents and Settings\All

Linux環境mysql分離以及主從配置(不錯可以的)

記下File及Position下的值。以備在配置從伺服器時使用。 注:File:當前binlog的檔名,每重啟一次mysql,就會生成一個新binlog檔案       Position:當前binlog的指標位置 三、從伺服器配置 1、配置mysql.cnf # vi /etc/my.cnf (1)修改

mysql分離springboot整合

springboot、mysql實現讀寫分離 1、首先在springcloud config中配置讀寫資料庫 mysql: datasource: readSize: 1 #讀庫個數 type: com.alibaba.druid.pool.DruidDat

二. Mysql分離springboot服務端AbstractRoutingDataSource整合

一 DataSourceConfig.class /** * 資料來源配置類 */ @Configuration @EnableTransactionManagement public class DataSourceConfig { /** * 只寫資料來源

springboot環境實現分離

本文講述springboot環境下構造讀寫分離的框架; 讀寫分離 有哪些好處呢,相信不用多講,大家能夠花時間看這篇文章,那麼就很清楚它的應用場景了,下面我們開始直接進入正題; 讀寫分離其實就是在底層替換資料來源即可,針對一主一從的情況下資料來源切換比較簡單,那麼在一主多從的

Mysql分離方案-MySQL Proxy環境部署記錄

round back 通過 and http 意思 同時 主從 角色 Mysql的讀寫分離可以使用MySQL Proxy和Amoeba實現,其實也可以使用MySQL-MMM實現讀寫分離的自動切換。MySQL Proxy有一項強大功能是實現"讀寫分離",基本原理是讓主數據

amoeba實現mysql分離+主從復制架構

mysql amoeba 讀寫分離 主從復制一、環境系統:centos6.5mysql版本:mysql5.6master服務器:192.168.1.21slave服務器: 192.168.1.100master寫 slave讀二、實現mysql主從復制在master的/etc/my.cnf中[mysqld]字

mysql中間件amoeba實現mysql分離

ipaddress -c export div 高可用 rop 6.0 res grant Amoeba是一個以MySQL為底層數據存儲,並相應用提供MySQL協議接口的proxy。它集中地響應應用的請求,根據用戶事先設置的規則。將SQL請求發送到特定的數據庫上運行

mysql分離的三種實現方式

不能 span bsp 缺點 解決方案 使用 隨機 mas 均衡   1 程序修改mysql操作類可以參考PHP實現的Mysql讀寫分離,阿權開始的本項目,以php程序解決此需求。優點:直接和數據庫通信,簡單快捷的讀寫分離和隨機的方式實現的負載均衡,權限獨立分配缺點:自己維

mysql分離

mysql amabeamoeba 實現 mysql 讀寫分離Amoeba 其實是一個代理,接收所有請求,將其分類,根據我們寫在 xml 文件裏的規則,將其投放出去,比如讀寫分離,將所有的寫操作投放給 master,將所有的讀操作投放給 slave,最後再由他返回給用戶. 讀寫分離能有效利用 mysql 主從

mycat實現MySQL分離

mycat mysql讀寫分離 mycat實現MySQL讀寫分離mycat是什麽Mycat是一個開源的分布式數據庫系統,但是由於真正的數據庫需要存儲引擎,而Mycat並沒有存儲引擎,所以並不是完全意義的分布式數據庫系統。Mycat是數據庫中間件,就是介於數據庫與應用之間,進行數據處理與交互的中間服務。

Linux的企業-Mysql分離,組的復制Group-based Replication(2)

mysql讀寫分離 組的復制 基於組的復制(Group-based Replication)是一種被使用在容錯系統中的技術。Replication-group(復制組)是由能夠相互通信的多個服務器(節點)組成的。在通信層,Group replication實現了一系列的機制:比如原子消息(atomic

使用Spring AOP實現MySQL分離

npr getclass mod rac ava nfa release box port spring aop , mysql 主從配置 實現讀寫分離,下來把自己的配置過程,以及遇到的問題記錄下來,方便下次操作,也希望給一些朋友帶來幫助。mysql主從配置參看:http:

實現MySQL分離MySQL性能調優

affect iad list cte 軟件包 密碼 sts 要求 select 實現MySQL讀寫分離 1.1 問題 本案例要求配置2臺MySQL服務器+1臺代理服務器,實現MySQL代理的讀寫分離: 用戶只需要訪問MySQL代理服務器,而實際的SQL查詢、寫入操作交給

6MySQL 主從同步 、 MySQL 分離MySQL 性能調優

chang 時間 form 變量名 col 最大 rom 驗證 uptime day06一、mysql主從同步 二、數據讀寫分離三、MySQL優化++++++++++++++++++++++++++++++++一、mysql主從同步 1.1 主從同步介紹?從庫服務器自動同

Mysql分離-Mysql router

post ble -- .rpm https mariadb ogg tcp 實現 原理:MySQL router根據端口來區分讀寫,把連接讀寫端口的所有請求發往master,連接只讀端口的所有請求以輪詢方式發往多個slave,從而實現讀寫分離 主: SQL-Maste

Mysql分離-amoeba

使用 客戶 cor 刷新 cto query 查詢 runt 刷新數據 轉載自http://www.cnblogs.com/liuyisai/p/6009379.html Amoeba主配置文件($AMOEBA_HOME/conf/amoeba.xml),用來配置Amo