springboot實(shí)現(xiàn)讀寫分離

server:本demo開發(fā)工具采用springSTS
前提讀寫分離庫已經(jīng)搭建好
1.首先新建一個(gè)springboot項(xiàng)目贺待。
2.項(xiàng)目新建成功之后早敬,個(gè)人習(xí)慣在springboot入口寫一個(gè)配置文件類與Application平級。如圖


下面逐一說明一下注解的含義雳刺。
@EnableWebMvc 說明啟用了spring mvc
@Configuration 讓spring boot 項(xiàng)目啟動(dòng)時(shí)識(shí)別當(dāng)前配置類(讓spring容器知道這個(gè)類是一個(gè)xml的配置類)
@ComponentScan 掃描注解
@MapperScan(basePackages = "com.wz.mail.mapper") 掃描dao
3.說明一下spring boot中的配置文件 application.properties 個(gè)人比較喜歡使用 application.yum(好處是比較有層級感)配置文件中的內(nèi)容如下

## context-path代表項(xiàng)目名稱 端口 以及超時(shí)時(shí)間
server:
  context-path: /mail-producer  
  port: 8001
  session:
    timeout: 900     
## Spring配置:
spring: 
  http: 
    encoding:
      charset: UTF-8 
## 序列化將時(shí)間默認(rèn)序列化為該格式的時(shí)間营密;not_null如果有null默認(rèn)過濾
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
    default-property-inclusion: NON_NULL      

##此處采用druid數(shù)據(jù)源 主從配置基本一樣 master slave   數(shù)據(jù)庫ip要區(qū)分            
druid: 
    type: com.alibaba.druid.pool.DruidDataSource
    master:
        url: jdbc:mysql://localhost/mail?characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true
        driver-class-name: com.mysql.jdbc.Driver
        username: root
        password: root
        initialSize: 5
        minIdle: 1
        #maxIdle: 10
        maxActive: 100
        maxWait: 60000
        timeBetweenEvictionRunsMillis: 60000
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        poolPreparedStatements: true
        maxPoolPreparedStatementPerConnectionSize: 20
        filters: stat,wall,log4j
        useGlobalDataSourceStat: true
    slave: 
        url: jdbc:mysql://localhost:3306/mail?characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true
        driver-class-name: com.mysql.jdbc.Driver
        username: root
        password: root
        initialSize: 5
        minIdle: 1
        #maxIdle: 10
        maxActive: 100
        maxWait: 60000
        timeBetweenEvictionRunsMillis: 60000
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        poolPreparedStatements: true
        maxPoolPreparedStatementPerConnectionSize: 20
        filters: stat,wall,log4j
        useGlobalDataSourceStat: true
 ##指定mybatis的配置文件      
mybatis:
    mapper-locations: classpath:com/wz/mail/mapping/*.xml

4.現(xiàn)在我們配置了兩個(gè)數(shù)據(jù)源 南缓,再啟動(dòng)項(xiàng)目的時(shí)候得把這兩個(gè)數(shù)據(jù)源都加載進(jìn)來
(1)需要把這兩個(gè)數(shù)據(jù)源先注入進(jìn)來

package com.wz.mail.config;

import java.sql.SQLException;

import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;

@Configuration//上邊有介紹
@EnableTransactionManagement //開啟事物spring提供的注解
public class DataSourceConfiguration {
    
    private static Logger LOGGER = LoggerFactory.getLogger(DataSourceConfiguration.class);

    //默認(rèn)去找application.yum中的druid.type相當(dāng)于將配置文件中的該值賦值給dataSourceType
    @Value("${druid.type}")
    private Class<? extends DataSource> dataSourceType;
    
 
    @Bean(name = "masterDataSource")
    @Primary//優(yōu)先選擇主數(shù)據(jù)源(原因可寫可讀)
    @ConfigurationProperties(prefix = "druid.master") //意思是從application.yum中找druid.master開頭所有的信息都要放到要?jiǎng)?chuàng)建的masterDataSource并且交給spring管理
    public DataSource masterDataSource() throws SQLException{
        DataSource masterDataSource = DataSourceBuilder.create().type(dataSourceType).build();
        LOGGER.info("========MASTER: {}=========", masterDataSource);
        return masterDataSource;
    }
 
    @Bean(name = "slaveDataSource")
    @ConfigurationProperties(prefix = "druid.slave")
    public DataSource slaveDataSource(){
        DataSource slaveDataSource = DataSourceBuilder.create().type(dataSourceType).build();
        LOGGER.info("========SLAVE: {}=========", slaveDataSource);
        return slaveDataSource;
    }
  
    //druid監(jiān)控界面需要用的到servlet
    @Bean
    public ServletRegistrationBean druidServlet() {
    
        ServletRegistrationBean reg = new ServletRegistrationBean();
        reg.setServlet(new StatViewServlet());
        reg.addUrlMappings("/druid/*");
        reg.addInitParameter("allow", "localhost");
        reg.addInitParameter("deny","/deny");
        LOGGER.info(" druid console manager init : {} ", reg);
        return reg;
  }

    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new WebStatFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico, /druid/*");
        LOGGER.info(" druid filter register : {} ", filterRegistrationBean);
        return filterRegistrationBean;
    }
  
}

現(xiàn)在該啟動(dòng)項(xiàng)目了只要出現(xiàn)兩個(gè)數(shù)據(jù)源中的log說明數(shù)據(jù)源啟動(dòng)成功日志如下:

2017-07-27 22:35:06.844  INFO 14424 --- [           main] c.w.mail.config.DataSourceConfiguration  : ========MASTER: {
    CreateTime:"2017-07-27 22:35:06",
    ActiveCount:0,
    PoolingCount:0,
    CreateCount:0,
    DestroyCount:0,
    CloseCount:0,
    ConnectCount:0,
    Connections:[
    ]
}=========
2017-07-27 22:35:07.178  INFO 14424 --- [           main] c.w.mail.config.DataSourceConfiguration  : ========SLAVE: {
    CreateTime:"2017-07-27 22:35:07",
    ActiveCount:0,
    PoolingCount:0,
    CreateCount:0,
    DestroyCount:0,
    CloseCount:0,
    ConnectCount:0,
    Connections:[
    ]
}=========

瀏覽器輸入http://localhost:8001/mail-producer/druid
成功訪問的druid監(jiān)控臺(tái)

Paste_Image.png

接下來該mybatis來整合數(shù)據(jù)源,經(jīng)典的SqlSessionFactory 厌秒,將這兩個(gè)數(shù)據(jù)源交給SqlSessionFactory 來管理读拆。然后怎么區(qū)分哪個(gè)是主數(shù)據(jù)源還是從數(shù)據(jù)源呢?

首先實(shí)現(xiàn)讀寫分離就意味著有兩個(gè)數(shù)據(jù)源鸵闪,當(dāng)寫操作時(shí)對主庫使用檐晕,當(dāng)讀操作時(shí)對從庫使用。也就是說我們再啟動(dòng)數(shù)據(jù)庫連接池時(shí)要啟動(dòng)兩個(gè)蚌讼。
但我們在真正使用的時(shí)候辟灰,可以在方法上加自定義注解的形式來區(qū)分讀還是寫。
思路:
首先配置兩個(gè)數(shù)據(jù)源后(已經(jīng)配置如上)要區(qū)分兩個(gè)數(shù)據(jù)源篡石。分別是主數(shù)據(jù)源和從數(shù)據(jù)源芥喇。
可以通過mybatis配置文件把兩個(gè)數(shù)據(jù)源注入到應(yīng)用中。但是我們要想實(shí)現(xiàn)讀寫分離凰萨,也就
是什么情況下用寫继控,什么情況下用讀,這里需要自己定義一個(gè)標(biāo)識(shí)來區(qū)分胖眷。要實(shí)現(xiàn)一個(gè)即時(shí)
切換主從數(shù)據(jù)源的標(biāo)識(shí)并且能保證線程安全的基礎(chǔ)下操作數(shù)據(jù)源(原因是并發(fā)會(huì)影響數(shù)據(jù)源
的獲取分不清主從武通,造成在從庫進(jìn)行寫操作,影響mysql(mariadb)數(shù)據(jù)庫的機(jī)制珊搀,導(dǎo)致
服務(wù)器異常厅须。這里使用threadocal來解決這個(gè)問題)
然后需要自定義注解,在方法上有注解則為只讀食棕,沒有則為寫操作

package com.bhz.mail.config.database;

public class DataBaseContextHolder {

    //區(qū)分主從數(shù)據(jù)源
    public enum DataBaseType {
        MASTER, SLAVE
    }
    //線程局部變量
    private static final ThreadLocal<DataBaseType> contextHolder = new ThreadLocal<DataBaseType>();
    
    //往線程里邊set數(shù)據(jù)類型
    public static void setDataBaseType(DataBaseType dataBaseType) {
        if(dataBaseType == null) throw new NullPointerException();
        contextHolder.set(dataBaseType);
    }
    
    //從容器中獲取數(shù)據(jù)類型
    public static DataBaseType getDataBaseType(){
        return contextHolder.get() == null ? DataBaseType.MASTER : contextHolder.get();
    }
    //清空容器中的數(shù)據(jù)類型
    public static void clearDataBaseType(){
        contextHolder.remove();
    }
    
}

將這兩種數(shù)據(jù)源交給SqlSessionFactory 來管理朗和。接下來寫一個(gè)mybatis的配置類相當(dāng)于傳統(tǒng)的mybatis.xml
先配置數(shù)據(jù)源,在注入到SqlSessionFactory (強(qiáng)依賴關(guān)系有先有后)
怎樣確保mybatis配置類中先加載數(shù)據(jù)源在注入SqlSessionFactory 呢簿晓?代碼如下:

package com.wz.mail.config;

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

import org.apache.ibatis.session.SqlSessionFactory;
import org.aspectj.apache.bcel.util.ClassLoaderRepository;
import org.aspectj.apache.bcel.util.ClassLoaderRepository.SoftHashMap;
import org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration;
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;
/**
 * 
 * @author wz
 *
 */
@Configuration
@AutoConfigureAfter({DataSourceConfiguration.class})//這個(gè)文件在DataSourceConfiguration加載完成之后再加載MybatisConfiguration 
public class MybatisConfiguration extends MybatisAutoConfiguration {

    @Resource(name="masterDataSource")
    private DataSource masterDataSource;
    
    @Resource(name="slaveDataSource")
    private DataSource slaveDataSource;
    
    @Bean(name="sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        //放入datasource 需要mybatis的AbstractRoutingDataSource 實(shí)現(xiàn)主從切換
        return super.sqlSessionFactory(roundRobinDataSourceProxy());
    }
    
    public AbstractRoutingDataSource roundRobinDataSourceProxy(){
        
        ReadWriteSplitRoutingDataSource proxy = new ReadWriteSplitRoutingDataSource();
        //proxy.
        SoftHashMap targetDataSource = new ClassLoaderRepository.SoftHashMap(); 
        targetDataSource.put(DataBaseContextHolder.DataBaseType.MASTER, masterDataSource);
        targetDataSource.put(DataBaseContextHolder.DataBaseType.SLAVE, slaveDataSource);
        //默認(rèn)數(shù)據(jù)源
        proxy.setDefaultTargetDataSource(masterDataSource);
        //裝入兩個(gè)主從數(shù)據(jù)源
        proxy.setTargetDataSources(targetDataSource);
        return proxy;
    }
    
}
package com.wz.mail.config;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
//mybatis動(dòng)態(tài)代理類
class ReadWriteSplitRoutingDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataBaseContextHolder.getDataBaseType();
    }

}

自定義只讀注解眶拉,含義就是將默認(rèn)的主數(shù)據(jù)源修改為只讀數(shù)據(jù)源

package com.bhz.mail.config.database;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})//該注解應(yīng)用在方法上
@Retention(RetentionPolicy.RUNTIME)//在運(yùn)行時(shí)運(yùn)行
public @interface ReadOnlyConnection {

}

package com.wz.mail.config;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class ReadOnlyConnectionInterceptor implements Ordered {

    public static final Logger LOGGER = LoggerFactory.getLogger(ReadOnlyConnectionInterceptor.class);
    
    @Around("@annotation(readOnlyConnection)")//在注解上加入切入點(diǎn)語法,實(shí)現(xiàn)方法
    public Object proceed(ProceedingJoinPoint proceedingJoinPoint, ReadOnlyConnection readOnlyConnection) throws Throwable {
        try{
            LOGGER.info("---------------set database connection  read only---------------");
            DataBaseContextHolder.setDataBaseType(DataBaseContextHolder.DataBaseType.SLAVE);
            Object result = proceedingJoinPoint.proceed();//讓這個(gè)方法執(zhí)行完畢
            return result;
        } finally {
            DataBaseContextHolder.clearDataBaseType();
            LOGGER.info("---------------clear database connection---------------");
        }
    }
    
    @Override
    public int getOrder() {
        return 0;
    }

}

代碼已經(jīng)OK憔儿,將注解寫到只讀方法上忆植。@ReadOnlyConnection
開始測試begin 日志打印如下

2017-07-30 21:35:13.499  INFO 8604 --- [nio-8001-exec-1] c.w.m.c.ReadOnlyConnectionInterceptor    : ---------------set database connection 2 read only---------------
2017-07-30 21:35:13.735  INFO 8604 --- [nio-8001-exec-1] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} inited
2017-07-30 21:35:13.761  INFO 8604 --- [nio-8001-exec-1] c.w.m.c.ReadOnlyConnectionInterceptor    : ---------------clear database connection---------------
測試總共多少條:2

由日志可以看出使用的是只讀數(shù)據(jù)源并且使用之后清空容器里的數(shù)據(jù)源。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市朝刊,隨后出現(xiàn)的幾起案子耀里,更是在濱河造成了極大的恐慌,老刑警劉巖拾氓,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冯挎,死亡現(xiàn)場離奇詭異,居然都是意外死亡咙鞍,警方通過查閱死者的電腦和手機(jī)房官,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來续滋,“玉大人翰守,你說我怎么就攤上這事∑W茫” “怎么了蜡峰?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長朗恳。 經(jīng)常有香客問我事示,道長,這世上最難降的妖魔是什么僻肖? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任肖爵,我火速辦了婚禮,結(jié)果婚禮上臀脏,老公的妹妹穿的比我還像新娘劝堪。我一直安慰自己,他們只是感情好揉稚,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布秒啦。 她就那樣靜靜地躺著,像睡著了一般搀玖。 火紅的嫁衣襯著肌膚如雪余境。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天灌诅,我揣著相機(jī)與錄音芳来,去河邊找鬼。 笑死猜拾,一個(gè)胖子當(dāng)著我的面吹牛即舌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播挎袜,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼顽聂,長吁一口氣:“原來是場噩夢啊……” “哼肥惭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起紊搪,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蜜葱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后耀石,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體牵囤,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年娶牌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了奔浅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片馆纳。...
    茶點(diǎn)故事閱讀 40,664評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡诗良,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鲁驶,到底是詐尸還是另有隱情鉴裹,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布钥弯,位于F島的核電站径荔,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏脆霎。R本人自食惡果不足惜总处,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望睛蛛。 院中可真熱鬧鹦马,春花似錦、人聲如沸忆肾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽客冈。三九已至旭从,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間场仲,已是汗流浹背和悦。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留渠缕,地道東北人薄疚。 一個(gè)月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像纠拔,于是被迫代替她去往敵國和親物蝙。 傳聞我的和親對象是個(gè)殘疾皇子澜汤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)舵匾,斷路器俊抵,智...
    卡卡羅2017閱讀 134,704評論 18 139
  • 1. 簡介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL坐梯、存儲(chǔ)過程以及高級映射的優(yōu)秀的...
    笨鳥慢飛閱讀 5,527評論 0 4
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,859評論 6 342
  • Spring 技術(shù)筆記Day 1 預(yù)熱知識(shí)一徽诲、 基本術(shù)語Blob類型,二進(jìn)制對象Object Graph:對象圖...
    OchardBird閱讀 979評論 0 2