SpringBoot多數(shù)據(jù)源的兩種實(shí)現(xiàn)方式實(shí)例
前言
公司項(xiàng)目有連接多個(gè)不同數(shù)據(jù)庫的需求,特研究了一下,根據(jù)網(wǎng)上的資料,造了一個(gè)基于AOP方式的數(shù)據(jù)源切換輪子,但繼續(xù)探索,突然發(fā)現(xiàn)有開源的多數(shù)據(jù)源管理啟動(dòng)器。不過,本篇兩種方式都會介紹。
基于dynamic-datasource實(shí)現(xiàn)多數(shù)據(jù)源
dynamic-datasource介紹
dynamic-datasource-spring-boot-starter 是一個(gè)基于springboot的快速集成多數(shù)據(jù)源的啟動(dòng)器。
其支持 Jdk 1.7+, SpringBoot 1.4.x 1.5.x 2.x.x
dynamic-datasource特性
- 支持 數(shù)據(jù)源分組 ,適用于多種場景 純粹多庫 讀寫分離 一主多從 混合模式。
- 支持?jǐn)?shù)據(jù)庫敏感配置信息 加密 ENC()。
- 支持每個(gè)數(shù)據(jù)庫獨(dú)立初始化表結(jié)構(gòu)schema和數(shù)據(jù)庫database。
- 支持無數(shù)據(jù)源啟動(dòng),支持懶加載數(shù)據(jù)源(需要的時(shí)候再創(chuàng)建連接)。
- 支持 自定義注解 ,需繼承DS(3.2.0+)。
- 提供并簡化對Druid,HikariCp,BeeCp,Dbcp2的快速集成。
- 提供對Mybatis-Plus,Quartz,ShardingJdbc,P6sy,Jndi等組件的集成方案。
- 提供 自定義數(shù)據(jù)源來源 方案(如全從數(shù)據(jù)庫加載)。
- 提供項(xiàng)目啟動(dòng)后 動(dòng)態(tài)增加移除數(shù)據(jù)源 方案。
- 提供Mybatis環(huán)境下的 純讀寫分離 方案。
- 提供使用 spel動(dòng)態(tài)參數(shù) 解析數(shù)據(jù)源方案。內(nèi)置spel,session,header,支持自定義。
- 支持 多層數(shù)據(jù)源嵌套切換 。(ServiceA >>> ServiceB >>> ServiceC)。
- 提供 基于seata的分布式事務(wù)方案。
- 提供 本地多數(shù)據(jù)源事務(wù)方案。 附:不能和原生spring事務(wù)混用。
我們目前只探討使用dynamic-datasource進(jìn)行數(shù)據(jù)源切換,其他請自行搜索
dynamic-datasource的相關(guān)約定
- dynamic-datasource只做 切換數(shù)據(jù)源 這件核心的事情,并不限制你的具體操作,切換了數(shù)據(jù)源可以做任何CRUD。
- 配置文件所有以下劃線 _ 分割的數(shù)據(jù)源 首部 即為組的名稱,相同組名稱的數(shù)據(jù)源會放在一個(gè)組下。
- 切換數(shù)據(jù)源可以是組名,也可以是具體數(shù)據(jù)源名稱。組名則切換時(shí)采用負(fù)載均衡算法切換。
- 默認(rèn)的數(shù)據(jù)源名稱為 master ,你可以通過 spring.datasource.dynamic.primary 修改。
- 方法上的注解優(yōu)先于類上注解。
- DS支持繼承抽象類上的DS,暫不支持繼承接口上的DS。
引入dynamic-datasource依賴
<dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>${version}</version> </dependency>
配置數(shù)據(jù)源
spring: datasource: dynamic: primary: mysql #設(shè)置默認(rèn)的數(shù)據(jù)源或者數(shù)據(jù)源組,默認(rèn)值即為master strict: false #嚴(yán)格匹配數(shù)據(jù)源,默認(rèn)false. true未匹配到指定數(shù)據(jù)源時(shí)拋異常,false使用默認(rèn)數(shù)據(jù)源 datasource: mysql: url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver # 3.2.0開始支持SPI可省略此配置 pgsql: url: ENC(xxxxx) # 內(nèi)置加密 username: ENC(xxxxx) password: ENC(xxxxx) driver-class-name: org.postgresql.Driver
使用 @DS 切換數(shù)據(jù)源
@DS 可以注解在方法上或類上,同時(shí)存在就近原則 方法上注解 優(yōu)先于 類上注解。
注解 | 結(jié)果 |
---|---|
不使用@DS注解 | 默認(rèn)數(shù)據(jù)源,即primary: mysql |
@DS(“dsName”) | dsName可以為組名也可以為具體某個(gè)庫的名稱 |
@DS使用實(shí)例
@Service @DS("mysql") public class UserServiceImpl implements UserService { @Autowired private JdbcTemplate jdbcTemplate; // 不使用@DS注解則代表使用默認(rèn)數(shù)據(jù)源 // 如果類上存在,則使用類上標(biāo)注的數(shù)據(jù)源 public List selectAll() { return jdbcTemplate.queryForList("select * from user"); } @Override @DS("pgsql") // 方法上注解 優(yōu)先于 類上注解,即使類上標(biāo)注也優(yōu)先采用方法上的標(biāo)注 public List selectByCondition() { return jdbcTemplate.queryForList("select * from user where age >10"); } }
基于AOP手動(dòng)實(shí)現(xiàn)多數(shù)據(jù)源
本次代碼參考 https://github.com/mianshenglee/my-example/tree/master/multi-datasource/dynamic-datasource ,因源碼不滿足的需求,因此我在此基礎(chǔ)做了修改。
項(xiàng)目工程結(jié)構(gòu)
項(xiàng)目依賴
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>me.mason.demo</groupId> <artifactId>dynamic-datasource</artifactId> <version>0.0.1-SNAPSHOT</version> <name>dynamic-datasource</name> <description>Demo project for dynamic datasource</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!--spring boot--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.9</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!--mysql 驅(qū)動(dòng)--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!--mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.0</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
配置文件
server.port=8080 server.servlet.context-path=/dd logging.level.root=INFO logging.level.me.mason.demo.dynamicdatasource.mapper=DEBUG # mybatis-plus mybatis-plus.type-aliases-package=me.mason.demo.dynamicdatasource.entity # 默認(rèn)位置,可不配置 #mybatis-plus.mapper-locations=classpath*:/mapper/*.xml mybatis.mapper-locations=classpath*:/mapper/*.xml # 使用數(shù)據(jù)庫自增ID mybatis-plus.global-config.db-config.id-type=auto # master spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.master.url=jdbc:mysql://10.0.1.243:3306/scheduling?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8 spring.datasource.master.username=root spring.datasource.master.password=123456 # slave spring.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.slave.url=jdbc:mysql://10.0.1.243:3306/scheduling1?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8 spring.datasource.slave.username=root spring.datasource.slave.password=123456
自定義注解
// 標(biāo)記注解可使用在方法與類上 @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface DS { // 默認(rèn)值為MASTER String value() default DataSourceConstants.DS_KEY_MASTER; }
編寫DataSourceConstants
/** * 數(shù)據(jù)源常量 **/ public class DataSourceConstants { /** * master數(shù)據(jù)源 */ public static final String DS_KEY_MASTER = "master"; /** * slave數(shù)據(jù)源 */ public static final String DS_KEY_SLAVE = "slave"; }
動(dòng)態(tài)數(shù)據(jù)源名稱上下文處理
/** * 動(dòng)態(tài)數(shù)據(jù)源名稱上下文處理 **/ public class DynamicDataSourceContextHolder { /** * 動(dòng)態(tài)數(shù)據(jù)源名稱上下文 */ private static final ThreadLocal<String> DATASOURCE_CONTEXT_KEY_HOLDER = new ThreadLocal<>(); /** * 設(shè)置數(shù)據(jù)源 * @param key */ public static void setContextKey(String key){ DATASOURCE_CONTEXT_KEY_HOLDER.set(key); } /** * 獲取數(shù)據(jù)源名稱 * @return */ public static String getContextKey(){ String key = DATASOURCE_CONTEXT_KEY_HOLDER.get(); return key == null?DataSourceConstants.DS_KEY_MASTER:key; } /** * 刪除當(dāng)前數(shù)據(jù)源名稱 */ public static void removeContextKey(){ DATASOURCE_CONTEXT_KEY_HOLDER.remove(); } }
獲取當(dāng)前動(dòng)態(tài)數(shù)據(jù)源方法
/** * 動(dòng)態(tài)數(shù)據(jù)源 **/ public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getContextKey(); } }
動(dòng)態(tài)數(shù)據(jù)源配置
/** * 動(dòng)態(tài)數(shù)據(jù)源配置 **/ @EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class }) @Configuration // 此處我們 //@PropertySource("classpath:config/jdbc.properties") @MapperScan(basePackages = "me.mason.demo.dynamicdatasource.mapper") public class DynamicDataSourceConfig { @Bean(DataSourceConstants.DS_KEY_MASTER) // 需要與配置文件中對應(yīng) @ConfigurationProperties(prefix = "spring.datasource.master") public DruidDataSource masterDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean(DataSourceConstants.DS_KEY_SLAVE) @ConfigurationProperties(prefix = "spring.datasource.slave") public DruidDataSource slaveDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean @Primary public DynamicDataSource dynamicDataSource() { Map<Object, Object> dataSourceMap = new HashMap<>(2); dataSourceMap.put(DataSourceConstants.DS_KEY_MASTER, masterDataSource()); dataSourceMap.put(DataSourceConstants.DS_KEY_SLAVE, slaveDataSource()); //設(shè)置動(dòng)態(tài)數(shù)據(jù)源 DynamicDataSource dynamicDataSource = new DynamicDataSource(); dynamicDataSource.setTargetDataSources(dataSourceMap); dynamicDataSource.setDefaultTargetDataSource(masterDataSource()); return dynamicDataSource; } }
AOP切面
/** * 切面 */ @Aspect @Component //@Order(-10) public class DynamicDataSourceAspect { // 以在類上使用了@Service作為切入點(diǎn) @Pointcut("@within(org.springframework.stereotype.Service)") public void dataSourcePointCut() { } @Around("dataSourcePointCut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Class<?> aClass = Class.forName(signature.getDeclaringType().getName()); // 方法優(yōu)先,如果方法上存在注解,則優(yōu)先使用方法上的注解 if (signature.getMethod().isAnnotationPresent(DS.class)) { DynamicDataSourceContextHolder.setContextKey(signature.getMethod().getAnnotation(DS.class).value()); // 其次類優(yōu)先,如果類上存在注解,則使用類上的注解 }else if (aClass.isAnnotationPresent(DS.class)) { DynamicDataSourceContextHolder.setContextKey(aClass.getAnnotation(DS.class).value()); // 如果都不存在,則使用默認(rèn) } else { DynamicDataSourceContextHolder.setContextKey(DataSourceConstants.DS_KEY_MASTER); } try { return joinPoint.proceed(); } finally { DynamicDataSourceContextHolder.removeContextKey(); } } }
編寫TestUser實(shí)體
@Data @TableName("test_user") public class TestUser implements Serializable { private static final long serialVersionUID = 1L; /** id */ private Long id; /** 姓名 */ private String name; /** 手機(jī)號 */ private String phone; /** 職稱職別 */ private String title; /** 郵箱 */ private String email; /** 性別 */ private String gender; /** 出生時(shí)間 */ private Date dateOfBirth; /** 1:已刪除,0:未刪除 */ private Integer deleted; /** 創(chuàng)建時(shí)間 */ private Date sysCreateTime; /** 創(chuàng)建人 */ private String sysCreateUser; /** 更新時(shí)間 */ private Date sysUpdateTime; /** 更新人 */ private String sysUpdateUser; /** 版本號 */ private Long recordVersion; public TestUser() { } }
TestUserMapper
@Service //@DS(DataSourceConstants.DS_KEY_SLAVE) public class TestUserService { @Autowired private TestUserMapper testUserMapper; /** * 查詢master庫User * @return */ // @DS(DataSourceConstants.DS_KEY_MASTER) public List<TestUser> getMasterUser(){ QueryWrapper<TestUser> queryWrapper = new QueryWrapper<>(); return testUserMapper.selectAll(queryWrapper.isNotNull("name")); } /** * 查詢slave庫User * @return */ // @DS(DataSourceConstants.DS_KEY_MASTER) public List<TestUser> getSlaveUser(){ return testUserMapper.selectList(null); } }
TestUserService
@Service //@DS(DataSourceConstants.DS_KEY_SLAVE) public class TestUserService { @Autowired private TestUserMapper testUserMapper; /** * 查詢master庫User * @return */ // @DS(DataSourceConstants.DS_KEY_MASTER) public List<TestUser> getMasterUser(){ QueryWrapper<TestUser> queryWrapper = new QueryWrapper<>(); return testUserMapper.selectAll(queryWrapper.isNotNull("name")); } /** * 查詢slave庫User * @return */ // @DS(DataSourceConstants.DS_KEY_MASTER) public List<TestUser> getSlaveUser(){ return testUserMapper.selectList(null); } }
TestUserController
@RestController @RequestMapping("/user") public class TestUserController { @Autowired private TestUserService testUserService; /** * 查詢?nèi)? */ @GetMapping("/listall") public Object listAll() { int initSize = 2; Map<String, Object> result = new HashMap<>(initSize); List<TestUser> masterUser = testUserService.getMasterUser(); result.put("masterUser", masterUser); List<TestUser> slaveUser = testUserService.getSlaveUser(); result.put("getSlaveUser", slaveUser); return ResponseResult.success(result); } }
MapperXml
<?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="me.mason.demo.dynamicdatasource.mapper.TestUserMapper"> <select id="selectAll" resultType="me.mason.demo.dynamicdatasource.entity.TestUser"> select * from test_user <if test="ew!=null"> ${ew.customSqlSegment} </if> </select> </mapper>
啟動(dòng)測試
不使用注解
@Service //@DS(DataSourceConstants.DS_KEY_SLAVE) public class TestUserService { @Autowired private TestUserMapper testUserMapper; /** * 查詢master庫User * @return */ // @DS(DataSourceConstants.DS_KEY_MASTER) public List<TestUser> getMasterUser(){ QueryWrapper<TestUser> queryWrapper = new QueryWrapper<>(); return testUserMapper.selectAll(queryWrapper.isNotNull("name")); } /** * 查詢slave庫User * @return */ // @DS(DataSourceConstants.DS_KEY_MASTER) public List<TestUser> getSlaveUser(){ return testUserMapper.selectList(null); } }
效果
該代碼優(yōu)先級與使用框架效果一致,即不使用注解將默認(rèn)使用MASTER數(shù)據(jù)庫,方法上存在注解優(yōu)先使用方法上標(biāo)注的注解。
已知MASTER 6條數(shù)據(jù), SLAVE4條數(shù)據(jù)
訪問 http://127.0.0.1:8080/dd/user/listall 查看效果
類上使用注解
@Service @DS(DataSourceConstants.DS_KEY_SLAVE) public class TestUserService { @Autowired private TestUserMapper testUserMapper; /** * 查詢master庫User * @return */ // @DS(DataSourceConstants.DS_KEY_MASTER) public List<TestUser> getMasterUser(){ QueryWrapper<TestUser> queryWrapper = new QueryWrapper<>(); return testUserMapper.selectAll(queryWrapper.isNotNull("name")); } /** * 查詢slave庫User * @return */ // @DS(DataSourceConstants.DS_KEY_MASTER) public List<TestUser> getSlaveUser(){ return testUserMapper.selectList(null); } }
效果
方法上使用注解
@Service @DS(DataSourceConstants.DS_KEY_SLAVE) public class TestUserService { @Autowired private TestUserMapper testUserMapper; /** * 查詢master庫User * @return */ @DS(DataSourceConstants.DS_KEY_SLAVE) public List<TestUser> getMasterUser(){ QueryWrapper<TestUser> queryWrapper = new QueryWrapper<>(); return testUserMapper.selectAll(queryWrapper.isNotNull("name")); } /** * 查詢slave庫User * @return */ @DS(DataSourceConstants.DS_KEY_MASTER) public List<TestUser> getSlaveUser(){ return testUserMapper.selectList(null); } }
效果
總結(jié)
到此這篇關(guān)于SpringBoot多數(shù)據(jù)源的兩種實(shí)現(xiàn)方式的文章就介紹到這了,更多相關(guān)SpringBoot多數(shù)據(jù)源實(shí)現(xiàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot項(xiàng)目多數(shù)據(jù)源及mybatis 駝峰失效的問題解決方法
- SpringBoot詳解如何進(jìn)行整合Druid數(shù)據(jù)源
- SpringBoot整合Druid數(shù)據(jù)源的方法實(shí)現(xiàn)
- Springboot集成mybatis實(shí)現(xiàn)多數(shù)據(jù)源配置詳解流程
- SpringBoot超詳細(xì)講解多數(shù)據(jù)源集成
- SpringBoot多數(shù)據(jù)源切換實(shí)現(xiàn)代碼(Mybaitis)
- 使用SpringBoot配置多數(shù)據(jù)源的經(jīng)驗(yàn)分享
- 親手教你SpringBoot中的多數(shù)據(jù)源集成問題
- SpringBoot內(nèi)置數(shù)據(jù)源的持久化與解決方案
相關(guān)文章
MyBatis中模糊查詢使用CONCAT('%',#{str},'%')出錯(cuò)的解
這篇文章主要介紹了MyBatis中模糊查詢使用CONCAT('%',#{str},'%')出錯(cuò)的解決,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01java?-jar啟動(dòng)參數(shù)設(shè)置file.encoding編碼,解決中文亂碼的問題
這篇文章主要介紹了java?-jar啟動(dòng)參數(shù)設(shè)置file.encoding編碼,解決中文亂碼的問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07mybatis-plus插入一條數(shù)據(jù),獲取插入數(shù)據(jù)自動(dòng)生成的主鍵問題
這篇文章主要介紹了mybatis-plus插入一條數(shù)據(jù),獲取插入數(shù)據(jù)自動(dòng)生成的主鍵問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12List調(diào)用toString()方法后,去除兩頭的中括號實(shí)例
下面小編就為大家?guī)硪黄狶ist調(diào)用toString()方法后,去除兩頭的中括號實(shí)例。希望對大家有所幫助。一起跟隨小編過來看看吧2017-03-03Java日期轉(zhuǎn)換注解配置date?format時(shí)間失效
這篇文章主要為大家介紹了Java日期轉(zhuǎn)換注解配置date?format時(shí)間失效,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12Java 數(shù)據(jù)庫時(shí)間返回前端顯示錯(cuò)誤(差8個(gè)小時(shí))的解決方法
本文主要介紹了Java 數(shù)據(jù)庫時(shí)間返回前端顯示錯(cuò)誤(差8個(gè)小時(shí))的解決方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-08-08