springboot如何配置多數(shù)據(jù)源及單庫(kù)事務(wù)控制?

1.背景介紹
通過(guò)springboot操作mysql數(shù)據(jù)庫(kù),但是在實(shí)際業(yè)務(wù)場(chǎng)景中,數(shù)據(jù)量迅速增長(zhǎng),一個(gè)庫(kù)一個(gè)表已經(jīng)滿(mǎn)足不了我們的需求的時(shí)候,我們就會(huì)考慮分庫(kù)分表的操作,在springboot中如何實(shí)現(xiàn)多數(shù)據(jù)源,動(dòng)態(tài)數(shù)據(jù)源切換,讀寫(xiě)分離等操作。
2.所需依賴(lài)
#父級(jí)項(xiàng)目依賴(lài)
<parent>
????<groupId>org.springframework.bootgroupId>
????<artifactId>spring-boot-starter-parentartifactId>
????<version>2.1.5.RELEASEversion>
??parent>
??#其他依賴(lài)
??<dependencies>
????
????
????<dependency>
??????<groupId>org.springframework.bootgroupId>
??????<artifactId>spring-boot-starter-webartifactId>
????dependency>
????<dependency>
??????<groupId>org.springframework.bootgroupId>
??????<artifactId>spring-boot-starter-testartifactId>
????dependency>
????<dependency>
??????<groupId>org.springframework.bootgroupId>
??????<artifactId>spring-boot-starter-aopartifactId>
????dependency>
????<dependency>
??????<groupId>com.alibabagroupId>
??????<artifactId>druidartifactId>
??????<version>1.0.2version>
????dependency>
????<dependency>
??????<groupId>mysqlgroupId>
??????<artifactId>mysql-connector-javaartifactId>
??????<scope>runtimescope>
????dependency>
????
????<dependency>
??????<groupId>org.mybatis.spring.bootgroupId>
??????<artifactId>mybatis-spring-boot-starterartifactId>
??????<version>1.3.1version>
????dependency>
????
????<dependency>
??????<groupId>org.aspectjgroupId>
??????<artifactId>aspectjweaverartifactId>
????dependency>
????<dependency>
??????<groupId>org.projectlombokgroupId>
??????<artifactId>lombokartifactId>
????dependency>
??dependencies>yml文件中配置
#多數(shù)據(jù)源 1主2從
datasource:
??#從庫(kù)數(shù)量
??readSize: 2
??# 使用druid數(shù)據(jù)源
??type: com.alibaba.druid.pool.DruidDataSource
??#主庫(kù)
??write:
????url:?jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&serverTimezone=GMT%2B8
????username: root
????password: root
????driver-class-name: com.mysql.cj.jdbc.Driver
????filters:?stat
????maxActive: 20
????initialSize: 1
????maxWait:?60000
????minIdle: 1
????timeBetweenEvictionRunsMillis:?60000
????minEvictableIdleTimeMillis:?300000
????validationQueryTimeout:?900000
????validationQuery: SELECT 1
????testWhileIdle: true
????testOnBorrow:?false
????testOnReturn: false
????poolPreparedStatements:?true
????maxOpenPreparedStatements:?20
??read1:
????url:?jdbc:mysql://localhost:3306/wb?characterEncoding=utf-8&serverTimezone=GMT%2B8
????username: root
????password: root
????driver-class-name: com.mysql.cj.jdbc.Driver
????filters:?stat
????maxActive: 20
????initialSize: 1
????maxWait:?60000
????minIdle: 1
????timeBetweenEvictionRunsMillis:?60000
????minEvictableIdleTimeMillis:?300000
????validationQueryTimeout:?900000
????validationQuery: SELECT 1
????testWhileIdle: true
????testOnBorrow:?false
????testOnReturn: false
????poolPreparedStatements:?true
????maxOpenPreparedStatements:?20
??read2:
????url:?jdbc:mysql://localhost:3306/sys?characterEncoding=utf-8&serverTimezone=GMT%2B8
????username: root
????password: root
????driver-class-name: com.mysql.cj.jdbc.Driver
????filters:?stat
????maxActive: 20
????initialSize: 1
????maxWait:?60000
????minIdle: 1
????timeBetweenEvictionRunsMillis:?60000
????minEvictableIdleTimeMillis:?300000
????validationQueryTimeout:?900000
????validationQuery: SELECT 1
????testWhileIdle: true
????testOnBorrow:?false
????testOnReturn: false
????poolPreparedStatements:?true
????maxOpenPreparedStatements:?201.自定義注解標(biāo)簽TargetDataSource,主要用于在接口處通過(guò)注解來(lái)切換數(shù)據(jù)源
package?com.lenovo.annotation;
import?com.lenovo.meiju.DataSourceType;
import?java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public?@interface?TargetDataSource {
??//默認(rèn)使用寫(xiě)庫(kù)
????DataSourceType value()?default?DataSourceType.write;
}2.定義切面DataSourceAop
通過(guò)獲取接口處的注解,獲取注解所需要切換的數(shù)據(jù)源名稱(chēng),從而來(lái)切換該數(shù)據(jù)源
package?com.lenovo.interceptor;
import?com.lenovo.annotation.TargetDataSource;
import?com.lenovo.config.DataSourceContextHolder;
import?lombok.extern.slf4j.Slf4j;
import?org.aspectj.lang.JoinPoint;
import?org.aspectj.lang.annotation.After;
import?org.aspectj.lang.annotation.Aspect;
import?org.aspectj.lang.reflect.MethodSignature;
import?org.springframework.stereotype.Component;
import?org.aspectj.lang.annotation.Before;
import?java.lang.reflect.Method;
@Aspect
@Component
@Slf4j
public?class?DataSourceAop?{
????@Before("@annotation(targetDataSource)")
????public?void?setWriteDataSourceType(JoinPoint point, TargetDataSource targetDataSource)?{
????????//獲得當(dāng)前訪(fǎng)問(wèn)的class
????????Class> className = point.getTarget().getClass();
????????//獲得訪(fǎng)問(wèn)的方法名
????????String methodName = point.getSignature().getName();
????????//得到方法的參數(shù)的類(lèi)型
????????Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
????????//獲取默認(rèn)的數(shù)據(jù)源名稱(chēng)
????????String dataSource = DataSourceContextHolder.DEFAULT_DS;
????????try?{
????????????// 得到訪(fǎng)問(wèn)的方法對(duì)象
????????????Method method = className.getMethod(methodName, argClass);
????????????// 判斷是否存在@注解
????????????if?(method.isAnnotationPresent(TargetDataSource.class)) {
????????????????TargetDataSource annotation = method.getAnnotation(TargetDataSource.class);
????????????????// 取出注解中的數(shù)據(jù)源名
????????????????dataSource = annotation.value().getType();
????????????}
????????} catch?(Exception e) {
????????????e.printStackTrace();
????????}
????????// 切換數(shù)據(jù)源
????????DataSourceContextHolder.setJdbcType(dataSource);
????}
????@After("@annotation(targetDataSource)")
????public?void?afterSwitchDS(JoinPoint point,TargetDataSource targetDataSource){
????????DataSourceContextHolder.clearDB();
????}
}3.設(shè)置本地線(xiàn)程全局變量
package?com.lenovo.config;
import?lombok.extern.slf4j.Slf4j;
@Slf4j
public?class?DataSourceContextHolder?{
????private?static?final?ThreadLocal local = new?ThreadLocal();
????public?static?final?String DEFAULT_DS = "write";//默認(rèn)數(shù)據(jù)源
????public?static?ThreadLocal getLocal()? {
????????return?local;
????}
????public?static?String getJdbcType()?{
????????return?local.get();
????}
????public?static?void?setJdbcType(String dbType)?{
????????log.info("dataSource切換到:"+dbType);
????????local.set(dbType);
????}
????// 清除數(shù)據(jù)源名
????public?static?void?clearDB()?{
????????local.remove();
????}
} 4.數(shù)據(jù)庫(kù)配置:解析application-pro.yml文件
通過(guò)@ConfigurationProperties注解 獲取配置文件屬性自動(dòng)配置,綁定其屬性
通過(guò)@Bean注解,申請(qǐng)實(shí)例對(duì)象
package?com.lenovo.config;
import?com.lenovo.meiju.DataSourceType;
import?lombok.extern.slf4j.Slf4j;
import?org.apache.ibatis.session.SqlSessionFactory;
import?org.mybatis.spring.SqlSessionFactoryBean;
import?org.springframework.beans.factory.annotation.Value;
import?org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import?org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import?org.springframework.context.annotation.Bean;
import?org.springframework.context.annotation.Configuration;
import?org.springframework.context.annotation.Import;
import?org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import?org.springframework.transaction.annotation.EnableTransactionManagement;
import?javax.annotation.Resource;
import?javax.sql.DataSource;
import?java.util.ArrayList;
import?java.util.HashMap;
import?java.util.List;
import?java.util.Map;
@Slf4j
@Configuration
@ConditionalOnClass({EnableTransactionManagement.class})
@Import({ DataBaseConfiguration.class})
public?class?MybatisConfiguration?{
????@Value("${datasource.type}")
????private?Class extends DataSource> dataSourceType;
????@Value("${datasource.readSize}")
????private?String dataSourceSize;
????@Resource(name = "writeDataSource")
????private?DataSource dataSource;
????@Resource(name = "readDataSource1")
????private?DataSource read1DataSources;
????@Resource(name = "readDataSource2")
????private?DataSource read2DataSources;
????List readDataSources;
????@Bean
????@ConditionalOnMissingBean
????public?SqlSessionFactory sqlSessionFactory()?throws?Exception {
????????SqlSessionFactoryBean sqlSessionFactoryBean = new?SqlSessionFactoryBean();
????????sqlSessionFactoryBean.setDataSource(roundRobinDataSouceProxy());
????????return?sqlSessionFactoryBean.getObject();
????}
????/**
?????* 設(shè)置默認(rèn)數(shù)據(jù)庫(kù),其他數(shù)據(jù)源
?????* @return
?????*/
????@Bean(name = "dynamicDataSource")
????public?AbstractRoutingDataSource roundRobinDataSouceProxy()?{
????????int?size = Integer.parseInt(dataSourceSize);
????????MyAbstractRoutingDataSource proxy = new?MyAbstractRoutingDataSource(size);
????????Map 5.通過(guò)枚舉法區(qū)分讀寫(xiě)庫(kù)標(biāo)識(shí)
package com.lenovo.meiju;
public?enum??DataSourceType {
????read("read", "從庫(kù)"),
????write("write", "主庫(kù)");
????private?String?type;
????private?String?name;
????public?String?getType() {
????????return?type;
????}
????public?void?setType(String?type) {
????????this.type = type;
????}
????public?String?getName() {
????????return?name;
????}
????public?void?setName(String?name) {
????????this.name = name;
????}
????DataSourceType(String?type, String?name) {
????????this.type = type;
????????this.name = name;
????}
}6.通過(guò)繼承AbstractRoutingDataSource實(shí)現(xiàn)其動(dòng)態(tài)選擇數(shù)據(jù)源
package?com.lenovo.config;
import?com.lenovo.meiju.DataSourceType;
import?org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import?java.util.concurrent.atomic.AtomicInteger;
public?class?MyAbstractRoutingDataSource?extends?AbstractRoutingDataSource?{
????//配置的讀庫(kù)數(shù)量
????private?final?int?dataSourceNumber;
????private?AtomicInteger count = new?AtomicInteger(0);
????/**
????* @dataSourceNumber??從庫(kù)的數(shù)量
????**/
????public?MyAbstractRoutingDataSource(int?dataSourceNumber)?{
????????this.dataSourceNumber = dataSourceNumber;
????}
????@Override
????protected?Object determineCurrentLookupKey()?{
????????//獲取通過(guò)aop設(shè)置的數(shù)據(jù)源名稱(chēng)
????????String typeKey = DataSourceContextHolder.getJdbcType();
????????if?(typeKey==null){//未加注解 默認(rèn)使用主庫(kù)
????????????typeKey=DataSourceType.write.getType();
????????}
????????//如果是當(dāng)前數(shù)據(jù)源是默認(rèn)主庫(kù),直接返回主庫(kù)
????????if?(typeKey.equals(DataSourceType.write.getType())){
????????????return?DataSourceType.write.getType();
????????}
????????//從庫(kù) 讀 簡(jiǎn)單負(fù)載均衡(輪詢(xún))
????????int?number = count.getAndAdd(1);
????????int?lookupKey = number % dataSourceNumber;
????????return?new?Integer(lookupKey);
????//如果不進(jìn)行負(fù)載,直接指定數(shù)據(jù)源的話(huà),則可以這邊修改
????}
}7.配置mybatis
package?com.lenovo.config;
import?com.lenovo.meiju.DataSourceType;
import?lombok.extern.slf4j.Slf4j;
import?org.apache.ibatis.session.SqlSessionFactory;
import?org.mybatis.spring.SqlSessionFactoryBean;
import?org.springframework.beans.factory.annotation.Value;
import?org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import?org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import?org.springframework.context.annotation.Bean;
import?org.springframework.context.annotation.Configuration;
import?org.springframework.context.annotation.Import;
import?org.springframework.jdbc.datasource.DataSourceTransactionManager;
import?org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import?org.springframework.transaction.PlatformTransactionManager;
import?org.springframework.transaction.annotation.EnableTransactionManagement;
import?javax.annotation.Resource;
import?javax.sql.DataSource;
import?java.util.ArrayList;
import?java.util.HashMap;
import?java.util.List;
import?java.util.Map;
@Slf4j
@Configuration
@ConditionalOnClass({EnableTransactionManagement.class})
@Import({ DataBaseConfiguration.class})
public?class?MybatisConfiguration?{
????@Value("${datasource.type}")
????private?Class extends DataSource> dataSourceType;
????@Value("${datasource.readSize}")
????private?String dataSourceSize;
????@Resource(name = "writeDataSource")
????private?DataSource dataSource;
????@Resource(name = "readDataSource1")
????private?DataSource read1DataSources;
????@Resource(name = "readDataSource2")
????private?DataSource read2DataSources;
????List readDataSources;
????@Bean
????@ConditionalOnMissingBean
????public?SqlSessionFactory sqlSessionFactory()?throws?Exception {
????????SqlSessionFactoryBean sqlSessionFactoryBean = new?SqlSessionFactoryBean();
????????sqlSessionFactoryBean.setDataSource(roundRobinDataSouceProxy());
????????return?sqlSessionFactoryBean.getObject();
????}
????/**
?????* 設(shè)置默認(rèn)數(shù)據(jù)庫(kù),其他數(shù)據(jù)源
?????* @return
?????*/
????@Bean(name = "dynamicDataSource")
????public?AbstractRoutingDataSource roundRobinDataSouceProxy()?{
????????int?size = Integer.parseInt(dataSourceSize);
????????MyAbstractRoutingDataSource proxy = new?MyAbstractRoutingDataSource(size);
????????Map targetDataSources = new?HashMap();
????????// DataSource writeDataSource = SpringContextHolder.getBean("writeDataSource");
????????// 寫(xiě)
????????targetDataSources.put(DataSourceType.write.getType(),dataSource);
????????// targetDataSources.put(DataSourceType.read.getType(),readDataSource);
????????//多個(gè)讀數(shù)據(jù)庫(kù)時(shí)
????????readDataSources=new?ArrayList();
????????readDataSources.add(read1DataSources);
????????readDataSources.add(read2DataSources);
????????for?(int?i = 0; i < size; i++) {
????????????targetDataSources.put(i, readDataSources.get(i));
????????}
????????proxy.setDefaultTargetDataSource(dataSource);
????????proxy.setTargetDataSources(targetDataSources);
????????return?proxy;
????}
????//配置Transaction,統(tǒng)一管理否則事務(wù)失效
????@Bean(name="transactionManager")
????public?PlatformTransactionManager transactionManager(){
????????return?new?DataSourceTransactionManager(roundRobinDataSouceProxy());
????}
} 8.數(shù)據(jù)源聲明
package?com.lenovo.config;
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;
@Slf4j
@Configuration
public class DataBaseConfiguration {
????@Value("${datasource.type}")
????private?Class extends?DataSource> dataSourceType;
????@Bean(name?= "writeDataSource")
????@Primary
????@ConfigurationProperties(prefix = "datasource.write")
????public DataSource writeDataSource() {
????????return?DataSourceBuilder.create().type(dataSourceType).build();
????}
????/**
?????* 有多少個(gè)從庫(kù)就要配置多少個(gè)
?????* @return
?????*/
????@Bean(name?= "readDataSource1")
????@ConfigurationProperties(prefix = "datasource.read1")
????public DataSource readDataSourceOne() {
????????return?DataSourceBuilder.create().type(dataSourceType).build();
????}
????@Bean(name?= "readDataSource2")
????@ConfigurationProperties(prefix = "datasource.read2")
????public DataSource readDataSourceTwo() {
????????return?DataSourceBuilder.create().type(dataSourceType).build();
????}
}評(píng)論
圖片
表情
