资讯专栏INFORMATION COLUMN

SpringBoot+Druid实现多数据源监控及事务控制

moven_j / 2008人阅读

   背景:一个项目中可能存在多数据源的情况,虽然微服务中,一般是单数据源,但是例如后台管理这些管理接口则不适合使用微服务来
   提供接口,所以业务库也需要共存于后台管理项目,而后台管理项目中则有自己本身的一个权限数据库,则就会存在多数据源的情况。

   思路:Spring本身已经有实现数据源切换的功能类,可以实现在项目运行时根据相应key值切换到对应的数据源DataSource上。   
   我们只需扩展实现即可。
   并结合数据源动态切换为需要切换数据源的方法增加注解,从而实现对带有注解的拦截切换。

   问题:事务控制,缺省数据源生效,而切换为第二数据源时,事务的数据源默认采用了缺省的。
         网上有说更改切面和事务的执行顺序,但是试验后并未成功。

以下是为动态数据源切换,及缺省事务第二数据源的事务控制的实现方案,以springboot作为基础框架。

使用druid做数据源监控与管理
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.jdbc.Driver
    druid:
        first:  #数据源1
            url: jdbc:mysql://127.0.0.01:63885/demo?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8
            username: demo
            password: demo
        rongyuan:  #数据源2
            url: jdbc:mysql://127.0.0.01:63885/demo?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8
            username: demo
            password: demo
        initial-size: 10
        max-active: 100
        min-idle: 10
        max-wait: 60000
        pool-prepared-statements: true
        max-pool-prepared-statement-per-connection-size: 20
        time-between-eviction-runs-millis: 60000
        min-evictable-idle-time-millis: 300000
        validation-query: SELECT 1 FROM DUAL
        test-while-idle: true
        test-on-borrow: false
        test-on-return: false
        stat-view-servlet:
            enabled: true
            url-pattern: /druid/*
            #login-username: admin
            #login-password: admin
        filter:
            stat:
                log-slow-sql: true
                slow-sql-millis: 1000
                merge-sql: true
            wall:
                config:
                    multi-statement-allow: true
构建数据源及注入到动态数据源中
package io.y.common.datasources;

import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;

/**
 * @title 
 * @author zengzp
 * @time 2018年7月25日 上午11:22:46
 * @Description 
 */
@Configuration
// 加上此注解禁用数据源自动配置
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class DynamicDataSourceConfig {

    @Bean(name="first")
    @ConfigurationProperties("spring.datasource.druid.first")
    public DataSource firstDataSource(){
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name="rongyuan")
    @ConfigurationProperties("spring.datasource.druid.rongyuan")
    public DataSource secondDataSource(){
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public DynamicDataSource dataSource(@Qualifier("first")DataSource firstDataSource, @Qualifier("rongyuan")DataSource secondDataSource) {
        Map targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceNames.FIRST, firstDataSource);
        targetDataSources.put(DataSourceNames.SECOND, secondDataSource);
        return new DynamicDataSource(firstDataSource, targetDataSources);
    }
    
}
继承spring的动态实现,及重写数据源的获取方法
package io.y.common.datasources;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

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


/**
 * @title 
 * @author zengzp
 * @time 2018年7月25日 上午 10:20:31
 * @Description 
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    private static final ThreadLocal contextHolder = new ThreadLocal<>();

    public DynamicDataSource(DataSource defaultTargetDataSource, Map targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(new HashMap<>(targetDataSources));
        super.afterPropertiesSet();
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return getDataSource();
    }

    public static void setDataSource(String dataSource) {
        contextHolder.set(dataSource);
    }

    public static String getDataSource() {
        return contextHolder.get();
    }

    public static void clearDataSource() {
        contextHolder.remove();
    }

}
定义数据源切换注解
package io.y.common.datasources.annotation;

import java.lang.annotation.*;


/**
 * @title 多数据源注解
 * @author zengzp
 * @time 2018年7月25日 下午14:50:53
 * @Description 
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
    String name() default "";
}
定义切面,用来拦截带注解的方法,并在方法执行前实现数据源的切换
package io.y.common.datasources.aspect;

import java.lang.reflect.Method;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import io.y.common.datasources.DataSourceNames;
import io.y.common.datasources.DynamicDataSource;
import io.y.common.datasources.annotation.TargetDataSource;


/**
 * @title 多数据源切面处理类
 * @author zengzp
 * @time 2018年7月25日 下午11:56:43
 * @Description 
 */
@Aspect
@Component
@Order(0)
public class DataSourceAspect {
    protected Logger logger = LoggerFactory.getLogger(getClass());

    @Pointcut("@annotation(io.y.common.datasources.annotation.TargetDataSource)")
    public void dataSourcePointCut() {

    }

    @Before("dataSourcePointCut()")
    public void around(JoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        TargetDataSource ds = method.getAnnotation(TargetDataSource.class);
        if(ds == null){
            DynamicDataSource.setDataSource(DataSourceNames.FIRST);
            logger.debug("set datasource is " + DataSourceNames.FIRST);
        }else {
            DynamicDataSource.setDataSource(ds.name());
            logger.debug("set datasource is " + ds.name());
        }
    }
    
    @AfterReturning("dataSourcePointCut()")
    public void after(){
        DynamicDataSource.clearDataSource();
        logger.debug("clean datasource");
    }

}
数据源名称常量类
package io.y.common.datasources;

/**
 * @title 增加多数据源,在此配置
 * @author zengzp
 * @time 2018年7月25日 下午4:55:20
 * @Description 
 */
public interface DataSourceNames {
    String FIRST = "first";
    String SECOND = "rongyuan";

}

以上已经完成了动态数据源的切换,只需在Service方法上加上@TargetDataScoure注解并且指定需要切换的数据源名称,first数据源为缺省数据源。

如果使用@Transactional,缺省数据源的事务正常执行,如果使@TargetDataScoure切换为第二数据源并执行事务时,则数据源切换失败。

问题分析:

   大多数项目只需要一个事务管理器。如果存在多数据源的情况,事务管理器是否会生效,由于spingboot约定大于配置的理念,
   默认事务管理器无需我们再声明定义,而是默认加载时已经指定了其数据源,其数据源则为缺省数据源,如果执行事务时是第二数据源,则
   还会以第一数据源做处理,这时则会异常。

第二数据源事务控制处理

定义事务管理器 并指定其对应管理的数据源和声明name

package io.y.common.datasources;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

/**
 * @title 多事物管理器配置
 * @author zengzp
 * @time 2018年7月25日 下午4:55:33
 * @Description 
 */
@Configuration
public class TransactionConfig {
    
    public final static String DEFAULT_TX = "defaultTx";
    
    public final static String RONGYUAN_TX = "rongyuanTx";
    
    @Bean(name=TransactionConfig.DEFAULT_TX)
    public DataSourceTransactionManager transaction(@Qualifier(DataSourceNames.FIRST)DataSource firstDataSource){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(firstDataSource);
        return dataSourceTransactionManager;
    }
    
    @Bean(name=TransactionConfig.RONGYUAN_TX)
    public DataSourceTransactionManager rongyuanTransaction(@Qualifier(DataSourceNames.SECOND) DataSource rongyuanDataScoure){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(rongyuanDataScoure);
        return dataSourceTransactionManager;
    }

}
2.事务管理器使用
在@Transactional上指定使用哪个名称的事务管理器
    @Override
    @Transactional(value=TransactionConfig.RONGYUAN_TX, rollbackFor=Exception.class)
    @TargetDataSource(name = "rongyuan")
    public void deleteBatch(Integer[] advertIds) {
        if (advertIds == null || advertIds.length <= 0) {
            throw new IllegalArgumentException("参数异常");
        }
        advertDao.deleteBatch(advertIds);
    }

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/76516.html

相关文章

  • SpringBoot+Mybatis配置Druid据源

    摘要:多数据源,一般用于对接多个业务上独立的数据库可能异构数据库。这也就导致异构数据库的检查也是类似问题。内容略数据源多数据源,涉及到异构数据库,必须明确指定,否则的转换出错取值内容可参考初始连接数最大连接池数量。 开篇之前,说一句题外话。多数据源和动态数据源的区别。 多数据源,一般用于对接多个业务上独立的数据库(可能异构数据库)。 动态数据源,一般用于大型应用对数据切分。 配置参考 如...

    Songlcy 评论0 收藏0
  • 单手撸了个springboot+mybatis+druid

    摘要:配置想想,我们需要哪些数据库要用到,数据库连接池要用到桥接器要用到,因此要仓库点我去仓库中找到搜索这些加进去。 本文旨在用最通俗的语言讲述最枯燥的基本知识 最近身边的程序员掀起了学习springboot的热潮,说什么学会了springboot在大街上就可以横着走、什么有了springboot妈妈再也不担心我的编程了、什么BAT都喜欢的框架...听得作者那个心痒痒的,于是找了个时间,下载...

    adie 评论0 收藏0
  • springboot系列】springboot整合独立模块Druid + mybatis-plus

    摘要:申请连接时执行检测连接是否有效,做了这个配置会降低性能。作者在版本中使用,通过监控界面发现有缓存命中率记录,该应该是支持。允许和不允许单条语句返回多个数据集取决于驱动需求使用列标签代替列名称。需要驱动器支持。将自动映射所有复杂的结果。 项目github地址:https://github.com/5-Ason/aso... 具体可看 ./db/db-mysql 模块 本文主要实现的是对...

    RobinTang 评论0 收藏0

发表评论

0条评论

moven_j

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<