SpringBoot多數(shù)據(jù)源配置并通過(guò)注解實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源
1. 環(huán)境準(zhǔn)備
1.1 數(shù)據(jù)庫(kù)準(zhǔn)備
一個(gè)本地環(huán)境的MySQL數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)mydb,創(chuàng)建表t_user
CREATE TABLE `t_user` (
`c_id` varchar(20) NOT NULL,
`c_username` varchar(20) DEFAULT NULL,
`c_password` varchar(20) DEFAULT NULL,
`c_gender` tinyint(2) DEFAULT NULL,
PRIMARY KEY (`c_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `mydb`.`t_user`(`c_id`, `c_username`, `c_password`, `c_gender`) VALUES ('1', '思思', '123', 1);
一個(gè)云服務(wù)器的MySQL數(shù)據(jù)庫(kù),創(chuàng)建數(shù)據(jù)庫(kù)book_db,創(chuàng)建表t_userinfo。
CREATE TABLE `t_user_info` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用戶id', `user_name` varchar(50) DEFAULT NULL COMMENT '用戶名', `password` varchar(255) DEFAULT NULL COMMENT '登錄密碼', `areaObj` varchar(255) DEFAULT NULL COMMENT '所在學(xué)院', `name` varchar(20) DEFAULT NULL COMMENT '姓名', `sex` tinyint(255) DEFAULT NULL COMMENT '性別', `user_photo` varchar(255) DEFAULT NULL COMMENT '學(xué)生照片', `birthday` varchar(20) DEFAULT NULL COMMENT '出生日期', `telephone` varchar(20) DEFAULT NULL COMMENT '聯(lián)系電話', `address` varchar(255) DEFAULT NULL COMMENT '家庭地址', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; INSERT INTO `book_db`.`t_user_info`(`id`, `user_name`, `password`, `areaObj`, `name`, `sex`, `user_photo`, `birthday`, `telephone`, `address`) VALUES (1, '張三', '123', '哈爾濱', '張三散', 1, '123', '02-16', '15756892458', '黑龍江省哈爾濱市');
創(chuàng)建數(shù)據(jù)庫(kù)chatroom,創(chuàng)建表admin。
CREATE TABLE `admin` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(20) NOT NULL COMMENT '登錄賬號(hào)', `nickname` varchar(20) NOT NULL COMMENT '昵稱', `password` varchar(255) NOT NULL COMMENT '密碼', `user_profile` varchar(255) DEFAULT NULL COMMENT '管理員頭像', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; INSERT INTO `chatroom`.`admin`(`id`, `username`, `nickname`, `password`, `user_profile`) VALUES (1, 'admin', '系統(tǒng)管理員', '$2a$10$PyloUEVGuO0fUZdfeIaROOTluRmccl.Scifa8S7Os0Wt.s4bDkb', 'https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1784117537,3335593911&fm=26&gp=0.jpg');
1.2 項(xiàng)目創(chuàng)建
創(chuàng)建SpringBoot項(xiàng)目,整合MyBatis-Plus。pom.xml引入的依賴:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!--mybatis plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.0.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--druid--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.9</version> </dependency> </dependencies>
配置讀取resource文件夾下的mapper文件
<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> </resource> </resources> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
2. ThreadLocal類介紹
3. AbstractRoutingDataSource類介紹
Spring Boot提供了AbstractRoutingDataSource 根據(jù)用戶定義的規(guī)則選擇要使用的數(shù)據(jù)源,這樣我們可以在每次數(shù)據(jù)庫(kù)操作前設(shè)置使用的數(shù)據(jù)源,實(shí)現(xiàn)可動(dòng)態(tài)路由的數(shù)據(jù)源。它的抽象方法determineCurrentLookupKey() 決定使用哪個(gè)數(shù)據(jù)源。

getConnection()獲取數(shù)據(jù)庫(kù)連接,根據(jù)查找lookup key鍵對(duì)不同目標(biāo)數(shù)據(jù)源的調(diào)用,通常是通過(guò)(但不一定)某些線程綁定的事物上下文來(lái)實(shí)現(xiàn)。通過(guò)這我們知道可以實(shí)現(xiàn):數(shù)據(jù)源的動(dòng)態(tài)切換,在程序運(yùn)行時(shí),把數(shù)據(jù)源動(dòng)態(tài)織入到程序中,靈活得進(jìn)行數(shù)據(jù)源切換,從而可以不依賴中間件,實(shí)現(xiàn)讀寫分離功能。
AbstractRoutingDataSource實(shí)現(xiàn)邏輯:
- 繼承抽象類AbstractRoutingDataSource,并實(shí)現(xiàn)determineCurrentLookupKey()方法。自定義LookupKey的選擇規(guī)則。
- 把配置的多個(gè)數(shù)據(jù)源放在AbstractRoutingDataSource的 targetDataSources和defaultTargetDataSource中(使用setDefaultTargetDataSource和setTargetDataSources方法),然后通過(guò)afterPropertiesSet()方法將數(shù)據(jù)源分別進(jìn)行復(fù)制到AbstractRoutingDataSource的resolvedDataSources屬性和resolvedDefaultDataSource屬性中。

調(diào)用AbstractRoutingDataSource的getConnection()的方法的時(shí)候,先調(diào)用determineTargetDataSource()方法返回DataSource在進(jìn)行g(shù)etConnection()。

determineTargetDataSource()方法通過(guò)調(diào)用determineCurrentLookupKey() 方法返回的lookupKey決定使用哪個(gè)數(shù)據(jù)源。

4. 具體實(shí)現(xiàn)
4.1 定義數(shù)據(jù)源枚舉類
定義數(shù)據(jù)源枚舉類DataSourceTypeEnum
public enum DataSourceTypeEnum {
/**
* chatroom
*/
CHATROOM("chatroom"),
/**
* book_db
*/
BOOK_DB("book_db"),
/**
* mydb
*/
MY_DB("mydb");
private final String name;
DataSourceTypeEnum(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
4.2 創(chuàng)建動(dòng)態(tài)多數(shù)據(jù)源類
定義一個(gè)動(dòng)態(tài)多數(shù)據(jù)源類DynamicDataSource用于管理不同線程間多個(gè)數(shù)據(jù)源的選擇和切換,擴(kuò)展 Spring 提供的 AbstractRoutingDataSource 抽象類,重寫 determineCurrentLookupKey 方法,其中的determineCurrentLookupKey() 方法用于決定使用哪個(gè)數(shù)據(jù)源。
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* ThreadLocal 用于提供線程局部變量,在多線程環(huán)境可以保證各個(gè)線程里的變量獨(dú)立于其它線程里的變量。
* 也就是說(shuō) ThreadLocal 可以為每個(gè)線程創(chuàng)建一個(gè)【單獨(dú)的變量副本】,相當(dāng)于線程的 private static 類型變量。
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 決定使用哪個(gè)數(shù)據(jù)源之前需要把多個(gè)數(shù)據(jù)源的信息以及默認(rèn)數(shù)據(jù)源信息配置好
*
* @param defaultTargetDataSource 默認(rèn)數(shù)據(jù)源
* @param targetDataSources 目標(biāo)數(shù)據(jù)源
*/
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
/**
* determineCurrentLookupKey決定使用哪個(gè)數(shù)據(jù)庫(kù)
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return getDataSource();
}
public static void setDataSource(String dataSource) {
CONTEXT_HOLDER.set(dataSource);
}
public static String getDataSource() {
return CONTEXT_HOLDER.get();
}
public static void clearDataSource() {
CONTEXT_HOLDER.remove();
}
}
4.3 創(chuàng)建動(dòng)態(tài)多數(shù)據(jù)源配置類
DynamicDataSourceConfig類作為配置類,讀取配置文件的三個(gè)數(shù)據(jù)源的配置,創(chuàng)建對(duì)應(yīng)DataSource類型的Bean。
@Configuration
public class DynamicDataSourceConfig {
@Bean(name="chatroom")
@ConfigurationProperties("spring.datasource.druid.first")
public DataSource dataSource1(){
return DruidDataSourceBuilder.create().build();
}
@Bean(name ="book_db")
@ConfigurationProperties("spring.datasource.druid.second")
public DataSource dataSource2(){
return DruidDataSourceBuilder.create().build();
}
@Bean(name="mydb")
@ConfigurationProperties("spring.datasource.druid.third")
public DataSource dataSource3(){
return DruidDataSourceBuilder.create().build();
}
@Bean(name="dynamicDataSource")
@Primary
public DynamicDataSource dataSource() {
Map<Object, Object> targetDataSources = new HashMap<>(5);
targetDataSources.put(DataSourceTypeEnum.CHATROOM.getName(), dataSource1());
targetDataSources.put(DataSourceTypeEnum.BOOK_DB.getName(), dataSource2());
targetDataSources.put(DataSourceTypeEnum.MY_DB.getName(), dataSource3());
return new DynamicDataSource(dataSource1(), targetDataSources);
}
}
4.4 自定義注解用于指定數(shù)據(jù)源
自定義注解@SpecifyDataSource用于在Service層方法上標(biāo)記要使用哪個(gè)數(shù)據(jù)源。這里定義默認(rèn)使用數(shù)據(jù)源 DataSourceType.CHATROOM。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SpecifyDataSource {
/**
* @return
*/
DataSourceTypeEnum value() default DataSourceTypeEnum.CHATROOM;
}
4.5 AOP實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源
定義數(shù)據(jù)源界面類DataSourceAspect,用于實(shí)現(xiàn)有SpecifyDataSource注解標(biāo)注的方法前切換注解指定的數(shù)據(jù)源。
@Aspect
@Component
@Order(value = 1)
public class DataSourceAspect {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Pointcut("@annotation(top.javahai.datasource.annotation.SpecifyDataSource)")
public void dataSourcePointCut() {
}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
SpecifyDataSource ds = method.getAnnotation(SpecifyDataSource.class);
if (ds == null) {
DynamicDataSource.setDataSource(DataSourceType.CHATROOM.getName());
logger.info("set datasource is " + DataSourceType.CHATROOM);
} else {
DynamicDataSource.setDataSource(ds.value().getName());
logger.info("set datasource is " + ds.value().getName());
}
try {
return point.proceed();
} finally {
DynamicDataSource.clearDataSource();
logger.info("clean datasource");
}
}
}
5. 測(cè)試使用
5.1 配置數(shù)據(jù)源
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver # 數(shù)據(jù)源1 spring.datasource.druid.first.url=jdbc:mysql://158.156.444.68:3306/chatroom?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8 spring.datasource.druid.first.username=root spring.datasource.druid.first.password=123456 # 數(shù)據(jù)源2 spring.datasource.druid.second.url=jdbc:mysql://158.156.444.68:3306/book_db?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8 spring.datasource.druid.second.username=root spring.datasource.druid.second.password=123456 #數(shù)據(jù)源3 spring.datasource.druid.third.url=jdbc:mysql:///mydb?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8 spring.datasource.druid.third.username=root spring.datasource.druid.third.password=123456 mybatis-plus.mapper-locations=classpath:mapper/*.xml #輸出sql執(zhí)行日志 mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
5.2 創(chuàng)建實(shí)體類
創(chuàng)建實(shí)體類Admin
public class Admin implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 登錄賬號(hào)
*/
private String username;
/**
* 昵稱
*/
private String nickname;
/**
* 密碼
*/
private String password;
/**
* 管理員頭像
*/
private String userProfile;
//省略getter/setter方法
創(chuàng)建實(shí)體類TUser
public class TUser implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "c_id", type = IdType.AUTO)
private Integer cId;
private String cUsername;
private String cPassword;
private Integer cGender;
}
創(chuàng)建實(shí)體類TUserinfo
@TableName(value = "t_user_info")
public class TUserinfo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* user_name
*/
private String userName;
/**
* 登錄密碼
*/
private String password;
/**
* 所在學(xué)院
*/
@TableField("areaObj")
private String areaObj;
/**
* 姓名
*/
private String name;
/**
* 性別
*/
private Integer sex;
/**
* 學(xué)生照片
*/
private String userPhoto;
/**
* 出生日期
*/
private String birthday;
/**
* 聯(lián)系電話
*/
private String telephone;
/**
* 家庭地址
*/
private String address;
}
創(chuàng)建UserVO用于測(cè)試
public class UserVO {
private List<Admin> adminList;
private List<TUserinfo> tUserinfos;
private List<TUser> tUsers;
}
5.3 服務(wù)層代碼
@Service
public class AdminServiceImpl extends ServiceImpl<AdminMapper, Admin> implements IAdminService {
public List<Admin> getAll(){
return this.list(null);
}
}
@Service
public class TUserinfoServiceImpl extends ServiceImpl<TUserinfoMapper, TUserinfo> implements ITUserinfoService {
@SpecifyDataSource(value = DataSourceTypeEnum.BOOK_DB)
public List<TUserinfo> selectAll(){
return this.list(null);
}
}
@Service
public class TUserServiceImpl extends ServiceImpl<TUserMapper, TUser> implements ITUserService {
@SpecifyDataSource(value = DataSourceTypeEnum.MY_DB)
public List<TUser> selectAll(){
return this.list(null);
}
}
public interface AdminMapper extends BaseMapper<Admin> {
}
public interface TUserinfoMapper extends BaseMapper<TUserinfo> {
}
public interface TUserMapper extends BaseMapper<TUser> {
}
5.4 控制層代碼
創(chuàng)建接口/test/list用于測(cè)試
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private AdminServiceImpl adminService;
@Autowired
private TUserinfoServiceImpl userinfoService;
@Autowired
private TUserServiceImpl userService;
@GetMapping("/list")
public UserVO list(){
List<Admin> adminList= adminService.getAll();
List<TUserinfo> tUserinfos = userinfoService.selectAll();
List<TUser> tUsers = userService.selectAll();
UserVO userVO = new UserVO();
userVO.setAdminList(adminList);
userVO.settUserinfos(tUserinfos);
userVO.settUsers(tUsers);
return userVO;
}
}
瀏覽器請(qǐng)求/test/list

查看控制臺(tái)輸出,查看數(shù)據(jù)源的切換日志

完整Demo代碼地址:https://github.com/JustCoding-Hai/learn-everyday/tree/master/learn-multi_data_source
到此這篇關(guān)于SpringBoot多數(shù)據(jù)源配置并通過(guò)注解實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源的文章就介紹到這了,更多相關(guān)SpringBoot 動(dòng)態(tài)切換數(shù)據(jù)源內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- springboot dynamic多數(shù)據(jù)源demo以及常見切換、事務(wù)的問(wèn)題
- Springboot實(shí)現(xiàn)多數(shù)據(jù)源切換詳情
- SpringBoot基于AbstractRoutingDataSource實(shí)現(xiàn)多數(shù)據(jù)源動(dòng)態(tài)切換
- SpringBoot多數(shù)據(jù)源切換實(shí)現(xiàn)代碼(Mybaitis)
- SpringBoot實(shí)現(xiàn)多數(shù)據(jù)源的切換實(shí)踐
- SpringBoot?+DynamicDataSource切換多數(shù)據(jù)源的全過(guò)程
- springboot中mybatis多數(shù)據(jù)源動(dòng)態(tài)切換實(shí)現(xiàn)
- Springboot如何設(shè)置多數(shù)據(jù)源,隨時(shí)切換
相關(guān)文章
詳解Spring中使用xml配置bean的細(xì)節(jié)
本篇文章主要介紹了Spring中使用xml配置bean的細(xì)節(jié),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-06-06
java開源好用的簡(jiǎn)繁轉(zhuǎn)換類庫(kù)推薦
這篇文章主要為大家介紹了java開源好用的簡(jiǎn)繁轉(zhuǎn)換類庫(kù)推薦,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
Java 數(shù)據(jù)庫(kù)連接池 Tomcat介紹
這篇文章主要給大家分享了 Java 數(shù)據(jù)庫(kù)連接池 Tomcat介紹,omcat 是一個(gè)小型的輕量級(jí)應(yīng)用服務(wù)器,在中小型系統(tǒng)和并發(fā)訪問(wèn)用戶不是很多的場(chǎng)合下被普遍使用,是開發(fā)和調(diào)試JSP 程序的首選。下面來(lái)看看文章內(nèi)容的詳細(xì)介紹吧2021-11-11
SpringBoot與SpringMVC中參數(shù)傳遞的原理解析
這篇文章主要介紹了SpringBoot與SpringMVC中參數(shù)傳遞的原理,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-07-07
spring boot基于DRUID實(shí)現(xiàn)數(shù)據(jù)源監(jiān)控過(guò)程解析
這篇文章主要介紹了spring boot基于DRUID實(shí)現(xiàn)數(shù)據(jù)源監(jiān)控過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
淺談SpringBoot項(xiàng)目打成war和jar的區(qū)別
這篇文章主要介紹了淺談SpringBoot項(xiàng)目打成war和jar的區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11

