SpringBoot动态数据源配置
序:数据源动态切换流程图如下:
1:pom.xml文件依赖声明
org.springframework.boot
spring-boot-starter-web
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.4
mysql
mysql-connector-java
8.0.12
org.springframework.boot
spring-boot-starter-aop
2:application.yml配置文件
配置多个数据源信息
server:
port: 9000
?
dynamic:
datasource:
ds1: # 数据源1
jdbc-url: jdbc:mysql://localhost:3306/test1?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
ds2: # 数据源2
jdbc-url: jdbc:mysql://localhost:3306/test2?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
?
mybatis:
mapper-locations: classpath:mapper/*.xml
configuration:
map-underscore-to-camel-case: true
3:数据源的配置到Spring管理
/**
* description 数据源配置类
*
* @author liekkas 2021/08/21 12:33
*/
@Configuration
public class DynamicDataSourceConfig {
?
@Bean(name = "dataSource1")
@ConfigurationProperties(prefix = "dynamic.datasource.ds1")
public DataSource dateSource1() {
return DataSourceBuilder.create().build();
}
?
@Bean(name = "dataSource2")
@ConfigurationProperties(prefix = "dynamic.datasource.ds2")
public DataSource dateSource2() {
return DataSourceBuilder.create().build();
}
?
@Bean
@Primary
public DynamicRoutingDataSource dynamicDataSource() {
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
?
//设置默认的数据源
dynamicRoutingDataSource.setDefaultTargetDataSource(dateSource1());
Map
注意:@ConfigurationProperties注解的prefix的值和配置文件application.yml必须要保持一致
3:配置数据源上下文
我们需要新建一个数据源上下文,用户记录当前线程使用的数据源是什么,以及记录所有注册成功的数据源的集合。对于线程级别的私有变量,我们首先ThreadLocal来实现。
/**
* description 设置当前线程数据源上下文类
*
* @author liekkas 2021/08/21 12:37
*/
public class DynamicDataSourceContextHolder {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
?
/**
* 存储已经注册的数据源的key
*/
public static Set
4:动态数据源路由
第三步我们以及新建了数据源上下文,用于存储我们当前线程的数据源,那么怎么通知spring用当前的数据源,spring提供一个接口,名为AbstractRoutingDataSource的抽象类,我们只需要重写determineCurrentLookupKey方法就可以,这个方法看名字就知道,就是返回当前线程的数据源,那我们只需要从我们刚刚的数据源上下文中取出当前数据源即可
/**
* description 数据源动态路由
*
* @author liekkas 2021/08/21 12:39
*/
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
?
private static final Logger logger = LoggerFactory.getLogger(DynamicRoutingDataSource.class);
?
@Override
protected Object determineCurrentLookupKey() {
String dataSourceName = DynamicDataSourceContextHolder.getDataSourceRouterKey();
logger.info("当前数据源是:{}", dataSourceName);
return DynamicDataSourceContextHolder.getDataSourceRouterKey();
}
}:
5:数据源动态切换注解
/**
* description 动态切换数据源注解
* 该注解可以同时作用于类和方法上,如果类和方法注解同时存在,则方法的注解优先级高于类上的注解优先级
* 也就是说类和方法同时存在该注解的话,则会使用方法的数据源
*
* @author liekkas 2021/08/21 12:40
*/
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String value() default "dataSource1";
}:
6:利用aop实现数据源动态切换
/**
* description 动态切换数据源切面
*
* @author liekkas 2021/08/21 12:42
*/
@Aspect
@Component
@Order(-1)
public class DynamicDataSourceAspect {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
?
/**
* 同时拦截标注DataSource的注解类上或者方法
*/
@Pointcut(value = "@within(com.liekkas.config.datasource.DataSource) || @annotation(com.liekkas.config.datasource.DataSource)")
public void datasource() {
}
?
?
@Before("datasource()")
public void changeDataSource(JoinPoint joinPoint) {
?
Class<?> klass = joinPoint.getTarget().getClass();
?
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
?
DataSource dataSource;
if (method.isAnnotationPresent(DataSource.class)) {
dataSource = method.getAnnotation(DataSource.class);
} else {
dataSource = klass.getAnnotation(DataSource.class);
}
?
//判断注解中的数据源是否在已注册的数据源中
if (DynamicDataSourceContextHolder.isExistDataSource(dataSource.value())) {
DynamicDataSourceContextHolder.setDataSourceRouterKey(dataSource.value());
logger.info("数据源设置为 > {}", dataSource.value());
} else {
throw new RuntimeException("数据源[" + dataSource.value() + "]不存在");
}
}
?
@After("datasource()")
public void restoreDataSource() {
DynamicDataSourceContextHolder.removeDataSourceRouterKey();
}
}
7:测试
7.1:controller
/**
* description
*
* @author liekkas 2021/01/09 15:47
*/
@RestController
public class PersonController {
?
@Resource
private PersonService personService;
?
@GetMapping("person")
public List findPerson() {
return personService.findPerson();
}
?
@GetMapping("student")
public List findStudent() {
return personService.findStudent();
}
}
7.2: service
public interface PersonService {
/**
* findPerson
*
* @return list
*/
List findPerson();
/**
* findStudent
*
* @return list
*/
List findStudent();
}
7.3: serviceImpl
@Service
@DataSource("dataSource1")
public class PersonServiceImpl implements PersonService {
?
@Resource
private PersonMapper personMapper;
?
@Override
public List findPerson() {
return personMapper.findPerson();
}
?
@Override
@DataSource("dataSource2")
public List findStudent() {
return personMapper.findStudent();
}
}
由于篇幅关系,mapper以及实体类不再描述
测试接口调用
http://localhost:9000/person
http://localhost:9000/student
至此数据源动态切换已经完成。