SpringBoot服务继承AbstractRoutingDataSource接口解决用户维度动态获取数据源

今天我们从另外一个维度解决多数据源的业务问题,实现读写分离,或者不同业务数据库的操作,核心是依据客户端传的参数,比如请求头类型、用户类型、租户类型等参数需要操作不同的数据库,因此在操作数据库之前需要获取到自己的数据源信息,一般通过切面或者拦截器等方式获取,今天选的技术方案是SpringBoot实现AbstractRoutingDataSource接口解决用户维度动态获取数据源。废话少说,进入正题:

1、核心jar的引入:

主要是springBoot jar相关包:

       
            org.springframework.boot
            spring-boot-starter-aop
        

        
            org.springframework.boot
            spring-boot-starter-web
        

        
        
        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            2.2.2
        

2、多个数据源配置信息:

server.port=8080
spring.datasource.jdbcUrl=jdbc:mysql://127.0.0.1:3306/user-db-1?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

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

3、初始化配置数据源及事务管理

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

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

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

    /**
     * 设置会话工厂
     */
    @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();
    }

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

}

4、继承AbstractRoutingDataSource类实现可选择目标数据源

/**
 * 多数据源获取
 */
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    protected Object determineCurrentLookupKey() {           
        logger.info("当前线程的数据源是 {}",DynamicDataSourceContextHolder.getDataSourceKey());
        //从动态数据源上下文持有者里面获取
        return DynamicDataSourceContextHolder.getDataSourceKey();
    }
}

5、配置多数据源上下文持有者及线程的持有者

//上下文对象
public class DynamicDataSourceContextHolder {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    /**
     * Maintain variable for every thread, to avoid effect other thread
     */
    private static ThreadLocal CONTEXT_HOLDER = ThreadLocal.withInitial(DataSourceKey.dataSource::name);;
    /**
     * All DataSource List
     */
    public static List dataSourceKeys = new ArrayList<>();
    /**
     * Get current DataSource
     * @return data source key
     */
    public static String getDataSourceKey() {
        return CONTEXT_HOLDER.get();
    }
    public static void setDataSourceKey(String key) {
        CONTEXT_HOLDER.set(key);
    }

    public static void clearDataSourceKey() {
        CONTEXT_HOLDER.remove();
    }

  
    public static boolean containDataSourceKey(String key) {
        return dataSourceKeys.contains(key);
    }

    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);
    }

    public static void useMasterDataSource() {
        CONTEXT_HOLDER.set(DataSourceKey.dataSource.dataSourceName);
    }

}

DataSourceKey枚举类

public enum DataSourceKey {
    dataSource("dataSource","dataSource"),
    y2022("key","y2022DataSource");

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

 6、AOP切面实现业务:

@Aspect
@Order(-1)//Order中的数字代表启动优先级,-1是比0还有更高的优先级
@Component
public class DynamicDataSourceAspect {
    private final Logger logger = LoggerFactory.getLogger(getClass());
	//execution用于匹配当前目录下所有的子目录、类、接口
    @Pointcut("execution(* com.nandao.mapper.*.*(..))")
    public void mapperAspect(){ }

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

到此、使用的核心技术点分享完毕,项目实战可以直接使用,可能很多小伙伴知其然、尔不知其所以然,依次下篇我们分享一下其底层原理,敬请期待!

你可能感兴趣的:(spring,boot相关,spring,boot)