springboot 配置多数据源(Aop+注解实现)
在实际项目中很多时候会涉及到多个数据库的访问,或者数据库读写分离的形式。
下面通过使用 Aspect+注解来实现mybatis的多数据源配置
动态数据源流程说明
Spring Boot 的动态数据源,本质上是把多个数据源存储在一个 Map 中,当需要使用某个数据源时,从 Map 中获取此数据源进行处理。而在 Spring 中,已提供了抽象类 AbstractRoutingDataSource
来实现此功能。因此,我们在实现动态数据源的,只需要继承它,实现自己的获取数据源逻辑即可。动态数据源流程如下所示:
用户访问应用,在需要访问不同的数据源时,根据自己的数据源路由逻辑,访问不同的数据源,实现对应数据源的操作。本示例中的两数据库的分别有一个表 t_user
,表结构一致,为便于说明,两个表中的数据是不一样的。
实现动态数据源
1.数据库连接信息配置
在application.yml中添加如下信息,可根据自己的项目进行替换相应的prefix和连接信息。
springboot:
datasource:
master:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/spring?useUnicode=true&characherEncoding=utf-8&useSSL=true&serverTimezone=UTC
username: root
password: 123456
slave:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/spring1?useUnicode=true&characherEncoding=utf-8&useSSL=true&serverTimezone=UTC
username: root
password: 123456
2.数据源配置
根据连接信息,把数据源注入到 Spring 中,添加 DynamicDataSourceConfig
文件,配置如下:
@Configuration
public class DynamicDataSourceConfig {
@Bean(DataSourceConstants.DS_KEY_MASTER)
@ConfigurationProperties(prefix = "springboot.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(DataSourceConstants.DS_KEY_SLAVE)
@ConfigurationProperties(prefix = "springboot.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
}
DataSourceConstants
类:
public class DataSourceConstants {
public final static String DS_KEY_MASTER = "master";
public final static String DS_KEY_SLAVE = "slave";
}
分别将master和slave数据源注入到spring容器中。
3.动态数据源设置
添加动态数据源类
DynamicDataSource
和 DynamicDataSourceContextHolder
类
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getContextKey();
}
}
public class DynamicDataSourceContextHolder {
/* 动态数据源名称上下文*/
private static final ThreadLocal<String> DATASOURCE_CONTEXT_KEY_HOLDER = new ThreadLocal<>();
/* 设置/切换数据源*/
public static void setContextKey(String key) {
System.out.println("切换数据源"+key);
DATASOURCE_CONTEXT_KEY_HOLDER.set(key);
}
/* 获取数据源名称 */
public static String getContextKey() {
String key = DATASOURCE_CONTEXT_KEY_HOLDER.get();
return key == null ? DataSourceConstants.DS_KEY_MASTER : key;
}
/*删除当前数据源名称*/
public static void removeContextKey() {
DATASOURCE_CONTEXT_KEY_HOLDER.remove();
}
}
3.设置动态数据源为主数据源
在前面的数据源配置文件 DynamicDataSourceConfig
中,添加以下代码:
@Bean
@Primary
public DataSource dynamicDataSource() {
Map<Object, Object> dataSourceMap = new HashMap<>(2);
dataSourceMap.put(DataSourceConstants.DS_KEY_MASTER, masterDataSource());
dataSourceMap.put(DataSourceConstants.DS_KEY_SLAVE, slaveDataSource());
//设置动态数据源
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(dataSourceMap);
dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
return dynamicDataSource;
}
-
使用注解
Primary
优先从动态数据源中获取 -
同时,需要在
DynamicDataSourceConfig
中,排除DataSourceAutoConfiguration
的自动配置,否则 会出现The dependencies of some of the beans in the application context form a cycle
的错误。在·DynamicDataSourceConfig
类上添加@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
DynamicDataSourceConfig
最终代码如下:
@Configuration
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
public class DynamicDataSourceConfig {
@Bean(DataSourceConstants.DS_KEY_MASTER)
@ConfigurationProperties(prefix = "springboot.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(DataSourceConstants.DS_KEY_SLAVE)
@ConfigurationProperties(prefix = "springboot.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@Primary
public DataSource dynamicDataSource() {
Map<Object, Object> dataSourceMap = new HashMap<>(2);
dataSourceMap.put(DataSourceConstants.DS_KEY_MASTER, masterDataSource());
dataSourceMap.put(DataSourceConstants.DS_KEY_SLAVE, slaveDataSource());
// 设置动态数据源
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(dataSourceMap);
dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
return dynamicDataSource;
}
}
使用AOP选择数据源
1.自定义一个annotation
DbAnnotation
注解,用于mapper接口上:
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface DbAnnotation {
/**
* 数据源名称,默认master
*/
String value() default DataSourceConstants.DS_KEY_MASTER;
}
2.定义数据源切面
定义数据源切面,用于拦截添加了@DbAnnotation
注解的类或方法:
添加maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
定义切面DynamicDataSourceAspect
类:
@Aspect
@Component
public class DynamicDataSourceAspect {
//拦截DbAnnotation
@Pointcut("@within(com.mj.vscodedemo.annotation.DbAnnotation)")
public void dataSourcePointCut() {
}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
String dsKey = this.getDSAnnotation(joinPoint).value();
DynamicDataSourceContextHolder.setContextKey(dsKey);
try {
return joinPoint.proceed();
} catch (Exception ex) {
throw ex;
} finally {
DynamicDataSourceContextHolder.removeContextKey();
}
}
/**
* 根据类或方法获取数据源注解
*/
private DbAnnotation getDSAnnotation(ProceedingJoinPoint joinPoint) {
//mybatis生成的代理类,所以获取它的接口来获取DbAnnotation注解信息
Class<?> targetClass = joinPoint.getTarget().getClass().getInterfaces()[0];
DbAnnotation dsAnnotation = targetClass.getAnnotation(DbAnnotation.class);
// 先判断类的注解,再判断方法注解
if (Objects.nonNull(dsAnnotation)) {
return dsAnnotation;
} else {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
DbAnnotation annotation = methodSignature.getMethod().getAnnotation(DbAnnotation.class);
return annotation;
}
}
}
在mapper接口上添加@DbAnnotation
注解,分别为master和slave数据源添加了个测试的mapper:
@Mapper
@Repository
@DbAnnotation(DataSourceConstants.DS_KEY_MASTER)
public interface UserMapper {
Integer getUserId();
}
@Mapper
@Repository
@DbAnnotation(DataSourceConstants.DS_KEY_SLAVE)
public interface UserMapper1 {
Integer getUserId();
}
多数据源配置已经完成了,下面就可以进行调用测试访问了
测试
添加一个controller并直接调用mapper的方法
@RestController
public class HelloController {
@Autowired
private UserMapper userMapper;
@Autowired
private UserMapper1 userMapper1;
@ApiOperation(value = "sayHi")
@GetMapping("/sayHi")
public String sayHi() {
int i = userMapper.getUserId();
System.out.println("用户1数量" + i);
int i2 = userMapper1.getUserId();
System.out.println("用户2数量" + i2);
return "你好啊";
}
}
输出如下:
切换数据源master
2020-11-29 22:44:58.933 INFO 13292 --- [nio-8090-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2020-11-29 22:45:00.451 INFO 13292 --- [nio-8090-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
用户1数量1
切换数据源slave
2020-11-29 22:45:00.664 INFO 13292 --- [nio-8090-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Starting...
2020-11-29 22:45:00.728 INFO 13292 --- [nio-8090-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Start completed.
用户2数量2
可以看到分别访问的是master和slave的数据源。
下面就可以进行db读写分离愉快的写代码了。
最后感谢这篇文章的博主,写的很详细,地址https://www.cnblogs.com/masonlee/p/12207853.html