思路还是继承 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

发表评论