思路还是继承 org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
通过重写父类方法:protected Object determineCurrentLookupKey() 来实现数据源的动态切换。通过注解在方法上实现方法内数据源的动态切换,方法结束后恢复默认数据源。
这个demo我用的springboot 2.1.7
demo项目下载: https://gitee.com/zhuhongliang/springboot_multi_datasource_demo.git
首先一定要在启动类加上exclude = DataSourceAutoConfiguration.class
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@MapperScan(basePackages = "com.zhl.multi_data_source_demo.dao")
public class MultiDataSourceDemoApplication implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(MultiDataSourceDemoApplication.class, args);
}
@Autowired
private ApplicationContext applicationContext;
@Override
public void run(ApplicationArguments args){
//系统启动会自动运行这段代码,用以测试
Service service = (Service) applicationContext.getBean("service");
service.contextLoads2();
service.contextLoads21();
}
}
否则会报错:The dependencies of some of the beans in the application context form a cycle:
DataSource.java:
import java.lang.annotation.*; @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataSource { String value() default DataSource.master; //主从数据源Bean的名字不能瞎写,否则数据源切换不成功 String master = "masterDataSource"; String slave = "slaveDataSource"; }
DataSourceAspect.java:
import org.aspectj.lang.annotation.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.aspectj.lang.JoinPoint; import java.lang.reflect.Method; @Aspect @Order(1) @Component public class DataSourceAspect { private static Logger logger = LoggerFactory.getLogger(DataSourceAspect.class); /** * @within匹配类上的注解 * @annotation匹配方法上的注解 */ @Pointcut(value = "@within(com.zhl.multi_data_source_demo.DataSource)||@annotation(com.zhl.multi_data_source_demo.DataSource)") public void pointcut(){} @Before(value = "pointcut()") public void beforeOpt(JoinPoint joinPoint) { Object target = joinPoint.getTarget(); Class<?> clazz = target.getClass(); Method[] methods = clazz.getMethods(); DataSource annotation = null; for (Method method : methods) { if (joinPoint.getSignature().getName().equals(method.getName())) { annotation = method.getAnnotation(DataSource.class); if (annotation == null) { annotation = joinPoint.getTarget().getClass().getAnnotation(DataSource.class); if (annotation == null) { return; } } } } String dataSourceName = annotation.value(); DataSourceHolder.setDataSource(dataSourceName); logger.info("切到" + dataSourceName + "数据库"); } @After(value="pointcut()") public void afterOpt(){ DataSourceHolder.clearDataSource(); logger.info("切回默认数据库"); } }
DataSourceConfig.java:
import org.springframework.beans.factory.annotation.Qualifier; 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; @Configuration public class DataSourceConfig { @Bean(name="masterDataSource")//主数据源Bean @ConfigurationProperties(prefix = "spring.datasource.master") public DataSource masterDataSource(){ return DataSourceBuilder.create().build(); } @Bean(name="slaveDataSource")//从数据源Bean @ConfigurationProperties(prefix = "spring.datasource.slave") public DataSource slaveDataSource(){ return DataSourceBuilder.create().build(); } @Primary//这个注解千万不能少 @Bean(name = "dynamicDataSource") public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("slaveDataSource") DataSource slaveDataSource) { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put("masterDataSource", masterDataSource);//key千万不能写错 targetDataSources.put("slaveDataSource", slaveDataSource);//key千万不能写错 return new DynamicDataSource(masterDataSource, targetDataSources); } /*这个bean可以不写,网上有很多demo都写了这个 @Bean("sqlSessionFactoryBean") public SqlSessionFactoryBean sqlSessionFactoryBean(@Qualifier("dynamicDataSource") DynamicDataSource dynamicDataSource) throws IOException { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dynamicDataSource); sqlSessionFactoryBean.setTypeAliasesPackage("com.zhl.multi_data_source_demo.dao"); return sqlSessionFactoryBean; }*/ }
DataSourceHolder.java:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 封装的对数据源进行操作的类: */ public class DataSourceHolder { private static Logger logger = LoggerFactory.getLogger(DataSourceAspect.class); //线程 本地环境 private static final ThreadLocal<String> dataSources = new InheritableThreadLocal(); //设置数据源 public static void setDataSource(String datasource) { logger.info("设置数据源={}", datasource); dataSources.set(datasource); } //获取数据源 public static String getDataSource() { return dataSources.get(); } //清除数据源 public static void clearDataSource() { dataSources.remove(); } }
DynamicDataSource.java:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import java.util.Map; public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceHolder.getDataSource(); } public DynamicDataSource(javax.sql.DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) { super.setDefaultTargetDataSource(defaultTargetDataSource); super.setTargetDataSources(targetDataSources); super.afterPropertiesSet(); } }
实际方法调用的时候只需要加上注解就行了。
@DataSource(DataSource.slave) public void contextLoads2() { System.out.println(JSONArray.toJSONString(userinfoDAO.findAll2())); }
demo项目下载: https://gitee.com/zhuhongliang/springboot_multi_datasource_demo.git