Springboot項目實現(xiàn)Mysql多數(shù)據(jù)源切換的完整實例
一、分析AbstractRoutingDataSource抽象類源碼
關注import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource以下變量
@Nullable private Map<Object, Object> targetDataSources; // 目標數(shù)據(jù)源 @Nullable private Object defaultTargetDataSource; // 默認目標數(shù)據(jù)源 @Nullable private Map<Object, DataSource> resolvedDataSources; // 解析的數(shù)據(jù)源 @Nullable private DataSource resolvedDefaultDataSource; // 解析的默認數(shù)據(jù)源
這兩組變量是相互對應的,在熟悉多實例數(shù)據(jù)源切換代碼的不難發(fā)現(xiàn),當有多個數(shù)據(jù)源的時候,一定要指定一個作為默認的數(shù)據(jù)源;
在這里也同理,當同時初始化多個數(shù)據(jù)源的時候,需要顯式的調用setDefaultTargetDataSource方法指定一個作為默認數(shù)據(jù)源;
我們需要關注的是Map<Object, Object> targetDataSources和Map<Object, DataSource> resolvedDataSources;
targetDataSources是暴露給外部程序用來賦值的,而resolvedDataSources是程序內部執(zhí)行時的依據(jù),因此會有一個賦值的操作;
根據(jù)這段源碼可以看出,每次執(zhí)行時,都會遍歷targetDataSources內的所有元素并賦值給resolvedDataSources;這樣如果我們在外部程序新增一個新的數(shù)據(jù)源,都會添加到內部使用,從而實現(xiàn)數(shù)據(jù)源的動態(tài)加載。
繼承該抽象類的時候,必須實現(xiàn)一個抽象方法:protected abstract Object determineCurrentLookupKey(),該方法用于指定到底需要使用哪一個數(shù)據(jù)源。
二、實現(xiàn)多數(shù)據(jù)源切換和動態(tài)數(shù)據(jù)源加載
A - 配置文件信息
application.yml文件
server: port: 18080 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource druid: # 主數(shù)據(jù)源 master-db: driverClassName: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.223.129:13306/test_master_db?characterEncoding=utf-8 username: josen password: josen # 從數(shù)據(jù)源 slave-db: driverClassName: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.223.129:13306/test_slave_db?characterEncoding=utf-8 username: josen password: josen mybatis: mapper-locations: classpath:mapper/*.xml logging: path: ./logs/mydemo20201105.log level: com.josen.mydemo20201105: debug
maven 依賴
<dependencies> <!-- AOP --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>
B - 編碼實現(xiàn)
1、創(chuàng)建一個DynamicDataSource類,繼承AbstractRoutingDataSource抽象類,實現(xiàn)determineCurrentLookupKey方法,通過該方法指定當前使用哪個數(shù)據(jù)源;
2、 在DynamicDataSource類中通過ThreadLocal維護一個全局數(shù)據(jù)源名稱,后續(xù)通過修改該名稱實現(xiàn)動態(tài)切換;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import javax.sql.DataSource; import java.util.Map; /** * @ClassName DynamicDataSource * @Description 設置動態(tài)數(shù)據(jù)源 * @Author Josen * @Date 2020/11/5 14:28 **/ public class DynamicDataSource extends AbstractRoutingDataSource { // 通過ThreadLocal維護一個全局唯一的map來實現(xiàn)數(shù)據(jù)源的動態(tài)切換 private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) { super.setDefaultTargetDataSource(defaultTargetDataSource); super.setTargetDataSources(targetDataSources); super.afterPropertiesSet(); } /** * 指定使用哪一個數(shù)據(jù)源 */ @Override protected Object determineCurrentLookupKey() { return getDataSource(); } public static void setDataSource(String dataSource) { contextHolder.set(dataSource); } public static String getDataSource() { return contextHolder.get(); } public static void clearDataSource() { contextHolder.remove(); } }
3、創(chuàng)建DynamicDataSourceConfig類,引入application.yaml配置的多個數(shù)據(jù)源信息,構建多個數(shù)據(jù)源;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * @ClassName DynamicDataSourceConfig * @Description 引入動態(tài)數(shù)據(jù)源,構建數(shù)據(jù)源 * @Author Josen * @Date 2020/11/5 14:23 **/ @Configuration @Component public class DynamicDataSourceConfig { /** * 讀取application配置,構建master-db數(shù)據(jù)源 */ @Bean @ConfigurationProperties("spring.datasource.druid.master-db") public DataSource myMasterDataSource(){ return DruidDataSourceBuilder.create().build(); } /** * 讀取application配置,構建slave-db數(shù)據(jù)源 */ @Bean @ConfigurationProperties("spring.datasource.druid.slave-db") public DataSource mySlaveDataSource(){ return DruidDataSourceBuilder.create().build(); } /** * 讀取application配置,創(chuàng)建動態(tài)數(shù)據(jù)源 */ @Bean @Primary public DynamicDataSource dataSource(DataSource myMasterDataSource, DataSource mySlaveDataSource) { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put("master-db",myMasterDataSource); targetDataSources.put("slave-db", mySlaveDataSource); // myMasterDataSource=默認數(shù)據(jù)源 // targetDataSources=目標數(shù)據(jù)源(多個) return new DynamicDataSource(myMasterDataSource, targetDataSources); } }
4、 創(chuàng)建MyDataSource自定義注解,后續(xù)AOP通過該注解作為切入點,通過獲取使用該注解存入不同的值動態(tài)切換指定的數(shù)據(jù)源;
import java.lang.annotation.*; /** * 自定義數(shù)據(jù)源注解 * 在需要切換數(shù)據(jù)源的Service層方法上添加此注解,指定數(shù)據(jù)源名稱 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyDataSource { String name() default ""; }
5、 創(chuàng)建DataSourceAspectAOP切面類,以MyDataSource注解為切入點作拓展。在執(zhí)行被@MyDataSource注解的方法時,獲取該注解傳入的name,并切換到指定數(shù)據(jù)源執(zhí)行,執(zhí)行完成后切換回默認數(shù)據(jù)源;
import com.josen.mydemo20201105.annotation.MyDataSource; import com.josen.mydemo20201105.datasource.DynamicDataSource; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.sql.DataSource; import java.lang.reflect.Method; /** * @ClassName DataSourceAspect * @Description Aop切面類配置 * @Author Josen * @Date 2020/11/5 14:35 **/ @Aspect @Component @Slf4j public class DataSourceAspect { private static final Logger logger = LoggerFactory.getLogger(DataSourceAspect.class); /** * 設置切入點 * 只有調用@MyDataSource注解的方法才會觸發(fā)around */ @Pointcut("@annotation(com.josen.mydemo20201105.annotation.MyDataSource)") public void dataSourcePointCut() { } /** * 截取使用MyDataSource注解的方法,切換指定數(shù)據(jù)源 * 環(huán)繞切面:是(前置&后置&返回&異常)通知的結合體,更像是動態(tài)代理的整個過程 * @param point */ @Around("dataSourcePointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); logger.info("execute DataSourceAspect around=========>"+method.getName()); // 1. 獲取自定義注解MyDataSource,查看是否配置指定數(shù)據(jù)源名稱 MyDataSource dataSource = method.getAnnotation(MyDataSource.class); if(dataSource == null){ // 1.1 使用默認數(shù)據(jù)源 DynamicDataSource.setDataSource("master-db"); }else { // 1.2 使用指定名稱數(shù)據(jù)源 DynamicDataSource.setDataSource(dataSource.name()); logger.info("使用指定名稱數(shù)據(jù)源=========>"+dataSource.name()); } try { return point.proceed(); } finally { // 后置處理 - 恢復默認數(shù)據(jù)源 DynamicDataSource.clearDataSource(); } } }
6、 配置啟動類
import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; @SpringBootApplication(exclude= {DataSourceAutoConfiguration.class}) // 不加載默認數(shù)據(jù)源配置 @MapperScan(basePackages = "com.josen.mydemo.mapper") public class MydemoApplication { public static void main(String[] args) { SpringApplication.run(MydemoApplication.class, args); } }
7、 到這里基本上已經完成,剩下的就是測試是否正確切換數(shù)據(jù)源了;
Mapper層
@Repository public interface PermissionMapper { List<Permission> findAll(); }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.josen.mydemo.mapper.PermissionMapper"> <select id="findAll" resultType="com.josen.mydemo20201105.pojo.Permission"> select * from t_permission; </select> </mapper>
Service層
@Service public class PermissionService { @Autowired private PermissionMapper permissionMapper; // 切換從庫數(shù)據(jù)源 @MyDataSource(name = "slave-db") public List<Permission> findSlaveAll(){ return permissionMapper.findAll(); } // 默認數(shù)據(jù)源 public List<Permission> findAll(){ return permissionMapper.findAll(); } }
Controller層
@RestController @RequestMapping("/permission") public class PermissionController { @Autowired private PermissionService permissionService; // 測試獲取默認master-db數(shù)據(jù) @GetMapping("/master") public List<Permission> handlerFindAll() { List<Permission> list = permissionService.findAll(); return list; } // 測試獲取指定slave-db數(shù)據(jù) @GetMapping("/slave") public List<Permission> handlerFindAll2() { List<Permission> list = permissionService.findSlaveAll(); return list; } }
C - 測試數(shù)據(jù)源切換
Mysql數(shù)據(jù)
接口返回數(shù)據(jù)
Demo源碼地址:https://gitee.com/taco-gigigi/multiple-data-sources
總結
到此這篇關于Springboot項目實現(xiàn)Mysql多數(shù)據(jù)源切換的文章就介紹到這了,更多相關Springboot項目Mysql多數(shù)據(jù)源切換內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
基于MyBatis的數(shù)據(jù)持久化框架的使用詳解
Mybatis是一個優(yōu)秀的開源、輕量級持久層框架,它對JDBC操作數(shù)據(jù)庫的過程進行封裝。本文將為大家講解一下基于MyBatis的數(shù)據(jù)持久化框架的使用,感興趣的可以了解一下2022-08-08SpringBoot如何讀取war包jar包和Resource資源
這篇文章主要介紹了SpringBoot如何讀取war包jar包和Resource資源,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-01-01Spring整合多數(shù)據(jù)源實現(xiàn)動態(tài)切換的實例講解
下面小編就為大家?guī)硪黄猄pring整合多數(shù)據(jù)源實現(xiàn)動態(tài)切換的實例講解。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-07-07AsyncHttpClient?RequestFilter請求篩選源碼解讀
這篇文章主要為大家介紹了AsyncHttpClient?RequestFilter請求篩選源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-12-12springboot使用war包部署到外部tomcat過程解析
這篇文章主要介紹了springboot使用war包部署到外部tomcat過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-01-01ShardingSphere jdbc集成多數(shù)據(jù)源的實現(xiàn)步驟
本文主要介紹了ShardingSphere jdbc集成多數(shù)據(jù)源的實現(xiàn)步驟,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-10-10Java was started but returned exit code=13問題解決案例詳解
這篇文章主要介紹了Java was started but returned exit code=13問題解決案例詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下2021-09-09