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"}