springboot 实现主从数据库动态切换,可实现读写分离
使用 AbstractRoutingDataSource 實現功能,代碼完整貼出,直接放心食用。?
從AbstractRoutingDataSource源碼角度簡單分析為什么可以實現數據庫動態切換。
前言
主從數據庫的配置,實現數據同步,配置可參考:
windows配置mysql8.0主從數據庫_追尋光的方向的博客-CSDN博客
一、AbstractRoutingDataSource
Spring boot提供了AbstractRoutingDataSource 根據用戶定義的規則選擇當前的數據源,這樣我們每次訪問數據庫之前,設置使用的數據源。從而實現動態切換數據源。
1.源碼簡單分析
?
?AbstractRoutingDataSource 繼承了?AbstractDataSource 類 從而實現了DataSource接口的getConnection()方法。
在AbstractRoutingDataSource 實現 getConnection 的具體方法源碼可以看到 ,最終獲取的數據源是DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey); 這一行。
resolvedDataSources是一個Map對象,就是說動態的數據源是放在這個Map對象的。
Object lookupKey = determineCurrentLookupKey(); 提供一個Map的key來獲取
那往下看,我們看下這個?resolvedDataSources 是怎么初始化的,
afterPropertiesSet 方法是?InitializingBean 接口的實現,InitializingBean接口為bean提供了初始化方法的方式,凡是繼承該接口的類,在初始化bean的時候都會執行該方法。
但是源碼中 resolvedDataSources 的來源是 targetDataSources?
那再來看看targetDataSources 是怎么初始化的。
2.源碼分析結論
到此,我們思路就有了
1.在每次數據庫訪問的時候能控制讀取存放數據源resolvedDataSources的key值。
2.調用setTargetDataSources方法,來初始化targetDataSources對象。
二、實現步驟
1.創建一個類繼承AbstractRoutingDataSource
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;public class RoutingDataSource extends AbstractRoutingDataSource {private Logger logger = LogManager.getLogger();@Overrideprotected Object determineCurrentLookupKey() {String dataSource = RoutingDataSourceHolder.getDataSource();logger.info("使用數據源:{}", dataSource);return dataSource;} }重寫?determineCurrentLookupKey方法,返回要使用的數據源key值。
2.創建一個管理數據源key值得類RoutingDataSourceHolder?
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger;public class RoutingDataSourceHolder {private static Logger logger = LogManager.getLogger();private static final ThreadLocal<String> dataSources = new ThreadLocal<>();//一個事務內用同一個數據源public static void setDataSource(String dataSourceName) {if (dataSources.get() == null) {dataSources.set(dataSourceName);logger.info("設置數據源:{}", dataSourceName);}}public static String getDataSource() {return dataSources.get();}public static void clearDataSource() {dataSources.remove();} }代碼設置了一個事務內使用同一個數據源。
兩個類解決了動態數據源key值的問題,下面處理初始化targetDataSources對象。
3.配置主從數據庫類DataSourceConfigurer
1.DataSourceConfigurer
import com.alibaba.druid.pool.DruidDataSource; import com.custom.common.utils.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.Primary;import javax.sql.DataSource; import java.util.HashMap; import java.util.Map;/*** 配置主從數據庫*/ @Configuration public class DataSourceConfigurer {private Logger logger = LogManager.getLogger();public final static String MASTER_DATASOURCE = "masterDataSource";public final static String SLAVE_DATASOURCE = "slaveDataSource";@Bean(MASTER_DATASOURCE)@ConfigurationProperties(prefix = "spring.datasource")public DruidDataSource masterDataSource(DataSourceProperties properties) {DruidDataSource build = properties.initializeDataSourceBuilder().type(DruidDataSource.class).build();logger.info("配置主數據庫:{}", build);return build;}@Bean(SLAVE_DATASOURCE)@ConfigurationProperties(prefix = "spring.slave-datasource")public DruidDataSource slaveDataSource(DataSourceProperties properties) {DruidDataSource build = properties.initializeDataSourceBuilder().type(DruidDataSource.class).build();logger.info("配置從數據庫:{}", build);return build;}/*** Primary 優先使用該Bean* DependsOn 先執行主從數據庫的配置* Qualifier 指定使用哪個Bean** @param masterDataSource* @param slaveDataSource* @return*/@Bean@Primary@DependsOn(value = {MASTER_DATASOURCE, SLAVE_DATASOURCE})public DataSource routingDataSource(@Qualifier(MASTER_DATASOURCE) DruidDataSource masterDataSource,@Qualifier(SLAVE_DATASOURCE) DruidDataSource slaveDataSource) {if (StringUtils.isBlank(slaveDataSource.getUrl())) {logger.info("沒有配置從數據庫,默認使用主數據庫");return masterDataSource;}Map<Object, Object> map = new HashMap<>();map.put(DataSourceConfigurer.MASTER_DATASOURCE, masterDataSource);map.put(DataSourceConfigurer.SLAVE_DATASOURCE, slaveDataSource);RoutingDataSource routing = new RoutingDataSource();//設置動態數據源routing.setTargetDataSources(map);//設置默認數據源routing.setDefaultTargetDataSource(masterDataSource);logger.info("主從數據庫配置完成");return routing;} }設置初始化targetDataSources對象關鍵代碼
Map<Object, Object> map = new HashMap<>(); map.put(DataSourceConfigurer.MASTER_DATASOURCE, masterDataSource); map.put(DataSourceConfigurer.SLAVE_DATASOURCE, slaveDataSource); RoutingDataSource routing = new RoutingDataSource(); //設置動態數據源 routing.setTargetDataSources(map); //設置默認數據源 routing.setDefaultTargetDataSource(masterDataSource);2.application.properties
# ---------------------------------------- # 主數據庫 # ---------------------------------------- spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.url=jdbc:mysql://127.0.0.1:3306/custom?useunicode=true&characterEncoding=utf8&serverTimezone=GMT%2b8 spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver# ---------------------------------------- # 從數據庫 # ---------------------------------------- spring.slave-datasource.type=com.alibaba.druid.pool.DruidDataSource spring.slave-datasource.url=jdbc:mysql://127.0.0.1:3309/custom?useunicode=true&characterEncoding=utf8&serverTimezone=GMT%2b8 spring.slave-datasource.username=root spring.slave-datasource.password=root spring.slave-datasource.driver-class-name=com.mysql.cj.jdbc.Driver一個配置類處理了targetDataSources對象的初始化.
那問題都處理了,那具體要怎么使用呢,關鍵就是在事務之前調用RoutingDataSourceHolder.setDataSource()方法就可以了。我們寫一個aop實現吧。
4.創建aop注解和類
1.DataSourceWith
import java.lang.annotation.*;@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @Documented public @interface DataSourceWith {String key() default ""; }2.DataSourceWithAspect
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.*; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component;import java.lang.reflect.Method;@Aspect @Order(-1)// 保證該AOP在@Transactional之前運行 @Component public class DataSourceWithAspect {/*** 使用DataSourceWith注解就攔截*/@Pointcut("@annotation(com.custom.configure.datasource.DataSourceWith)||@within(com.custom.configure.datasource.DataSourceWith)")public void doPointcut() {}/*** 方法前,為了在事務前設置*/@Before("doPointcut()")public void doBefore(JoinPoint joinPoint) {MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();Method method = methodSignature.getMethod();// 獲取注解對象DataSourceWith dataSource = method.getAnnotation(DataSourceWith.class);if (dataSource == null) {//方法沒有就獲取類上的dataSource = method.getDeclaringClass().getAnnotation(DataSourceWith.class);}String key = dataSource.key();RoutingDataSourceHolder.setDataSource(key);}@After("doPointcut()")public void doAfter(JoinPoint joinPoint) {RoutingDataSourceHolder.clearDataSource();}}3.使用和效果
@DataSourceWith在方法上或者類上都可以。
/*** 獲取部門列表**/@DataSourceWith(key = DataSourceConfigurer.SLAVE_DATASOURCE)public List<Dept> findDeptTree() {logger.debug("獲取部門樹數據");List<Dept> deptList = new ArrayList<>();return deptList;}結果:動態切換成功
?
總結
以上是生活随笔為你收集整理的springboot 实现主从数据库动态切换,可实现读写分离的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 重庆邮电工商管理类转计算机专业,2021
- 下一篇: js仿饿了吗?谁去拿外卖小游戏(锻炼布局