SpringBoot整合druid实现多数据源切换并实现sql监控

本文介绍了如何在SpringBoot项目中整合Druid数据源,实现多个数据库之间的动态切换,并利用Druid的监控功能进行SQL监控。通过配置properties文件、创建数据源配置类、动态数据源上下文以及AOP实现数据源切换,最终达到在不修改代码的情况下,根据业务需求自由选择数据源。同时,文章还提到了在使用过程中需要注意的配置问题,如AOP的执行顺序和Shiro框架可能带来的影响。
摘要由CSDN通过智能技术生成

SpringBoot整合druid实现多数据源切换并实现sql监控

前言:目前需要项目需要引用不同数据库的数据(用户信息及订单数据等),想整合在一起,但是项目还在开发中,尝试着在项目中引用多数据源进行开发,通过aop实现数据源切换,以下是我代码实现:
一.开发环境:
       JDK:1.8 
       SpringBoot:2.4.4
二.加入依赖
     <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>

    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.3</version>
    </dependency>

    <!--druid连接池-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.22</version>
    </dependency>
    
    <!--SpringAOP-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
三.代码实现:
1. 在properties里面添加数据源配置
#==============================主数据库相关配置
spring.druid.master.driver-class-name =com.mysql.cj.jdbc.Driver
spring.druid.master.url=jdbc:mysql:XXXXXXXX
spring.druid.master.username=xxxx
spring.druid.master.password=xxxx


#==============================1数据库相关配置
spring.druid.slave1.driver-class-name =com.mysql.cj.jdbc.Driver
spring.druid.slave1.url=jdbc:mysql:XXXXXXXX
spring.druid.slave1.username=xxxx
spring.druid.slave1.password=xxxx


#最大连接数
spring.druid.maxActive=30
#最小连接数
spring.druid.minIdle=5
#获取连接的最大等待时间
spring.druid.maxWait=10000
#解决mysql8小时的问题
spring.druid.validationQuery=SELECT 'x'
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.druid.timeBetweenEvictionRunsMillis=60000
#空闲连接最小空闲时间
spring.druid.minEvictableIdleTimeMillis=300000
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
spring.druid.filters=stat,wall,log4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.druid.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

#开启控制台打印sql
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# mybatis 下划线转驼峰配置,两者都可以
mybatis.configuration.map-underscore-to-camel-case=true
2. 多数据源配置类
/**
 * @Author xw
 * @Description 多数据源配置类
 * @Date 2021/4/19  13:56
 */
@Configuration
public class DataSourceConfig {

    /**
     * 数据源1
     * spring.druid.master :application.properteis中对应属性的前缀
     * @return
     */
    @Bean(name = "master")
    @ConfigurationProperties(prefix = "spring.druid.master")
    public DataSource master() {
        System.out.println("master:"+new DruidDataSource());
        return new DruidDataSource();
    }

    /**
     * 数据源2
     * spring.druid.slave1 :application.properteis中对应属性的前缀
     * @return
     */
    @Bean(name = "slave1")
    @ConfigurationProperties(prefix = "spring.druid.slave1")
    public DataSource slave1() {
        System.out.println("slave1:"+ new DruidDataSource());
        return  new DruidDataSource();
    }

    /**
     * 动态数据源: 通过AOP在不同数据源之间动态切换
     * @return
     */
    @Primary //  注意:这里需要该注解声明是默认数据源
    @Bean(name = "dynamicDataSource")
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 配置多数据源
        Map<Object, Object> targetDataSource = new HashMap();
        targetDataSource.put("master", master());
        targetDataSource.put("slave1", slave1());

        // 默认数据源
        dynamicDataSource.setDefaultTargetDataSource(master());
        //数据源
        dynamicDataSource.setTargetDataSources(targetDataSource);
        return dynamicDataSource;
    }

    //添加事务
    @Bean(name = "transactionManager")
    public PlatformTransactionManager platformTransactionManager(@Qualifier("dynamicDataSource")DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }


    @Bean
    @ConditionalOnMissingBean
    public ServletRegistrationBean druidServlet() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        //白名单
        servletRegistrationBean.addInitParameter("allow", "ip");
       // IP黑名单(存在共同时,deny优先于allow):如果满足deny的话提示:Sorry, you are not permitted to view this page.
        servletRegistrationBean.addInitParameter("deny", "ip");
        //用于登陆的账号密码
        servletRegistrationBean.addInitParameter("loginUsername", "admin");
        servletRegistrationBean.addInitParameter("loginPassword", "admin");
        //是否能重置数据
        servletRegistrationBean.addInitParameter("resetEnable", "true");
        return servletRegistrationBean;
    }

    /**
     * @Description: 注册filter信息,用于拦截
     */
    @Bean
    public FilterRegistrationBean<WebStatFilter> filterRegistrationBean() {
        //创建过滤器
        FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(new WebStatFilter());
        //设置过滤器过滤路径
        filterRegistrationBean.addUrlPatterns("/*");
        //忽略过滤得形式
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }

}
3. 动态数据源上下文,数据源相关操作类
/**
 * @Author xw
 * @Description 动态数据源上下文,数据源相关操作类
 * @Date 2021/4/19  13:59
 */
public class DataSourceContextHolder {
    private static final Logger logger = LoggerFactory.getLogger(DataSourceContextHolder.class);

    public static final String Mater = "master";
    public static final String Slave1 = "slave1";

    private static final ThreadLocal<String> contextHolder = ThreadLocal.withInitial(() -> Mater);

    /**
     * 设置数据源名
     * @param dataSource
     */
    public static void setDB(String dataSource ) {
        logger.info("切换数据源 : {}",dataSource);
        contextHolder.set(dataSource);
    }

    /**
     * 获取数据源名
     * @return
     */
    public static String getDB() {
        return contextHolder.get();
    }

    /**
     * 清除数据源名
     */
    public static void clearDB() {
        contextHolder.remove();
    }


}
4. 动态获取数据源(动态获取数据源需要继承AbstractRoutingDataSource,并重写determineCurrentLookupKey()方法,此方法是在open connection**时触发, 事务是在connection层面管理的,启用事务后,一个事务内部的connection是复用的,所以就算AOP切了数据源字符串,但是数据源并不会被真正修改这是一个注意事项,后续会进行说明
**
 * @Author xw
 * @Description 动态获取数据源
 * @Date 2021/4/19  14:00
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);

    public static  DynamicDataSource build() {
        return new DynamicDataSource();
    }

    @Override
    protected Object determineCurrentLookupKey() {
        logger.info("当前使用数据源 : {}",DataSourceContextHolder.getDB());
        return DataSourceContextHolder.getDB();
    }
}
5. 自定义注解(自定义aop注解,实现自由切换数据源)
**
 * 自定义注解
 */
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface DS  {
    String value() default DataSourceContextHolder.Mater;
}
6.AOP实现数据源切换
**
 * @Author xw
 * @Description AOP实现数据源切换
 * @Date 2021/4/19  14:01
 */
@Aspect
@Component
@Order(-1)    //值越小,优先级越高 保证该AOP在@Transactional之前执行
public class DynamicDataSourceAspect {

    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
    @Pointcut("@annotation(DS)")
    public void dataSourcePointCut(){

    }
    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point)throws  Throwable{
        MethodSignature signature = (MethodSignature)point.getSignature();
        Method method = signature.getMethod();
        DS ds = method.getAnnotation(DS.class);
        // 通过判断 DataSource 中的值来判断当前方法应用哪个数据源
        if(ds == null){
            DataSourceContextHolder.setDB(DataSourceContextHolder.Mater);
            logger.debug("当前数据源: " + ds.value());
        }else{
            DataSourceContextHolder.setDB(ds.value());
            logger.debug("当前数据源:",ds.value());
        }

        try{
            return point.proceed();
        }finally {
            DataSourceContextHolder.clearDB();
            logger.info("clear datasource {}",ds.value());
        }
    }

}
至此,多数据源配置已经完成,我们只需要在service的实现类里面使用自定义的@DS注解就可以实现aop多数据源切换了

在这里插入图片描述
继承baseServiceImpl事务处理,需要在aop实现类上加上@Order(-1)进行优先级排序,不然事务在aop 之前执行时,切换数据源的方法在事务开启时触发,get不到数据源,会一直默认为主数据源。

如上代码有几点注意事项:

  1. 加@Order(-1),保证aop在事务之前执行,不然切换不了数据源
  2. 有可能做了@Order(-1)的优先级排序处理依然切换不了数据源,可能出现的问题:
    项目使用了shiro安全框架,做了DefaultAdvisorAutoProxyCreator,如下的bean
 @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

这是shiro的事务自动代理,启动项目时,二次代理首先开启了事务支持,所以加入了@Order(-1)也没有起效可能是这个原因引起的,如果不影响,可以直接注释掉这个bean就可以正常使用了。

可能我说的不太准确,理解不太深入,但是,可以查阅一下相关知识点进行补充。。。至此SpringBoot结合druid实现多数据源切换已经完成,经测试,没问题。
druid监控在配置里面已经有做过,项目访问ip+端口+druid(localhost:8080/druid)进行访问,输入自己配置的用户名密码(admin)登录可以查看durid监控。

完结,感谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值