搭建通用的SSM框架 (三) 项目使用多数据源,并且实现数据源的动态切换

源码为分支multi-datasource

一、实现目标: 

          1) 可以灵活的在项目中添加新的数据源

          2) 可以通过注解的方式动态切换数据源

          3) 集成HttpClient实现api的远程调用

二、实现思路:

              单数据源的时候,只要把初始化的数据源注入到对应的事务管理器TransactionManager和SqlSessionFactoryBean

中去即可;

              多数剧源的时候,只需要定义一个自定义的数据源类,把其代替单数据源注入对应的事务管理器TransactionManager

和SqlSessionFactoryBean中去即可;

               自定义的数据源,需要包含项目所有的的数据源信息,而且最好设置一个默认的数据源

三、实现方法:

               根据实现思路,根据现有框架所支持的,自定义的数据源类只需继承AbstractRoutingDataSource类,重写

determineCurrentLookupKey方法即可

四、需要解决的问题:

             1) 如何在项目启动的时候根据配置文件的信息,初始化所有的数据源,并把所有的数据源注入自定义的数据源类

              2) 如何在执行业务方法的时候,自动切换数据源


问题1的解决方案: 利用下面注解实现根据属性文件初始化数据源信息

@EnableConfigurationProperties
@Configuration
@ConfigurationProperties(prefix = "master.datasource.druid")
@PropertySource(value = "classpath:masterdb.properties")

                               利用下面的注解,把数据源信息注入到自定义的数据源中去

@Autowired

1.1) 定义一个基类

package com.roger.core.config;

import com.alibaba.druid.pool.DruidDataSource;
import lombok.Data;

import javax.sql.DataSource;
import java.sql.SQLException;

@Data
public class DBConfig {
    protected String driverClassName;
    protected String url;
    protected String userName;
    protected String password;
    protected int initialSize;
    protected int minIdle;
    protected int maxActive;
    protected long maxWait;
    protected boolean poolPreparedStatements;
    protected String validationQuery;
    protected boolean testOnBorrow;
    protected boolean testOnReturn;
    protected boolean testWhileIdle;
    protected long timeBetweenEvictionRunsMillis;
    protected long minEvictableIdleTimeMillis;
    protected String filters;

    public DataSource initDruidDataSource() throws SQLException {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(driverClassName);
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(userName);
        druidDataSource.setPassword(password);

        druidDataSource.setInitialSize(initialSize);

        druidDataSource.setMinIdle(minIdle);
        druidDataSource.setMaxActive(maxActive);
        druidDataSource.setMaxWait(maxWait);

        druidDataSource.setPoolPreparedStatements(poolPreparedStatements);

        druidDataSource.setValidationQuery(validationQuery);
        druidDataSource.setTestOnBorrow(testOnBorrow);
        druidDataSource.setTestOnReturn(testOnReturn);
        druidDataSource.setTestWhileIdle(testWhileIdle);
        druidDataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);

        druidDataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);

        druidDataSource.setFilters(filters);
        return druidDataSource;
    }
}

1.2) 定义主数据源信息类

package com.roger.core.config.datasource;

import com.roger.core.config.DBConfig;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Data
@Configuration
@ConfigurationProperties(prefix = "master.datasource.druid")
@PropertySource(value = "classpath:masterdb.properties")
public class MasterDBConfig extends DBConfig {

}

1.3) 定义从数据源信息类

package com.roger.core.config.datasource;

import com.roger.core.config.DBConfig;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Data
@Configuration
@ConfigurationProperties(prefix = "slave.datasource.druid")
@PropertySource(value = "classpath:slavedb.properties")
public class SlaveDBConfig extends DBConfig {

}

1.4) 自定义需要注入事务管理器TransactionManager和SqlSessionFactoryBean中的数据源类

package com.roger.core.config;

import com.roger.core.config.datasource.MasterDBConfig;
import com.roger.core.config.datasource.SlaveDBConfig;
import com.roger.core.enumeration.DataSourceType;
import com.roger.core.multiple.DataSourceContextHolder;
import com.roger.core.multiple.MultipleDataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;

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

@Configuration
@Import({MasterDBConfig.class, SlaveDBConfig.class})
@Slf4j
public class DynamicDataSource {


    @Autowired
    private MasterDBConfig masterDBConfig;

    @Autowired
    private SlaveDBConfig slaveDBConfig;

    @Bean(name = "multipleDataSource")
    @Primary
    public DataSource multipleDataSource() throws SQLException {
        MultipleDataSource multipleDataSource = new MultipleDataSource();
        DataSource masterDataSource = masterDBConfig.initDruidDataSource();
        log.info("设置默认数据源{}", masterDataSource);
        multipleDataSource.setDefaultTargetDataSource(masterDataSource);
        log.info("开始配置多数据源");
        Map<Object, Object> targetDataSources = new HashMap<>();
        addTargetDataSources(targetDataSources, DataSourceType.MASTER, masterDataSource);
        log.info("主数据源添加成功...");
        addTargetDataSources(targetDataSources, DataSourceType.SLAVE, slaveDBConfig.initDruidDataSource());
        log.info("从数据源添加成功...");
        multipleDataSource.setTargetDataSources(targetDataSources);
        log.info("配置多数据源完成");
        return multipleDataSource;
    }

    private void addTargetDataSources(Map<Object, Object> targetDataSources, DataSourceType dataSourceType, DataSource dataSource) {
        targetDataSources.put(dataSourceType.name(), dataSource);
        DataSourceContextHolder.dataSourceIds.add(dataSourceType.name());
    }
}
package com.roger.core.multiple;


import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

@Slf4j
public class MultipleDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
      String currentDataSource =  DataSourceContextHolder.getDataSource();
      if(currentDataSource == null){
          log.info("未设置数据源,使用默认的数据源");
      }else{
          log.info("当前使用的数据源为{}",currentDataSource);
      }
      return currentDataSource;
    }
}
package com.roger.core.multiple;

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

public class DataSourceContextHolder {
    /**
     *      当使用TreadLocal维护变量时,ThreadLocal为每个使用这个变量的线程
     * 维护一个变量副本
     *      因此每一个线程都能独立的改变自己的副本,而不会影响其他线程的所对应的副本
     */
    private static final ThreadLocal<String> contextHolder = new InheritableThreadLocal<>();

    /**
     *      管理所有的数据源ID,主要了判断数据源是否存在
     */
    public static List<String> dataSourceIds = new ArrayList<String>();

    /**
     *  设置数据源
     * @param db
     */
    public static void setDataSource(String db){
        contextHolder.set(db);
    }

    /**
     * 取得当前数据源
     * @return
     */
    public static String getDataSource(){
        return contextHolder.get();
    }

    /**
     * 清除上下文数据
     */
    public static void clear(){
        contextHolder.remove();
    }


    public static boolean containsDataSource(String dataSourceId){
        return dataSourceIds.contains(dataSourceId);
    }
}

1.5) 一个总的Spring容器类

package com.roger.core.config;

import com.roger.core.config.datasource.MasterDBConfig;
import com.roger.core.config.datasource.SlaveDBConfig;
import com.roger.core.constant.SystemConstant;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * Spring管理容器
 */
@Configuration
@EnableConfigurationProperties({MasterDBConfig.class,SlaveDBConfig.class})
@ComponentScan(value = SystemConstant.COMPONENT_SCAN_PACKAGE)
@EnableAspectJAutoProxy//启动aop切面功能
public class SpringConfig {
}

问题2的解决方案:  利用aop切面功能和注解实现数据源的动态切换功能


2.1) 定义一个数据源切换时需要的注解类

package com.roger.core.annotation;

import com.roger.core.enumeration.DataSourceType;

import java.lang.annotation.*;

@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {

    DataSourceType value() default DataSourceType.MASTER;
}

2.2) 定义一个数据源对应的枚举类

package com.roger.core.enumeration;

public enum DataSourceType {
    MASTER,SLAVE;
}

2.3) 定义aop切面类

package com.roger.core.aspect;

import com.roger.core.annotation.TargetDataSource;
import com.roger.core.multiple.DataSourceContextHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

@Component
@Aspect
@Order(-10)//保证切面的执行时间在@Transactional之前
public class DynamicDataSourceAspect {

    @Pointcut("execution(* com.roger.biz.service..*.*(..)) || execution(* com.roger.core.service..*.*(..))")
    public void selectDataSource(){

    }

    @After("selectDataSource()")
    public void after(){
        DataSourceContextHolder.clear();
    }

    @Before("selectDataSource()")
    public void before(JoinPoint joinPoint){
        Class<?> targetCls = joinPoint.getTarget().getClass();

        TargetDataSource targetDataSourceAnno = targetCls.getAnnotation(TargetDataSource.class);
        if(targetDataSourceAnno != null){
            DataSourceContextHolder.setDataSource(targetDataSourceAnno.value().name());
            return;
        }
        //获取即将要执行方法
        Method method = getInvokedMethod(targetCls, joinPoint);
        if (method == null) {
            return;
        }
        targetDataSourceAnno = method.getAnnotation(TargetDataSource.class);
        if(targetDataSourceAnno != null){
            DataSourceContextHolder.setDataSource(targetDataSourceAnno.value().name());
        }
    }

    private Method getInvokedMethod(Class targetCls, JoinPoint pJoinPoint) {
        List<Class<? extends Object>> clazzList = new ArrayList<>();
        Object[] args = pJoinPoint.getArgs();
        for (Object arg : args) {
            clazzList.add(arg.getClass());
        }

        Class[] argsCls = (Class[]) clazzList.toArray(new Class[0]);

        String methodName = pJoinPoint.getSignature().getName();
        Method method = null;
        try {
            method = targetCls.getMethod(methodName, argsCls);
        } catch (NoSuchMethodException e) {
            //不做任何处理,这个切面只处理事务相关逻辑
            //其他任何异常不在这个切面的考虑范围
        }
        return method;
    }

}

猜你喜欢

转载自blog.csdn.net/lihongtai/article/details/87275322