1.說明
在開發過程中經常遇到使用多數據源的時候,我們可以基於配置實現多數據源,也可以基於註解實現多數據源。剛好最近項目做數據遷移中時使用到多數據源,在此以demo形式展示如何使用Spring Boot基於註解整合多數據源。整合步驟及方法主要借鑑了網上開源項目renren-fast
。
2.環境準備
- 搭建物理環境(虛擬機安裝Mysql)
服務 | 說明 |
---|---|
192.168.1.100 | db1(數據庫) |
192.168.1.101 | db2(數據庫) |
Spring Boot | 2.1.5.RELEASE |
Mysql | 8.0.16 |
說明:
1.分別在兩臺虛擬機中安裝mysql數據庫,在此基於docker安裝(前提虛擬機安裝docker),主要步驟如下
# 拉取鏡像
$ docker pull mysql:latest
# 啓動鏡像,分別在主機掛載配置、日誌、數據
$ docker run -d --name mysql -p 3306:3306 -v /home/milk/docker/mysql/conf:/etc/mysql/conf.d -v /home/milk/docker/mysql/logs:/logs -v /home/milk/docker/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root mysql:latest
# 進行mysql容器
$ docker exec -it mysql /bin/bash
# 進行容器中mysql數據庫,密碼爲上邊設置的`root`
$ mysql -u root -p
# 創建數據庫
$ create database db1
2.創建兩張數據表t_study
及t_user
CREATE TABLE t_user
(
id int PRIMARY KEY NOT NULL AUTO_INCREMENT,
name varchar(20),
password varchar(20)
);
INSERT INTO `t_user` (`name`, `password`) VALUES ('user', 'qqwert35274');
-----------------------------------------------------------------------------------------------------------------------
CREATE TABLE t_study
(
id int PRIMARY KEY NOT NULL AUTO_INCREMENT,
name varchar(20),
password varchar(20)
);
INSERT INTO `t_study` (`name`, `password`) VALUES ('study', '1234543er');
完成上述操作,我們已經準備好mysql環境及數據庫
- 搭建項目環境
創建Maven工程,引入Spring Boot基本依賴
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.20</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
</dependency>
</dependencies>
完成上述步驟,我們的硬件環境和項目環境已經搭建完成,下邊進行具體整合。
3.演示案例
- 引入所需依賴
主要引入了druid、mybatisPlus、mysql依賴
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.20</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
</exclusion>
</exclusions>
</dependency>
- 配置文件配置多數據源
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
druid:
study:
url: jdbc:mysql://192.168.1.100:3306/db1
username: root
password: root
user:
url: jdbc:mysql://192.168.100.102:3306/db2
username: root
password: root
- 整合數據源
(1) 配置數據源定義
public interface DataSourceType {
String dbOne = "db1";
String dbTwo = "db2";
}
(2) 自定義數據源配置
@Configuration
public class DataSourceConfig {
@Bean("dbOne")
@ConfigurationProperties(prefix = "spring.datasource.druid.db1")
public DataSource dbOneDatasource(){
return DruidDataSourceBuilder.create().build();
}
@Bean("dbTwo")
@ConfigurationProperties(prefix = "spring.datasource.druid.db2")
public DataSource dbTwoDatasource(){
return DruidDataSourceBuilder.create().build();
}
@Bean
@Primary
public RoutingDataSource dataSource(@Qualifier(value = "dbOne") DataSource dbOne,@Qualifier(value = "dbTwo") DataSource dbTwo){
Map<Object,Object> datasourceMap = new HashMap<>();
datasourceMap.put(DataSourceType.dbOne, dbOne);
datasourceMap.put(DataSourceType.dbTwo, dbTwo);
return new RoutingDataSource(dbOne,datasourceMap);
}
}
說明
- 分別配置兩個
bean
,通過@ConfigurationProperties
配置前綴對應配置文件中配置的兩個數據源前綴 - 配置
RoutingDataSource
,該對象在下邊創建,主要通過其構造方法創建一個Map
保存配置的數據源,key:設置的數據源名稱,value:設置的數據源
,在此對應着key:db1,value:dboneDatasource
。
(3) 書寫RoutingDataSource
public class RoutingDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public RoutingDataSource(DataSource dbOne, Map<Object, Object> datasourceMap) {
super.setDefaultTargetDataSource(dbOne);
super.setTargetDataSources(datasourceMap);
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
return getDatasource();
}
public static String getDatasource() {
return contextHolder.get();
}
public static void setDatasource(String datasource) {
contextHolder.set(datasource);
}
public static void clearDatasource(){
contextHolder.remove();
}
}
說明:
- 通過構造方法,將上邊配置中創建的
Map
和dbOneDataSource
傳遞對象RoutingDataSource
- 構造方法中使用了父類
AbstractRoutingDataSource
三個方法,分別是創建默認數據源dbOneDataSource
,創建目標數據源Map
,設置後續操作。 determineCurrentLookupKey
主要是從當前線程ThreadLocal
中獲取數據源
(4) 書寫註解ExtDataSource
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface ExtDataSource{
String value() default "";
}
說明:該註解可以作用在對象、方法上
(5) 書寫切面ExtDataSourceAspect
@Order(Ordered.HIGHEST_PRECEDENCE)
@Aspect
@Component
public class ExtDataSourceAspect{
@Pointcut("@annotation(com.study.db.datasource.annotion.ExtDataSource)" +
"|| @within(com.study.db.datasource.annotion.ExtDataSource)")
public void datasourcePointcut() {
}
@Around("datasourcePointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
//獲取目標對象
Class targetClazz = pjp.getTarget().getClass();
//獲取目標方法
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Method targetMethod = methodSignature.getMethod();
//獲取對象和方法上註解值
ExtDataSourcetargetDB = (ExtDataSource) targetClazz.getAnnotation(ExtDataSource.class);
ExtDataSourcemethodDB = targetMethod.getAnnotation(ExtDataSource.class);
//判斷並處理
if (Objects.nonNull(targetDB) || Objects.nonNull(methodDB)) {
String value;
if (Objects.nonNull(methodDB)) {
value = methodDB.value();
} else {
value = targetDB.value();
}
RoutingDataSource.setDatasource(value);
}
try {
return pjp.proceed();
} finally {
RoutingDataSource.clearDatasource();
}
}
}
說明:
@Order
:定義順序,值越低表示優先級越高@Aspect
表示爲一切面,@Component
交給Spring
管理@Poingcut
:定義切入點,@annotation
表示帶該註解的方法,@within
表示該註解的對象都會進行切面- 切面邏輯處理,主要判斷方法或類上是否含有註解
@ExtDataSource
,如果存在,則獲取註解的value
值 ,該值對應配置的數據源key值。 - 通過後通
RoutingDataSource.setDatasource(value);
設置數據源,同時將該value
值存入線程的ThreadLocal
的Map
中
(6) 測試多數據源
- 創建兩個實體
@TableName("t_study")
@Data
public class Study {
/**
* 用戶ID
*/
@TableId
private Long id;
/**
* 用戶名
*/
private String name;
/**
* 密碼
*/
private String password;
}
--------------------------------------------------------------------------------------------------------------------
@TableName("t_user")
@Data
public class User {
/**
* 用戶ID
*/
@TableId
private Long id;
/**
* 用戶名
*/
private String name;
/**
* 密碼
*/
private String password;
}
- 啓動類排出默認數據,並引入自定義數據源配置
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@Import(DataSourceConfig.class)
public class DBApp {
public static void main(String[] args) {
SpringApplication.run(DBApp.class);
}
}
- 創建
Mapper
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
--------------------------------------------------------------------------------------------------------------------
@Mapper
public interface StudyMapper extends BaseMapper<Study> {
}
- 創建兩個
controller
@RestController
@IDatasouce(value = DataSourceType.dbOne)
public class StudyController {
@Resource
private StudyMapper studyMapper;
@GetMapping("/getStudy")
public Study getStudyById(@RequestParam("id") Integer id){
return studyMapper.selectById(id);
}
}
--------------------------------------------------------------------------------------------------------------------
@RestController
public class UserController {
@Resource
private UserMapper userMapper;
@GetMapping("/getUser")
@IDatasouce(value = DataSourceType.dbTwo)
public User getUserById(@RequestParam("id") Integer id){
User user = userMapper.selectById(id);
System.out.println(user);
return user;
}
}
測試結果,調用http://localhost:8888/getStudyById?id=1
和http://localhost:8888/getUserById?id=1
分別顯示如下,表示搭建成功
# 該數據來至dbOne中`t_user`表
{"id":1,"name":"user","password":"qqwert35274"}
# 該數據來至dbTwo中`t_study`表
{"id":1,"name":"study","password":"1234543er"}