在操作数据前,dao层会先调用 AbstractRoutingDataSource 抽象类getConnection(),其中的调用的是determineCurrentLookupKey(),获取到对应的datasource;

借助ThreadLocal保存数据源信息,在整个业务流程中方便获取;

不同连接池实例化事务管理、和生成类似数据库的sessionFactory方法有所不同,可能会费些时间。

springboot版本: 1.5.6.RELEASE

连接池:alibaba druid

  • 配置文件, 密码就暴漏吧,哈哈
  • spring.datasource.druid.test1.url=jdbc:mysql://localhost:3306/multi_test1?characterEncoding=utf8&characterSetResults=utf8&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT

    spring.datasource.druid.test1.username=root

    spring.datasource.druid.test1.password=123456

    spring.datasource.druid.test2.url=jdbc:mysql://localhost:3306/multi_test2?characterEncoding=utf8&characterSetResults=utf8&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT

    spring.datasource.druid.test2.username=root

    spring.datasource.druid.test2.password=123456

    spring.datasource.type = com.alibaba.druid.pool.DruidDataSource

    spring.jpa.show-sql=true

    spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy

  • 实现AbstractRoutingDataSource 抽象类
  • public class DynamicDataSourceRouter extends AbstractRoutingDataSource {

    public static final ThreadLocal<String> dbStore = new ThreadLocal<>();
    @Override
    protected Object determineCurrentLookupKey() {
        String dbBean = dbStore.get();
        if(StringUtils.isBlank(dbBean)){
            dbBean = SourceName.TEST1.dbName;
        } else {
        return dbBean;
    enum SourceName{
        TEST1("test1"),TEST2("test2");
        public String dbName;
        private SourceName(String name){
            this.dbName = name;
    
  • 实例化出多个数据源,加入到DynamicDataSourceRouter父类的setTargetDataSources,并设置默认值
  • @Configuration

    @Slf4j

    public class MultiDataConfig {

    @Value("${spring.datasource.type}")
    private Class<? extends DataSource> dataSourceType;
    @Bean("test1")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.druid.test1")
    public DataSource test1(){
        log.info("构建test1数据源");
        DataSource source = DataSourceBuilder.create().type(dataSourceType).build();
        return source;
    @Bean("test2")
    @ConfigurationProperties(prefix = "spring.datasource.druid.test2")
    public DataSource test2(){
        log.info("构建test2--- 数据源");
        DataSource source = DataSourceBuilder.create().type(dataSourceType).build();
        return source;
    @Bean("routingDataSource")
    public AbstractRoutingDataSource routingDataSource(@Qualifier("test1") DataSource test1,@Qualifier("test2") DataSource test2){
        DynamicDataSourceRouter assistant = new DynamicDataSourceRouter();
        Map<Object, Object> dataSources = new HashMap<>();
        dataSources.put(DynamicDataSourceRouter.SourceName.TEST1.dbName,test1);
        dataSources.put(DynamicDataSourceRouter.SourceName.TEST2.dbName,test2);
        assistant.setDefaultTargetDataSource( test2 );
        assistant.setTargetDataSources(dataSources);
        return assistant;
    
  • 手动对 Jpa 的 EntityManager 进行初始化和配置
  • @Configuration

    @EnableConfigurationProperties(JpaProperties.class)

    @EnableJpaRepositories(value = "cn.rzwl.multidata.dao")

    public class JpaEntityManager {

    @Autowired
    private JpaProperties jpaProperties;
    @Resource(name = "routingDataSource")
    private DataSource routingDataSource;
    @Bean(name = "entityManagerFactoryBean")
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(EntityManagerFactoryBuilder builder) {
        // 不明白为什么这里获取不到 application.yml 里的配置
        Map<String, String> properties = jpaProperties.getProperties();
        //要设置这个属性,实现 CamelCase -> UnderScore 的转换
        properties.put("hibernate.physical_naming_strategy",
                "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
        return builder
                .dataSource(routingDataSource)//关键:注入routingDataSource
                .properties(properties)
                .packages("cn.rzwl.multidata.entity") //TODO 改成自己的实体类包
                .persistenceUnit("myPersistenceUnit")
                .build();
    @Primary
    @Bean(name = "entityManagerFactory")
    public EntityManagerFactory entityManagerFactory(EntityManagerFactoryBuilder builder) {
        return this.entityManagerFactoryBean(builder).getObject();
    @Primary
    @Bean(name = "transactionManager")
    public PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(entityManagerFactory(builder));
    
  • 为方便测试,用filter验证
  • @WebFilter(urlPatterns = "/*")

    @Slf4j

    public class MyFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("初始化自定义过滤器");
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        String site = request.getParameter("source");
        if( DynamicDataSourceRouter.SourceName.TEST1.dbName.equals( site ) ){
            DynamicDataSourceRouter.dbStore.set( DynamicDataSourceRouter.SourceName.TEST1.dbName );
        } else {
            DynamicDataSourceRouter.dbStore.set( DynamicDataSourceRouter.SourceName.TEST2.dbName );
        log.info("进入业务流程");
        chain.doFilter( request,response );
        log.info("完成处理");
        System.out.println("后续操作");
    @Override
    public void destroy() {
    

    源码地址:

  • 一个神奇的AbstractRoutingDataSource类,使数据库切换操作举重若轻,优雅得体,告别劳力式编程。
  •