如何实现动态数据源?
比如,现存有两个数据源:master、salve,想实现某些方法上面使用master数据源,某些方法使用salve数据源。
数据源定义
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
ds:
# 主库数据源
master:
url: jdbc:mysql://localhost:3306/test01?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: root
# 从库数据源
slave:
url: jdbc:mysql://localhost:3306/test02?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: root
通过@ConfigurationProperties
来映射获得配置中的数据源信息
//省去get/set方法
@ConfigurationProperties(prefix = "spring.datasource")
public class DruidProperties {
private String type;
private String driverClassName;
private Map<String,Map<String,String>> ds;
private Integer initialSize;
private Integer minIdle;
private Integer maxActive;
private Integer maxWait;
//此处的方法是为了在加载某个数据源的时候,传入公共的数据源的参数
public DataSource buildDataSource(DruidDataSource druidDataSource){
druidDataSource.setInitialSize(initialSize);
druidDataSource.setMinIdle(minIdle);
druidDataSource.setMaxWait(maxWait);
druidDataSource.setMaxActive(maxActive);
return druidDataSource;
}
}
将映射完成的数据源信息加载成真正的数据源类型
@Component
@EnableConfigurationProperties(DruidProperties.class)
public class LoadDataSource {
@Autowired
DruidProperties druidProperties;
/**
* 加载配置文件中的数据源到map中
* @return
*/
public Map<String, DataSource> loadAllDataSource() {
HashMap<String,DataSource> map = new HashMap<>();
//拿到配置文件中的ds配置
Map<String, Map<String, String>> ds = druidProperties.getDs();
try {
Set<String> keySet = ds.keySet();
for (String key : keySet) {
map.put(key, druidProperties.buildDataSource((DruidDataSource) DruidDataSourceFactory.createDataSource(ds.get(key))));
}
}catch (Exception e){
e.printStackTrace();
}
return map;
}
}
定义数据源的注解
数据源类型控制
public interface DataSourceType {
String DEFAULT_DATASOURCE_NAME = "master";
}
数据源注解
/**
* 这个注解放在一个service类上或者方法上面,通过value 属性来指定类或者方法该使用哪个数据源
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface DataSource {
String value() default DataSourceType.DEFAULT_DATASOURCE_NAME;
}
数据源存储
使用
ThreadLocal
来存储当前的数据源名称
/**
* 用来存放当前线程所使用的数据源的名称
*/
public class DynamicDataSourceContextHolder {
private static ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
public static void setDataSourceType(String dsType){
CONTEXT_HOLDER.set(dsType);
}
public static String getDataSourceType(){
return CONTEXT_HOLDER.get();
}
public static void clearDataSourceType(){
CONTEXT_HOLDER.remove();
}
}
切面解析注解
@Component
@Aspect
public class DataSourceAspect {
/**
* @annotation(com.origin.dynamicdatasource.annotation.DataSource) 表示方法上有@DataSurce注解的会被拦截下来
* @within(com.origin.dynamicdatasource.annotation.DataSource) 表示如果类上有@DataSource注解的,类中的方法就会被拦截下来
*/
@Pointcut("@annotation(com.origin.dynamicdatasource.annotation.DataSource) || @within(com.origin.dynamicdatasource.annotation.DataSource)")
public void pc(){ }
@Around("pc()")
public Object around(ProceedingJoinPoint pjp){
//获取方法上面的有效注解
DataSource dataSource = getDataSource(pjp);
if(dataSource != null){
//获取注解中的数据源名称
String value = dataSource.value();
DynamicDataSourceContextHolder.setDataSourceType(value);
}
try {
return pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}finally {
DynamicDataSourceContextHolder.clearDataSourceType();
}
return null;
}
/**
* 获取注解信息
* @param pjp
* @return
*/
private DataSource getDataSource(ProceedingJoinPoint pjp) {
MethodSignature signature = (MethodSignature) pjp.getSignature();
//查找方法上的注解
DataSource annotation = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
if(annotation != null){
//说明方法上有@DataSource注解
return annotation;
}
//否则获取类上的@DataSource注解
return AnnotationUtils.findAnnotation(signature.getDeclaringType(),DataSource.class);
}
}
设置动态数据
此环节是实现动态数据源的关键,需要重写AbstractRoutingDataSource
中的determineCurrentLookupKey()
方法,该方法用来返回数据源的名称,当系统需要数据源的时候,会自动调用该方法获取数据源的名称,这也是动态切换数据源的关键点,当调用该方法时,我们可以动态改变里面的数据源,然后再给系统。
@Component
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(LoadDataSource loadDataSource) {
//1.设置所有的数据源
Map<String, DataSource> allDs = loadDataSource.loadAllDataSource();
super.setTargetDataSources(new HashMap<>(allDs));
//2.设置默认的数据源,因为并不是所有的方法上都有@DataSource注解,对于没有该注解的方法,就默认使用这里的默认数据源
super.setDefaultTargetDataSource(allDs.get(DataSourceType.DEFAULT_DATASOURCE_NAME));
//3.整理上面设置的两个数据源参数
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
测试
Mapper
@Mapper
public interface UserMapper {
@Select("select * from user;")
List<User> getAllUser();
}
Service
@Service
public class UserService {
@Autowired
UserMapper userMapper;
@DataSource(value = "slave") //配置该方法使用slave数据源
public List<User> getAllUser(){
return userMapper.getAllUser();
}
}
Model
public class User {
private Integer id;
private String username;
private Integer age;
}
Controller
@RestController
public class DataSourceController {
@Autowired
UserService userService;
@GetMapping("/getalluser")
public List<User> getallUser(){
return userService.getAllUser();
}
}
数据库
结果
切换成
master
@Service
public class UserService {
@Autowired
UserMapper userMapper;
@DataSource(value = "master")//当然也可以不用设置,因为在DynamicDataSource中设置了默认的数据源master
public List<User> getAllUser(){
return userMapper.getAllUser();
}
}
结果