
该主题概念
多数据源动态切换常用语项目中读写分离。多数据源配置参考下上一篇文章: 多数据源配置
动态切换数据源demo
其实和多数据源配置差不多,你在看完多数据源配置后,需要有一个切换数据源的概念,spring中提供了AbstractRoutingDataSource抽象类来实现。下面配置多数据源的类如出一辙,看明白的可以直接在之前的类里操作。
注销默认配置
注销spring boot自动注入数据源
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AccessingChooseDataSourceApplication {
public static void main(String[] args) {
SpringApplication.run(AccessingChooseDataSourceApplication.class, args);
}
}
配置切换所需类
数据源切换该类起切换作用,根据aop注解的方式,与map.key将对应要切换的数据源注入。
package com.example.demo.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Created by 神烦 on 2019/9/6.
*/
public class DataSourceContextHolder {
private static Logger logger = LoggerFactory.getLogger(DataSourceContextHolder.class);
// 默认数据源
public static String dataSourceName = "primarySource";
// 防止数据污染
public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
// 设置数据源名
public static void setDataSourceName(String dbType) {
logger.info("切换 ->{} 数据源", dbType);
threadLocal.set(dbType);
}
// 获取数据源名
public static String getDataSourceName() {
return (threadLocal.get());
}
// 清除数据源名
public static void clearDataSourceName() {
threadLocal.remove();
}
}
AbstractRoutingDataSource是切换数据源的核心类,在访问数据库时 会调用determineCurrentLookupKey方法,通过此方法实现切换数据源。
package com.example.demo.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* Created by 神烦 on 2019/9/6.
* 获取数据源信息
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
private static Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);
// 在访问数据库时 会调用determineCurrentLookupKey方法
@Override
protected Object determineCurrentLookupKey() {
logger.info("多数据源配置获取数据源信息 :{}",DataSourceContextHolder.getDataSourceName());
return DataSourceContextHolder.getDataSourceName();
}
}
配置多数据源
先看代码,下面我讲一下该类的作用
package com.example.demo.config;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* Created by 神烦 on 2019/9/4.
* 因为去掉了数据源自动加载 所有要手动加载数据源
*/
@Configuration
@MapperScan(basePackages = {"com.example.demo.mapper"}, sqlSessionTemplateRef = "dynamicSqlSessionTemplate")
public class DataSourceConfig {
// 主库
@Primary
@Bean(name = "primarySource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource PrimarySource() {
return DataSourceBuilder.create().build();
}
// 从库
@Bean(name = "secondSource")
@ConfigurationProperties(prefix = "spring.datasource.second")
public DataSource SecondSource() {
return DataSourceBuilder.create().build();
}
// 主从数据源注入 通过aop切换
@Bean(name = "dynamicSource")
public DataSource dataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 默认数据源
dynamicDataSource.setDefaultTargetDataSource(SecondSource()); // 默认数据源
// 配置多数据源
Map<Object, Object> map = new HashMap(5);
map.put("primarySource", PrimarySource());
map.put("secondSource", SecondSource());
dynamicDataSource.setTargetDataSources(map); // 将多数据源注入
return dynamicDataSource;
}
// 之前把该类分开 通过Authwired dynamicSource
// 会造成多数据源AbstractRoutingDataSource配置访问不成功的问题 从而造成多数据源切换失败的问题
@Bean
public SqlSessionFactoryBean dynamicSqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
return factoryBean;
}
// SqlSeesionTemplate是关键一步 将多数据源信息注入
@Bean
public SqlSessionTemplate dynamicSqlSessionTemplate() throws Exception {
SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(dynamicSqlSessionFactory().getObject());
return sqlSessionTemplate;
}
}
这里的配置需要讲一下,DynamicDataSource是DataSource下实现动态切换的类,既然是动态切换,那么就是多数据源,这里我们需要把需要动态切换的类注入,并设置一下默认的数据源。重点来了,之前在整合的时候,为了美观我将动态数据类DynamicDataSource的其他配置放在了另一个类,然后通过authwired注入该DynamicDataSource,结果就是死活都切换不了,后来东改西改,最后才明白问题出现在注入方式上。望众知。
aop切换
添加注释,方便切换数据源
package com.example.demo.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by 神烦 on 2019/9/6.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DataSourceChoose {
String value() default "primarySource";
}
切面实现类
package com.example.demo.aspect;
import com.example.demo.annotation.DataSourceChoose;
import com.example.demo.config.DataSourceContextHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* Created by 神烦 on 2019/9/6.
*/
@Aspect
@Component
public class DynamicDataSourceAspect {
private static Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
@Pointcut("execution(public * com.example.demo..*(..)) && @annotation(com.example.demo.annotation.DataSourceChoose))")
public void pointCutUtil(){
}
// 获取注释 更换数据源
@Before("pointCutUtil()")
public void updateDataSource(JoinPoint joinPoint){
logger.info("进入切面类......");
// 获取当前访问的类
Class<?> className = joinPoint.getTarget().getClass();
// 获取访问方法的方法名
String methodName = joinPoint.getSignature().getName();
// 获取方法参数的类型
Class[] argClass = ((MethodSignature)joinPoint.getSignature()).getParameterTypes();
String dataSource = DataSourceContextHolder.dataSourceName;
try{
// 获取访问的对象
java.lang.reflect.Method method = className.getMethod(methodName, argClass);
// 判断切面方法是否有注释
if (method.isAnnotationPresent(DataSourceChoose.class)){
DataSourceChoose annotation = method.getAnnotation(DataSourceChoose.class);
// 注解参数
dataSource = annotation.value();
}
}catch (NoSuchMethodException e){
e.getMessage();
}
// 切换数据源
DataSourceContextHolder.setDataSourceName(dataSource);
logger.info("切换数据源成功...... {}", dataSource);
}
@After("pointCutUtil()")
public void clearDataSource(JoinPoint joinPoint){
DataSourceContextHolder.clearDataSourceName();
}
}
dao
再加个数据访问层就ok了,和多数据源相比的话就不需要写多个mapper,少写了不少代码了,代价就是配置略微麻烦。
package com.example.demo.mapper;
import com.example.demo.model.Test;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
/**
* Created by 神烦 on 2019/9/6.
*/
@Mapper
public interface TestMapper {
@Select("select * from t_test")
Test PrimaryTest();
}
测试
创建个controller测试一下来
package com.example.demo.controller;
import com.example.demo.annotation.DataSourceChoose;
import com.example.demo.mapper.TestMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* Created by 神烦 on 2019/9/6.
*/
@RestController
public class MoreDataSourceController {
private static Logger logger = LoggerFactory.getLogger(MoreDataSourceController.class);
@Resource
public TestMapper testMapper;
@RequestMapping("/primaryTest.json")
@DataSourceChoose(value = "primarySource")
public void primaryTest() {
logger.info("primary测试接口...... {}", testMapper.PrimaryTest().toString());
}
@RequestMapping("/secondTest.json")
@DataSourceChoose(value = "secondSource")
public void secondTest() {
logger.info("second数据源测试接口...... {}", testMapper.PrimaryTest().toString());
}
}
测试结果:
2019-09-07 15:41:31.415 INFO 5456 --- [ main] e.d.AccessingChooseDataSourceApplication : Starting AccessingChooseDataSourceApplication on USER-20180705XA with PID 5456 (D:\accessingChooseDataSource\target\classes started by 神烦 in D:\accessingChooseDataSource)
2019-09-07 15:41:31.417 INFO 5456 --- [ main] e.d.AccessingChooseDataSourceApplication : No active profile set, falling back to default profiles: default
2019-09-07 15:41:33.735 INFO 5456 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2019-09-07 15:41:33.762 INFO 5456 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2019-09-07 15:41:33.763 INFO 5456 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.24]
2019-09-07 15:41:33.876 INFO 5456 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2019-09-07 15:41:33.876 INFO 5456 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 2362 ms
2019-09-07 15:41:34.626 INFO 5456 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2019-09-07 15:41:35.069 INFO 5456 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2019-09-07 15:41:35.072 INFO 5456 --- [ main] e.d.AccessingChooseDataSourceApplication : Started AccessingChooseDataSourceApplication in 4.364 seconds (JVM running for 4.939)
2019-09-07 15:41:45.570 INFO 5456 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-09-07 15:41:45.571 INFO 5456 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2019-09-07 15:41:45.587 INFO 5456 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 16 ms
2019-09-07 15:41:45.626 INFO 5456 --- [nio-8080-exec-1] c.e.demo.aspect.DynamicDataSourceAspect : 进入切面类......
2019-09-07 15:41:45.629 INFO 5456 --- [nio-8080-exec-1] c.e.demo.config.DataSourceContextHolder : 切换 ->primarySource 数据源
2019-09-07 15:41:45.632 INFO 5456 --- [nio-8080-exec-1] c.e.demo.aspect.DynamicDataSourceAspect : 切换数据源成功...... primarySource
2019-09-07 15:41:45.658 INFO 5456 --- [nio-8080-exec-1] c.example.demo.config.DynamicDataSource : 多数据源配置获取数据源信息 :primarySource
2019-09-07 15:41:45.659 INFO 5456 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2019-09-07 15:41:45.955 INFO 5456 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2019-09-07 15:41:46.016 INFO 5456 --- [nio-8080-exec-1] c.e.d.c.MoreDataSourceController : primary测试接口...... Test(id=99999, name=主库, remark=主库数据库)
2019-09-07 15:41:53.804 INFO 5456 --- [nio-8080-exec-4] c.e.demo.aspect.DynamicDataSourceAspect : 进入切面类......
2019-09-07 15:41:53.804 INFO 5456 --- [nio-8080-exec-4] c.e.demo.config.DataSourceContextHolder : 切换 ->secondSource 数据源
2019-09-07 15:41:53.805 INFO 5456 --- [nio-8080-exec-4] c.e.demo.aspect.DynamicDataSourceAspect : 切换数据源成功...... secondSource
2019-09-07 15:41:53.805 INFO 5456 --- [nio-8080-exec-4] c.example.demo.config.DynamicDataSource : 多数据源配置获取数据源信息 :secondSource
2019-09-07 15:41:53.805 INFO 5456 --- [nio-8080-exec-4] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Starting...
2019-09-07 15:41:53.923 INFO 5456 --- [nio-8080-exec-4] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Start completed.
2019-09-07 15:41:53.935 INFO 5456 --- [nio-8080-exec-4] c.e.d.c.MoreDataSourceController : second数据源测试接口...... Test(id=99999, name=从库, remark=从库数据库)
总结
dynamicDataSource多数据源需要注入该类,spring实现多数据源切换得类AbstractRoutingDataSource,在访问数据源时会请求该类下的determineCurrentLookupKey方法,注解要注意别写错了。基本就没啥问题了。
源码地址: github.com/Liyinzuo/ac…
如果有什么不足或者错误的地方欢迎指正~