Spring Boot 基于Druid连接池的多数据源 + 自定义事务管理器

前言

本篇文章详细介绍了基于阿里巴巴的Druid连接池的多数据源以及Spring 的自定义事务管理器实现方式。
技术栈:
JDK:8
Spring Boot:2.5.5
Mybatis:2.2.0
Druid:1.1.10 (建议使用1.2.8,我受限于Nexus只能用1.1.10)

Yml配置

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&tinyInt1isBit=false
    username: root
    password: root
  datasource2:
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&tinyInt1isBit=false
    username: root
    password: root

这两个数据源可以是异构数据库(未实测),注意调整相关配置

Druid连接池配置类

package com.xxx.config;

import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import com.alibaba.druid.wall.WallConfig;
import com.alibaba.druid.wall.WallFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

/**
 * Description:
 * 阿里巴巴数据库库连接池配置类
 * <p>
 * date: 2021/11/26 15:20
 *
 * @author Arvin Lee
 * @version 1.0
 */
@Configuration
@Slf4j
public class DruidConfig {
    @Value("${spring.datasource.url:#{null}}")
    private String dbUrl;
    @Value("${spring.datasource.username: #{null}}")
    private String username;
    @Value("${spring.datasource.password:#{null}}")
    private String password;
    @Value("${spring.datasource.driverClassName:#{null}}")
    private String driverClassName;

    @Value("${spring.datasource2.url:#{null}}")
    private String dbUrl2;
    @Value("${spring.datasource2.username: #{null}}")
    private String username2;
    @Value("${spring.datasource2.password:#{null}}")
    private String password2;
    @Value("${spring.datasource2.driverClassName:#{null}}")
    private String driverClassName2;

    @Bean
    @Primary
    public DataSource dataSource() {
        return CommonFunctionInConfig.getDataSource(dbUrl,
                username,
                password,
                driverClassName,
                statFilter(), wallFilter());
    }

    @Bean
    public DataSource dataSource2() {
        return CommonFunctionInConfig.getDataSource(dbUrl2, username2, password2, driverClassName2,
                statFilter(), wallFilter());
    }

    @Bean
    public ServletRegistrationBean druidServlet() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");

        //控制台管理用户,加入下面2行 进入druid后台就需要登录
        servletRegistrationBean.addInitParameter("loginUsername", "admin");
        servletRegistrationBean.addInitParameter("loginPassword", "admin");
        return servletRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new WebStatFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        filterRegistrationBean.addInitParameter("profileEnable", "true");
        return filterRegistrationBean;
    }

    @Bean
    public StatFilter statFilter() {
        StatFilter statFilter = new StatFilter();
        //slowSqlMillis用来配置SQL慢的标准,执行时间超过slowSqlMillis的就是慢。
        statFilter.setLogSlowSql(true);
        //SQL合并配置
        statFilter.setMergeSql(true);
        //slowSqlMillis的缺省值为3000,也就是3秒。
        statFilter.setSlowSqlMillis(1000);
        return statFilter;
    }

    @Bean
    public WallFilter wallFilter() {
        WallFilter wallFilter = new WallFilter();
        //允许执行多条SQL
        WallConfig config = new WallConfig();
        config.setMultiStatementAllow(true);
        wallFilter.setConfig(config);
        return wallFilter;
    }
}

配置通用方法类

package com.xxx.config;

import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.wall.WallFilter;

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

/**
 * Description:
 * 配置包下的通用方法
 * <p>
 * date: 2021/11/29 15:06
 *
 * @author Arvin Lee
 * @version 1.0
 */
public class CommonFunctionInConfig {

    /**
     * getDataSource
     * 获取数据源
     *
     * @param dbUrl           数据库连接地址
     * @param username        用户名
     * @param password        密码
     * @param driverClassName 驱动名
     * @param statFilter      -
     * @param wallFilter      -
     * @return com.alibaba.druid.pool.DruidDataSource
     * @author Arvin Lee
     * @date 2021/12/7 9:28
     **/
    protected static DruidDataSource getDataSource(String dbUrl,
                                                   String username,
                                                   String password,
                                                   String driverClassName,
                                                   StatFilter statFilter,
                                                   WallFilter wallFilter) {
        DruidDataSource datasource = new DruidDataSource();
        datasource.setUrl(dbUrl);
        datasource.setUsername(username);
        datasource.setPassword(password);
        datasource.setDriverClassName(driverClassName);
        List<Filter> filters = new ArrayList<>();
        filters.add(statFilter);
        filters.add(wallFilter);
        datasource.setProxyFilters(filters);
        return datasource;
    }
}

连接池其他配置参数可以在这里统一调整

第一个数据源(datasource)配置类

package com.xxx.config;

import com.xxx.BaseMapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import tk.mybatis.spring.annotation.MapperScan;

import javax.annotation.Resource;
import javax.sql.DataSource;

/**
 * Description:
 * mybatis数据源配置
 * <p>
 * date: 2021/11/26 15:39
 *
 * @author Arvin Lee
 * @version 1.0
 */
@Configuration
@MapperScan(basePackages = "com.xxx.dao.datasource1", markerInterface = BaseMapper.class, sqlSessionFactoryRef = "sqlSessionFactory")
public class DataSourceConfig {
    @Resource
    @Qualifier("dataSource")
    private DataSource ds;

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(ds);
        //指定mapper xml目录
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        factoryBean.setMapperLocations(resolver.getResources("classpath:mapper/datasource1/*.xml"));
        return factoryBean.getObject();

    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate() throws Exception {
        // 使用上面配置的Factory
        return new SqlSessionTemplate(sqlSessionFactory());
    }

    /**
     * masterTransactionManager
     * 关于事务管理器,不管是JPA还是JDBC等都实现自接口 PlatformTransactionManager
     * 如果你添加的是 spring-boot-starter-jdbc 依赖,框架会默认注入 DataSourceTransactionManager 实例。
     * 在Spring容器中,我们手工注解@Bean 将被优先加载,框架不会重新实例化其他的 PlatformTransactionManager 实现类。
     *
     * @return org.springframework.jdbc.datasource.DataSourceTransactionManager
     * @author Arvin Lee
     * @date 2021/11/26 15:42
     **/
    @Bean(name = "transactionManager")
    @Primary
    public DataSourceTransactionManager masterTransactionManager() {
        //MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源
        // 与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。
        return new DataSourceTransactionManager(ds);

    }
}
  1. 此处需要注意一下,代码经过脱敏,所以MapperScan 注解的 basePackages 路径com.xxx.dao.datasource1 大意如下
    –com.xxx
    ------dao (存放dao层接口)
    ----------datasource1(与第一个数据源映射的dao)
    ----------datasource2(与第二个数据源映射的dao)
  2. markerInterface 对应的BaseMapper.class是我自定义的Mybatis的BaseMapper,如果没有可以不写。
  3. sqlSessionFactoryRef 参考的sql会话工厂,这个与返回SqlSessionFactory的方法的方法名对应
  4. @Qualifier(“dataSource”) 这是给DataSource起别名,因为是多数据源,命名没有特殊要求。
  5. classpath:mapper/datasource1/*.xml 这个是mapper.xml映射路径,路径格式参考dao
    示例:
    –reosurce
    ------mapper
    ----------datasource1
    ----------datasource2

第二个数据源(datasource2)配置类

package com.xxx.config;

import com.xxx.common.BaseMapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import tk.mybatis.spring.annotation.MapperScan;

import javax.annotation.Resource;
import javax.sql.DataSource;

/**
 * Description:
 * mybatis数据源2配置
 * <p>
 * date: 2021/11/26 16:02
 *
 * @author Arvin Lee
 * @version 1.0
 */
@Configuration
@MapperScan(basePackages = "com.xxx.dao.datasource2", markerInterface = BaseMapper.class, sqlSessionFactoryRef = "sqlSessionFactory2")
public class DataSource2Config {
    @Resource
    @Qualifier("dataSource2")
    private DataSource ds;

    @Bean
    public SqlSessionFactory sqlSessionFactory2() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(ds);
        //指定mapper xml目录
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        factoryBean.setMapperLocations(resolver.getResources("classpath:mapper/datasource2/*.xml"));
        return factoryBean.getObject();
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate2() throws Exception {
        // 使用上面配置的Factory
        return new SqlSessionTemplate(sqlSessionFactory2());
    }

    /**
     * masterTransactionManager
     * 关于事务管理器,不管是JPA还是JDBC等都实现自接口 PlatformTransactionManager
     * 如果你添加的是 spring-boot-starter-jdbc 依赖,框架会默认注入 DataSourceTransactionManager 实例。
     * 在Spring容器中,我们手工注解@Bean 将被优先加载,框架不会重新实例化其他的 PlatformTransactionManager 实现类。
     *
     * @return org.springframework.jdbc.datasource.DataSourceTransactionManager
     * @author Arvin Lee
     * @date 2021/11/26 15:42
     **/
    @Bean(name = "transactionManager2")
    @Primary
    public DataSourceTransactionManager masterTransactionManager() {
        //MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源
        // 与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。
        return new DataSourceTransactionManager(ds);

    }
}
  1. 格式与第一个一致,只不过dao、mapper.xml、datasource、SQLSessionFactory不同,需要加个2
  2. 如果3个数据源,以此类推,再加个DataSourceConfig 同时也要修改DruidConfig

自定义事务管理器通用方法类

package com.xxx.interceptor;

import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.interceptor.*;

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

/**
 * Description:
 * <p>
 * date: 2021/11/29 10:11
 *
 * @author Arvin Lee
 * @version 1.0
 */
public class OtherFunction {
    private static final int TX_METHOD_TIMEOUT = 50000;

    protected static TransactionInterceptor getInterceptor(TransactionManager transactionManager) {
        NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
        /*只读事务,不做更新操作*/
        RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
        readOnlyTx.setReadOnly(true);
        readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
        /*当前存在事务就使用当前事务,当前不存在事务就创建一个新的事务*/
        RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
        requiredTx.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
        requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        requiredTx.setTimeout(TX_METHOD_TIMEOUT);
        Map<String, TransactionAttribute> txMap = new HashMap<>(8);
        txMap.put("add*", requiredTx);
        txMap.put("save*", requiredTx);
        txMap.put("insert*", requiredTx);
        txMap.put("update*", requiredTx);
        txMap.put("delete*", requiredTx);
        txMap.put("get*", readOnlyTx);
        txMap.put("select*", readOnlyTx);
        txMap.put("query*", readOnlyTx);
        source.setNameMap(txMap);
        return new TransactionInterceptor(transactionManager, source);
    }
}

这个是下面事务管理器的基础

自定义第一个数据源的事务管理器

package com.xxx.interceptor;

import com.xxx.config.DataSourceConfig;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.interceptor.TransactionInterceptor;

import javax.annotation.Resource;

/**
 * Description:
 * 使用 DataSourceConfig
 * 生成的事务管理器,通过AOP为指定包下的service方法提供事务支持
 * 并根据不同的方法提供不同的事务隔离级别
 *
 * @author Arvin Lee
 * @version 1.0
 * @see DataSourceConfig
 *
 * <p>
 * date: 2021/11/26 15:44
 */
@Aspect
@Configuration
public class TxAdviceInterceptor {
    /**
     * 单位秒
     */

    private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.xxx.service.datasource1.*.*(..))";

    @Resource
    @Qualifier("transactionManager")
    private TransactionManager transactionManager;
    @Bean
    public TransactionInterceptor txAdvice() {
        return OtherFunction.getInterceptor(transactionManager);
    }
    @Bean
    public Advisor txAdviceAdvisor() {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
        return new DefaultPointcutAdvisor(pointcut, txAdvice());
    }
}

service映射路径规则与dao一致,不做赘述

自定义第二个数据源的事务管理器

package com.xxx.interceptor;

import org.aspectj.lang.annotation.Aspect;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.interceptor.TransactionInterceptor;

import javax.annotation.Resource;

/**
 * Description:
 * <p>
 * date: 2021/11/26 16:05
 *
 * @author Arvin Lee
 * @version 1.0
 */
@Aspect
@Configuration
public class TxAdvice2Interceptor {
    /**
     * 单位秒
     */
    private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.xxx.service.datasource2.*.*(..))";
    @Resource
    @Qualifier("transactionManager2")
    private TransactionManager transactionManager;
    @Bean
    public TransactionInterceptor txAdvice2() {
        return OtherFunction.getInterceptor(transactionManager);
    }
    @Bean
    public Advisor txAdviceAdvisor2() {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
        return new DefaultPointcutAdvisor(pointcut, txAdvice2());
    }
}

同上

结语

  1. 实现以上代码以后就可以实现多数据源以及多数据源的自定义事务了。如果由异常或其他问题可以留言讨论
  2. 我的实现是为了解决业务上的不同库问题,当然,这个也可以用来做读写分离
  3. 关于Mybatis的自定义Mapper 自定义Service我会再另一篇文章中介绍
  4. 其实很多人都不知道,druid是有可视化界面的,相信细心的童鞋已经通过DruidConfig这个类发现类,可以配置账号密码,访问地址就是 https://localhost:port/druid 这个UI界面详细展示了连接池的相关信息,并对应用的SQL进行监控,基于这个数据,也可以拓展个系统SQL性能监控功能模块,详情请见官网
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,这个问题很具体,我可以回答。在 Spring Boot 中使用 Druid 连接,可以通过手动指定数据的方式来实现。具体步骤如下: 1. 在 pom.xml 文件中添加 Druid数据库驱动的依赖。 2. 在 application.yml 或 application.properties 中配置 Druid 相关属性,如下: ```yaml spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driverClassName: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: root filters: stat,wall,log4j maxActive: 20 initialSize: 1 maxWait: 60000 minIdle: 1 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: select 'x' testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true maxPoolPreparedStatementPerConnectionSize: 20 ``` 3. 在配置类中手动创建 Druid 数据,如下: ```java @Configuration public class DruidConfig { @Value("${spring.datasource.url}") private String dbUrl; @Value("${spring.datasource.username}") private String username; @Value("${spring.datasource.password}") private String password; @Value("${spring.datasource.driver-class-name}") private String driverClassName; @Bean public DataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setUrl(dbUrl); dataSource.setUsername(username); dataSource.setPassword(password); dataSource.setDriverClassName(driverClassName); return dataSource; } } ``` 通过以上步骤,就可以使用手动指定数据的方式来整合 Druid 连接了。如果还有疑问,可以继续追问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

暴躁码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值