上篇文章讲述了一种简单粗暴的多数据源配置方式,这篇来讲一下动态切换数据源的方式配置。
动态切换数据源,其核心在于一个AbstractRoutingDataSource类,通过继承此类并重写determineCurrentLookupKey方法可以实现动态切换数据源,具体切换方式可点进去看源码的determineTargetDataSource方法,比较简单,这里只记录实现。
数据库准备
参考上篇文章:http://www.scarlettbai.com/index.php/archives/112.html
动态数据源配置
public class MyDynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.get();
}
}
这个方法的返回值,就是一个数据源的key,我们对于多个数据源,会对每个数据源定义一个key,之后以map形式保存在AbstractRoutingDataSource的targetDataSources属性中。
接下来看一下上文中出现的DynamicDataSourceContextHolder类:
public class DynamicDataSourceContextHolder {
private static ThreadLocal<String> DBNAME = new ThreadLocal<>();
public static String get() {
return DBNAME.get();
}
public static void set(String dbName) {
DBNAME.set(dbName);
}
public static void clear() {
DBNAME.remove();
}
}
DynamicDataSourceContextHolder类的作用很简单,就是根据线程来存取数据库的key。
DataSource配置
@Configuration
public class DataSourceConfig {
@Autowired
private Environment env;
@Bean
public DataSource backDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(env.getProperty("spring.datasource.back.jdbcUrl"));
dataSource.setUsername(env.getProperty("spring.datasource.back.username"));
dataSource.setPassword(env.getProperty("spring.datasource.back.password"));
return dataSource;
}
@Bean
public DataSource frontDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(env.getProperty("spring.datasource.front.jdbcUrl"));
dataSource.setUsername(env.getProperty("spring.datasource.front.username"));
dataSource.setPassword(env.getProperty("spring.datasource.front.password"));
return dataSource;
}
@Bean
@Primary
public DataSource dynamicDataSource() {
MyDynamicDataSource dataSource = new MyDynamicDataSource();
dataSource.setDefaultTargetDataSource(frontDataSource());
Map<Object, Object> allDataSource = new HashMap<>();
allDataSource.put("backDataSource", backDataSource());
allDataSource.put("frontDataSource", frontDataSource());
dataSource.setTargetDataSources(allDataSource);
return dataSource;
}
}
这里datasource配置引入了阿里的druid数据源,各位不用的话可以切换为自己的数据源即可,主要注意一下dynamicDataSource这个数据源,这里需要将他设置为主数据源,即@Primary。之后调用数据源都会走determineTargetDataSource进行数据源路由切换。
在调用dao前进行数据源切换
这里通过AOP方式实现,看各人系统需求,可以直接通过aop扫描对应的分包目录,也可以自定义一个注解来自己在方法上添加注解。
//定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DynamicDataSource {
/**
* dbname
* @return
*/
String value();
}
//AOP实现
@Aspect
@Component
public class DynamicDataSourceAOP {
@Before("@annotation(org.white.mutidatasource2.aop.DynamicDataSource)")
public void changeDataSource(JoinPoint point) {
Class<?> clazz = point.getTarget().getClass();
MethodSignature signature = (MethodSignature) point.getSignature();
try {
Method method = clazz.getDeclaredMethod(signature.getName(), signature.getParameterTypes());
if (method.isAnnotationPresent(DynamicDataSource.class)) {
DynamicDataSource dynamicDataSource = method.getAnnotation(DynamicDataSource.class);
String dbName = dynamicDataSource.value();
if (DynamicDataSourceContextAware.names.contains(dbName)) {
DynamicDataSourceContextHolder.set(dbName);
}
}
} catch (Exception e) {
System.out.println("error" + e.getMessage());
}
}
@After("@annotation(org.white.mutidatasource2.aop.DynamicDataSource)")
public void after() {
DynamicDataSourceContextHolder.clear();
}
}
这里的aop实现也很简单,就是通过读取注解内容来切换DynamicDataSourceContextHolder中的值。
其中的DynamicDataSourceContextAware类代码如下,只是记录一个names的集合,可以看需求省略:
@Component
public class DynamicDataSourceContextAware implements ApplicationContextAware {
public static Set<String> names = new HashSet<>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, DataSource> dataSources = applicationContext.getBeansOfType(DataSource.class);
for (String dataSourceName : dataSources.keySet()) {
if (!"dynamicDataSource".equals(dataSourceName)) {
names.add(dataSourceName);
}
}
}
}
最后,来看一下调用方式:
@Service
public class BizServiceImpl implements BizService {
@Autowired
private AdminMapper adminMapper;
@Autowired
private UserMapper userMapper;
//无注解,使用默认datasource
@Override
public int addUser(UserDTO userDTO) {
return userMapper.insert(userDTO);
}
//使用backDatasource
@Override
@DynamicDataSource(value = "backDataSource")
public int addAdmin(AdminDTO adminDTO) {
return adminMapper.insert(adminDTO);
}
}
实现过程就在这里了,其核心其实就是一个AbstractRoutingDataSource类,其他都是一些spring的基础知识。
这种模式优点在于扩展方便,新增数据源只需新增一个datasource的配置即可。
另外这里附上github项目地址:https://github.com/whiteBX/mutidatasource2
可以直接下载运行,包括初始化sql都在项目内。