基于Spring的动态路由AbstractRoutingDataSource实现动态分库

1.定义数据库配置文件

server.port=8081

spring.datasource.jdbcUrl=jdbc:mysql://******:3306/spring-db-2019?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=*****
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

y2018.spring.datasource.jdbcUrl=jdbc:mysql://******:3306/spring-db-2018?useUnicode=true&characterEncoding=utf8
y2018.spring.datasource.username=root
y2018.spring.datasource.password=*****
y2018.spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

logging.level.com.ijianghu.dynamic.web=debug

2.配置多数据源、sessionFactory和事务管理器

package com.ijianghu.dynamic.web.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
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 org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.util.HashMap;

@Configuration
public class BaseDataSourceConfig {

    /**
     * 配置默认数据源,并设置高优先级
     * @return
     */
    @Primary
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource(){
        return DataSourceBuilder.create().build();
    }

    /**
     * 配置多数据源
     * @return
     */
    @Bean("y2018DataSource")
    @ConfigurationProperties(prefix = "y2018.spring.datasource")
    public DataSource y2018DataSource(){
        return DataSourceBuilder.create().build();
    }

    /**
     * 设置多数据源,配置动态路由数据源
     * @return
     */
    @Bean("dynamincDataSource")
    public DataSource dynamincDataSource(){
        DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
        HashMap<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put("dataSource",dataSource());
        dataSourceMap.put("y2018DataSource",y2018DataSource());
        //配置默认目标数据源
        dynamicRoutingDataSource.setDefaultTargetDataSource(dataSource());
        //设置目标数据源集合,供路由选择
        dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
        return dynamicRoutingDataSource;
    }

    /**
     * 设置会话工厂
     * @return
     * @throws Exception
     */
    @Bean(name="sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        //配置数据源为多数据源
        factoryBean.setDataSource(dynamincDataSource());
        //设置mybaits的xml文件路径
        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/*.xml"));
        return factoryBean.getObject();
    }

    /**
     * 配置事务管理器
     * @return
     */
    @Bean(name="transactionManager")
    public PlatformTransactionManager transactionManager(){
        return new DataSourceTransactionManager(dynamincDataSource());
    }

}



3.继承AbstractRoutingDataSource类,实现选择目标数据源

package com.ijianghu.dynamic.web.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * Multiple DataSource Configurer
 */
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    protected Object determineCurrentLookupKey() {
            
        logger.info("Current DataSource is {}",DynamicDataSourceContextHolder.getDataSourceKey());
        //从动态数据源上下文持有者里面获取
        return DynamicDataSourceContextHolder.getDataSourceKey();
    }
}

4.配置多数据源上下文持有者

package com.ijianghu.dynamic.web.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;

/**
 * Multiple DataSource Context Holder
 */
public class DynamicDataSourceContextHolder {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * Maintain variable for every thread, to avoid effect other thread
     */
    private static ThreadLocal<String> CONTEXT_HOLDER = ThreadLocal.withInitial(DataSourceKey.dataSource::name);;


    /**
     * All DataSource List
     */
    public static List<Object> dataSourceKeys = new ArrayList<>();

    /**
     * Get current DataSource
     *
     * @return data source key
     */
    public static String getDataSourceKey() {
        return CONTEXT_HOLDER.get();
    }

    /**
     * To switch DataSource
     *
     * @param key the key
     */
    public static void setDataSourceKey(String key) {
        CONTEXT_HOLDER.set(key);
    }

    /**
     * To set DataSource as default
     */
    public static void clearDataSourceKey() {
        CONTEXT_HOLDER.remove();
    }

    /**
     * Check if give DataSource is in current DataSource list
     *
     * @param key the key
     * @return boolean boolean
     */
    public static boolean containDataSourceKey(String key) {
        return dataSourceKeys.contains(key);
    }

    /**
     * Use slave data source.
     */
    public static void useSlaveDataSource(String ds) {
        DataSourceKey dataSourceKey = DataSourceKey.valueOf(ds);
        //if there is no suitable enum,then use default dataSource
        if(dataSourceKey == null){
            setDataSourceKey(DataSourceKey.dataSource.dataSourceName);
        }
        setDataSourceKey(dataSourceKey.dataSourceName);
    }

    /**
     * Use master data source.
     */
    public static void useMasterDataSource() {
        CONTEXT_HOLDER.set(DataSourceKey.dataSource.dataSourceName);
    }

}

5.根据数据源名称,配置多数据源枚举类,根据参数获取对应的数据源名称

package com.ijianghu.dynamic.web.config;

public enum DataSourceKey {
    dataSource("dataSource","dataSource"),
    y2018("key","y2018DataSource");

    public String key;
    public String dataSourceName;
    DataSourceKey(String key, String dataSourceName){
        this.key = key;
        this.dataSourceName = dataSourceName;
    }
}

6.配置AOP切面,根据参数设置对应的数据源名称

package com.ijianghu.dynamic.web.aop;


import com.ijianghu.dynamic.web.config.DynamicDataSourceContextHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

@Aspect
@Order(-1)
@Component
public class DynamicDataSourceAspect {

    private final Logger logger = LoggerFactory.getLogger(getClass());


    @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)||" +
            "@annotation(org.springframework.web.bind.annotation.GetMapping)||" +
            "@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void daoAspect(){ }


    @Before("daoAspect()")
    public void switchDataSource(JoinPoint point){
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String ds = request.getHeader("ds");
        if(ds != null){
            DynamicDataSourceContextHolder.useSlaveDataSource(ds);
        }else{
            DynamicDataSourceContextHolder.useMasterDataSource();
        }

    }

}

7.关键点

1、数据源是如何切换的

根据参数获取到对应的数据源名称,然后动态数据源路由根据获取到的name,从数据源集合里面获取对应的dataSource

posted @ 2020-05-27 19:08  南宫煌_慧  阅读(446)  评论(0编辑  收藏  举报