Spring 配置多数据源并实现Druid自动切换

一、多数据源切换
   实现数据库的读写分离,这种情况往往是读多写少的情况,例如电商平台。既然数据库读写分离了,那么代码层也就需要读写不同的数据库了。实现方法应该有不少,我知道有插件实现,判断写请求还是读请求来请求不同的数据库,还有代码实现,不同的SQL访问不同的数据源,也就是接下来要说的多数据源。
  1、 类或者方法上只需要添加注解,即可实现多数据源切换,具体逻辑实现已经封装在数据访问层,业务逻辑处理层等多个组件进行使用
  2、类或者方法上添加如下注解:@DataSource(ContextConstant.DataSourceType.CTS) 或者@DataSource(ContextConstant.DataSourceType.REPORT) ,分别表示切换到XXX数据库和XXS数据库
   3、若需要再增加其他数据源,只需要在配置文件applicationContext.xml中增加数据配置信息,在常量接口ContextConstant中增量对应数据源类型即可。代码侵入性小,方便切换
二、代码配置信息
  1、applicationContext.xml配置


    <!-- report 数据源(使用连接池) -->
    <bean id = "reportDataSource" class = "com.alibaba.druid.pool.DruidDataSource" destroy-method = "close" >
        <!-- 数据库基本信息配置 -->
        <property name = "url" value = "${report.jdbc.url}" />
        <property name = "username" value = "${report.jdbc.username}" />
        <property name = "password" value = "${report.jdbc.password}" />
        <property name = "driverClassName" value = "${report.jdbc.driverClassName}" />
        <property name = "filters" value = "config,wall" />
        <!-- 最大并发连接数 -->
        <property name = "maxActive" value = "${report.jdbc.maxActive}" />
        <!-- 初始化连接数量 -->
        <property name = "initialSize" value = "${report.jdbc.initialSize}" />
        <!-- 配置获取连接等待超时的时间 -->
        <property name = "maxWait" value = "${report.jdbc.maxWait}" />
        <!-- 最小空闲连接数 -->
        <property name = "minIdle" value = "${report.jdbc.minIdle}" />
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name = "timeBetweenEvictionRunsMillis" value ="3600000" />
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name = "minEvictableIdleTimeMillis" value ="3600000" />
        <property name = "validationQuery" value = "select 1 from dual" />
        <property name = "testWhileIdle" value = "true" />
        <property name = "testOnBorrow" value = "false" />
        <property name = "testOnReturn" value = "false" />
       <!--
        <property name="connectionProperties" value="config.decrypt=true;config.decrypt.key=${report.jdbc.publicKey}" />
       -->
    </bean>

    <!-- cts 数据源(使用连接池) -->
    <bean id = "ctsDataSource" class = "com.alibaba.druid.pool.DruidDataSource" destroy-method = "close" >
        <!-- 数据库基本信息配置 -->
        <property name = "url" value = "${cts.jdbc.url}" />
        <property name = "username" value = "${cts.jdbc.username}" />
        <property name = "password" value = "${cts.jdbc.password}" />
        <property name = "driverClassName" value = "${cts.jdbc.driverClassName}" />
        <property name = "filters" value = "config,wall" />
        <!-- 最大并发连接数 -->
        <property name = "maxActive" value = "${cts.jdbc.maxActive}" />
        <!-- 初始化连接数量 -->
        <property name = "initialSize" value = "${cts.jdbc.initialSize}" />
        <!-- 配置获取连接等待超时的时间 -->
        <property name = "maxWait" value = "${cts.jdbc.maxWait}" />
        <!-- 最小空闲连接数 -->
        <property name = "minIdle" value = "${cts.jdbc.minIdle}" />
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name = "timeBetweenEvictionRunsMillis" value ="3600000" />
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name = "minEvictableIdleTimeMillis" value ="3600000" />
        <property name = "validationQuery" value = "select 1 from dual" />
        <property name = "testWhileIdle" value = "true" />
        <property name = "testOnBorrow" value = "false" />
        <property name = "testOnReturn" value = "false" />
        <property name="connectionProperties" value="config.decrypt=true;config.decrypt.key=${cts.jdbc.publicKey}" />
    </bean>


    <!-- 多数据源配置 -->
    <bean id="multipleDataSource" class="com.fqfin.credit.dbconfig.MultipleDataSource">
        <!-- 默认数据源 -->
        <property name="defaultTargetDataSource" ref="reportDataSource" />
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry key="REPORT" value-ref="reportDataSource"/>
                <entry key="CTS" value-ref="ctsDataSource"/>
            </map>
        </property>
    </bean>


    <!-- (事务管理)transaction manager, use JtaTransactionManager for global tx -->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="multipleDataSource" />
      <!--  <tx:annotation-driven transaction-manager="transactionManager"/>-->
    </bean>


    <!-- define the SqlSessionFactory, notice that configLocation is not needed
        when you use MapperFactoryBean -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
       <!-- <property name="dataSource" ref="dataSource" />-->
        <!-- 数据库连接池 -->
        <property name="dataSource" ref="multipleDataSource"/>
        <property name="configLocation" value="classpath:spring/mybatis-config.xml"/>
        <property name="typeAliasesPackage" value="com.fqfin.**.dto" />
        <!--&lt;!&ndash; 自动扫描Mapper.xml文件 &ndash;&gt;-->
        <property name="mapperLocations" value="classpath:mapper/**/*Mapper.xml"/>
    </bean>

  2、数据源实例

public interface ContextConstant {
      /**
    *@Description: 数据源枚举
    *@Param:  * @Param: null
    */
    enum DataSourceType {
        REPORT, CTS
    }
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    /**
    *@Description: 默认为上报数据库数据源
    */
    ContextConstant.DataSourceType value() default ContextConstant.DataSourceType.REPORT;
}

  2、切点表达式,biz数据访问层,business包下可以自动识别(须符合此种路径,方可生效,也可以自定义修改)

/**
 * @ClassName: DynamicDataSourceAspect
 * @Description: 切换数据源类
 */
@Component
@Aspect
@Order(1)   /**设置为1,优先加载,优先级高于AbstractRoutingDataSource的determineCurrentLookupKey()
              */
public class DynamicDataSourceAspect  {

    private static final Logger log = LoggerFactory.getLogger(DynamicDataSourceAspect.class);

    /**
    *@Description: 前置切面
    *@Param:  * @Param: null
 
    */
    @Before("execution(* com.fqfin.credit.business..*.*(..))")
    public void before(JoinPoint point) {
        try {
            DataSource annotationOfClass = point.getTarget().getClass().getAnnotation(DataSource.class);
            String methodName = point.getSignature().getName();
            Class[] parameterTypes = ((MethodSignature) point.getSignature()).getParameterTypes();
            Method method = point.getTarget().getClass().getMethod(methodName, parameterTypes);
            DataSource methodAnnotation = method.getAnnotation(DataSource.class);
            methodAnnotation = methodAnnotation == null ? annotationOfClass : methodAnnotation;
            ContextConstant.DataSourceType dataSourceType = methodAnnotation != null
                    && methodAnnotation.value() != null ? methodAnnotation.value() : ContextConstant.DataSourceType.REPORT;

            MultipleDataSourceHandler.setRouteKey(dataSourceType.name());
        } catch (NoSuchMethodException e) {
            log.error("aspect error,err={}",e);
        }
    }

    /**
    *@Description: 后置切面
    */
    @After("execution(* com.fqfin.credit.business..*.*(..))")
    public void after(JoinPoint point) {
        MultipleDataSourceHandler.removeRouteKey();
    }
}
/**
 * @ClassName: MultipleDataSource
 * @Description: 数据源路由实现类
 */
public class MultipleDataSource   extends AbstractRoutingDataSource {
    private static final Logger log = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
    @Override
    protected Object determineCurrentLookupKey() {
        String dataSourceCofig = MultipleDataSourceHandler.getRouteKey();
        if (dataSourceCofig == null) {
            log.info("当前数据源为[report]");
        } else {
            log.info("当前数据源为{}", dataSourceCofig);
        }
        return dataSourceCofig;
    }
}

  3、使用ThreadLocal存储当前使用数据源实例的key

/**
 * @ClassName: MultipleDataSourceHandler
 * @Description: 多数据源持有类,数据源路由
 */
public class MultipleDataSourceHandler {
    private static final Logger logger = LoggerFactory.getLogger(MultipleDataSourceHandler.class);
    /**
     * 线程副本,保证线程安全
     */
    private static ThreadLocal<String> routeKey = new ThreadLocal<String>();

     /**
     *@Description: 绑定当前线程数据源路由的key 使用完成后必须调用removeRouteKey()方法删除
     */
    public static void setRouteKey(String key){
        logger.warn("系统切换到{}数据源",key);
        routeKey.set(key);
    }

    /**
    *@Description: 获取当前线程的路由
    */
    public static String getRouteKey(){
        return routeKey.get();
    }



    /**
    *@Description: 删除与当前线程绑定的路由key
    */
    public static void removeRouteKey(){
        routeKey.remove();
    }

}

  4、类或者方法上添加注解,实现路由

@DataSource(ContextConstant.DataSourceType.CTS)
@Service
public class BaiHangAccountService  extends AbstractService {
    @Autowired
    private BaihangLoanAccountInfoMapper baihangLoanAccountInfoMapper;

    public List<BaihangLoanAccountInfo> getList(BaihangLoanAccountInfoExample example, PageRequest pageRequest){
      
        return pageList;

    }
    @DataSource(ContextConstant.DataSourceType.REPORT)
    public void updateByPrimaryKeySelective( BaihangLoanAccountInfo baihangLoanAccountInfo){
        baihangLoanAccountInfoMapper.updateByPrimaryKeySelective(baihangLoanAccountInfo);
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值