当前位置: 首页 > news >正文

【SpringBoot Druid Mysql多数据源整合】

SpringBoot Druid Mysql多数据源整合

  • 一、背景
  • 二、配置结果
    • 2.1 SpringBoot java 类配置
      • 2.1.1 启动类配置
      • 2.1.2 java Config配置
    • 2.2 SpringBoot yml 配置
  • 三、mybatis插件配置
    • 3.1 PageHelper的yml配置
    • 3.2 mybatis设置自定义字段默认值
  • 四、配置解释

一、背景

  • 公司项目需要连接另外一个数据库来处理数据

  • 处理方法其实很多,如下所示:

    1. 给这个数据库单独跑一个服务,用来查询数据
    2. 直接将该数据库表同步到本地服务数据库
    3. 在本地服务中直接配置多数据源用来查询数据
  • 其实每种方法都有自己的利弊,由于各种原因还是选择了方法3,配置多数据源进行查询

  • SpringBoot版本 2.3.12.RELEASE

  • Druid版本

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.22</version>
</dependency>
  • 其他mysql,mybatis都是正常配置

二、配置结果

2.1 SpringBoot java 类配置

2.1.1 启动类配置

@SpringBootApplication(exclude = {DruidDataSourceAutoConfigure.class})
@ServletComponentScan
@EnableCaching
public class TestApplication {

    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }

}
  • 其中使用@SpringBootApplication(exclude = {DruidDataSourceAutoConfigure.class})的原因见1

2.1.2 java Config配置

  • 需要建立3个java类,DruidAutoConfiguration.java,DruidConfigTestOne.java,DruidConfigTestTwo.java
  • DruidAutoConfiguration作用见2
  • DruidConfigTestOne作用见3
  • DruidConfigTestTwo作用见4
@Configuration
@ConditionalOnClass(DruidDataSource.class)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties(DruidStatProperties.class)
@Import({DruidSpringAopConfiguration.class,
        DruidStatViewServletConfiguration.class,
        DruidWebStatFilterConfiguration.class,
        DruidFilterConfiguration.class})
public class DruidAutoConfiguration {
}
@Configuration
@MapperScan(basePackages = DruidConfigTestOne.PACKAGE, sqlSessionTemplateRef  = "testOneSqlSessionTemplate")
public class DruidConfigTestOne {
    /**
     * mapper 所在包
     * 这个地方需要注意xml文件要在resources目录com/test/one/mapper下,mapper java类在com.test.one.mapper包下,这样就不需要配置xml文件所在位置,否则需要在sqlSessionFactory上面新增mapperLocation的配置
     * bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/com/test/one/mapper/*.xml"));
     */
    protected static final String PACKAGE = "com.test.one.mapper";

    /**
     * 配置自定义数据源,一定要切记,在SpringBoot启动类上去掉DruidDataSourceAutoConfigure.class
     *
     * @return javax.sql.DataSource
     * @author liulin
     * @date 2025/4/11 16:49
     */
    @Bean("testOneDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.druid.test-one")
    @Primary
    public DataSource testOneDataSource(){
        return DruidDataSourceBuilder.create().build();
    }


    /**
     * 创建并配置一个SqlSessionFactory实例
     * 该方法使用Spring框架的@Bean注解,表明该方法会返回一个SqlSessionFactory对象,该对象将被Spring容器管理
     *
     * @param dataSource 数据源,通过该参数指定SqlSessionFactory将要使用的数据库连接
     * @return SqlSessionFactory 一个配置好的SqlSessionFactory实例,用于创建SqlSession
     * @throws Exception 如果创建过程中出现错误,将抛出异常
     */
    @Bean(name = "testOneSqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactory(@Qualifier("testOneDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        return bean.getObject();
    }

    /**
     * 创建一个数据源事务管理器
     * <p/>
     * 该方法定义了一个Spring的@Bean注解,用于创建一个DataSourceTransactionManager实例
     * 这个实例用于管理与数据源相关的事务这个方法被标记为@Primary,意味着在相同类型
     * 的多个Bean存在时,Spring会默认选择这个Bean
     *
     * @param dataSource 数据源实例,用于事务管理器的构造
     * @return DataSourceTransactionManager实例,用于管理数据库事务
     */
    @Bean(name = "testOneTransactionManager")
    @Primary
    public DataSourceTransactionManager transactionManager(@Qualifier("testOneDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    /**
     * 创建并配置一个SqlSessionTemplate实例
     * 该实例用于执行SQL操作,利用Spring框架的依赖注入功能进行管理
     *
     * @param sqlSessionFactory 用于创建SqlSessionTemplate的工厂,通过资格器@Qualifier指定特定的工厂
     * @return 返回配置好的SqlSessionTemplate实例,用于执行数据库操作
     * @throws Exception 如果实例化过程中遇到任何问题,则抛出异常
     */
    @Bean(name = "testOneSqlSessionTemplate")
    @Primary
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("testOneSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}
@Configuration
@MapperScan(basePackages = DruidConfigTestTwo.PACKAGE, sqlSessionTemplateRef  = "testTwoSqlSessionTemplate")
public class DruidConfigTestTwo {
    /**
     * mapper 所在包
     */
    protected static final String PACKAGE = "com.test.two.mapper";

    /**
     * 配置自定义数据源,一定要切记,在SpringBoot启动类上去掉DruidDataSourceAutoConfigure.class
     *
     * @return javax.sql.DataSource
     * @author liulin
     * @date 2025/4/11 16:49
     */
    @Bean("testTwoDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.druid.test-two")
    public DataSource testTwoDataSource(){
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 创建并配置一个SqlSessionFactory实例
     * 该方法使用Spring框架的@Bean注解,表明该方法会返回一个SqlSessionFactory对象,该对象将被Spring容器管理
     *
     * @param dataSource 数据源,通过该参数指定SqlSessionFactory将要使用的数据库连接
     * @return SqlSessionFactory 一个配置好的SqlSessionFactory实例,用于创建SqlSession
     * @throws Exception 如果创建过程中出现错误,将抛出异常
     */
    @Bean(name = "testTwoSqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("testTwoDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        return bean.getObject();
    }

    /**
     * 创建一个数据源事务管理器
     * <p/>
     * 该方法定义了一个Spring的@Bean注解,用于创建一个DataSourceTransactionManager实例
     * 这个实例用于管理与数据源相关的事务这个方法被标记为@Primary,意味着在相同类型
     * 的多个Bean存在时,Spring会默认选择这个Bean
     *
     * @param dataSource 数据源实例,用于事务管理器的构造
     * @return DataSourceTransactionManager实例,用于管理数据库事务
     */
    @Bean(name = "testTwoTransactionManager")
    public DataSourceTransactionManager transactionManager(@Qualifier("testTwoDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    /**
     * 创建并配置一个SqlSessionTemplate实例
     * 该实例用于执行SQL操作,利用Spring框架的依赖注入功能进行管理
     *
     * @param sqlSessionFactory 用于创建SqlSessionTemplate的工厂,通过资格器@Qualifier指定特定的工厂
     * @return 返回配置好的SqlSessionTemplate实例,用于执行数据库操作
     * @throws Exception 如果实例化过程中遇到任何问题,则抛出异常
     */
    @Bean(name = "testTwoSqlSessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("testTwoSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

2.2 SpringBoot yml 配置

  • yml 配置原因见5
spring:
  datasource:
    # 这个配置可要可不要,意义不大
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      # Spring监控
      aop-patterns: com.test.one.service.*,com.test.two.service.*
      # 监控配置
      web-stat-filter:
        # 是否启用StatFilter默认值true
        enabled: true
        # 添加过滤规则
        url-pattern: /*
        # 忽略过滤的格式
        exclusions: /druid/*,*.js,*.gif,*.jpg,*.png,*.css,*.ico
        # 关闭session监控
        session-stat-enable: false
      # Druid内置提供了一个StatViewServlet用于展示Druid的统计信息
      stat-view-servlet:
        # 是否启用StatViewServlet默认值true
        enabled: true
        # 访问路径为/druid时,跳转到StatViewServlet
        url-pattern: /druid/*
        # 是否能够重置数据
        reset-enable: false
        # 需要账号密码才能访问控制台,默认为root
        login-username: test
        login-password: test
        # IP白名单
        allow:
        # IP黑名单(共同存在时,deny优先于allow)
        deny:
      test-one:
        url: jdbc:mysql://127.0.0.1:3306/test_one?useSSL=false&characterEncoding=utf8&useTimezone=true&serverTimezone=GMT%2B8
        username: root
        password: '123456'
        driver-class-name: com.mysql.cj.jdbc.Driver
        # 初始化时建立物理连接的个数
        initial-size: 5
        # 连接池的最小空闲数量
        min-idle: 5
        # 连接池最大连接数量
        max-active: 200
        # 获取连接时最大等待时间,单位毫秒
        max-wait: 60000
        # 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
        test-while-idle: true
        # 既作为检测的间隔时间又作为testWhileIdel执行的依据
        time-between-eviction-runs-millis: 60000
        # 销毁线程时检测当前连接的最后活动时间和当前时间差大于该值时,关闭当前连接(配置连接在池中的最小生存时间)
        min-evictable-idle-time-millis: 30000
        # 用来检测数据库连接是否有效的sql 必须是一个查询语句(oracle中为 select 1 from dual)
        validation-query: select 1
        # 申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
        test-on-borrow: false
        # 归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
        test-on-return: false
        # 是否缓存preparedStatement, 也就是PSCache,PSCache对支持游标的数据库性能提升巨大,比如说oracle,在mysql下建议关闭。
        pool-prepared-statements: false
        # 置监控统计拦截的filters,去掉后监控界面sql无法统计,stat: 监控统计、Slf4j:日志记录、waLL: 防御sqL注入
        filters: stat,wall,slf4j
        # 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
        max-pool-prepared-statement-per-connection-size: -1
        # 合并多个DruidDataSource的监控数据
        use-global-data-source-stat: true
        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
        connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
      test-two:
        url: jdbc:mysql://127.0.0.1:3306/test_two?useSSL=false&characterEncoding=utf8&useTimezone=true&serverTimezone=GMT%2B8
        username: root
        password: '123456'
        driver-class-name: com.mysql.cj.jdbc.Driver
        # 初始化时建立物理连接的个数
        initial-size: 5
        # 连接池的最小空闲数量
        min-idle: 5
        # 连接池最大连接数量
        max-active: 200
        # 获取连接时最大等待时间,单位毫秒
        max-wait: 60000
        # 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
        test-while-idle: true
        # 既作为检测的间隔时间又作为testWhileIdel执行的依据
        time-between-eviction-runs-millis: 60000
        # 销毁线程时检测当前连接的最后活动时间和当前时间差大于该值时,关闭当前连接(配置连接在池中的最小生存时间)
        min-evictable-idle-time-millis: 30000
        # 用来检测数据库连接是否有效的sql 必须是一个查询语句(oracle中为 select 1 from dual)
        validation-query: select 1
        # 申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
        test-on-borrow: false
        # 归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
        test-on-return: false
        # 是否缓存preparedStatement, 也就是PSCache,PSCache对支持游标的数据库性能提升巨大,比如说oracle,在mysql下建议关闭。
        pool-prepared-statements: false
        # 置监控统计拦截的filters,去掉后监控界面sql无法统计,stat: 监控统计、Slf4j:日志记录、waLL: 防御sqL注入
        filters: stat,wall,slf4j
        # 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
        max-pool-prepared-statement-per-connection-size: -1
        # 合并多个DruidDataSource的监控数据
        use-global-data-source-stat: true
        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
        connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

三、mybatis插件配置

3.1 PageHelper的yml配置

  • yml配置原因见6
#分页插件配置
pagehelper:
  helper-dialect: mysql
  reasonable: true
  support-methods-arguments: true
  params: count=countSql

3.2 mybatis设置自定义字段默认值

  • 需要2个java类,MyBatisInterceptorConfig.java,SetMySqlValueAutoConfiguration.java
  • MyBatisInterceptorConfig见原因7
  • SetMySqlValueAutoConfiguration见原因8
@Slf4j
@SuppressWarnings("unchecked")
@Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }) })
public class MyBatisInterceptorConfig implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];

        // 获取 SQL 命令
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();

        // 获取参数
        Object parameter = invocation.getArgs()[1];

        // 如果不存在参数,直接返回
        if (parameter == null) {
            return invocation.proceed();
        }

        // 如果是批量插入一般是List类型,包装在ParamMap中
        if (parameter instanceof MapperMethod.ParamMap) {
            MapperMethod.ParamMap<Object> paramMap = (MapperMethod.ParamMap<Object>) parameter;
            for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
                Object paramValue = entry.getValue();
                if (paramValue instanceof List) {
                    List<Object> list = (List<Object>) paramValue;
                    for (Object value : list) {
                        // 设置对应数据值
                        setFieldValues(value, sqlCommandType);
                    }
                    break;
                }
            }
        } else {
            setFieldValues(parameter, sqlCommandType);
        }
        return invocation.proceed();
    }

    /**
     * 设置字段对应值
     *
     * @param parameter 实体信息
     * @param sqlCommandType sql类型
     * @author liulin
     * @date 2024-04-22 16:32:39
     **/
    private void setFieldValues(Object parameter, SqlCommandType sqlCommandType) throws IllegalAccessException, InvocationTargetException {
        // 获取私有成员变量
        Method[] declaredMethods = parameter.getClass().getDeclaredMethods();

        for (Method method : declaredMethods) {
            if (SqlCommandType.INSERT.equals(sqlCommandType)) {
                String name = method.getName();
                switch (name) {
                    case "setUpdateUser":
                    case "setCreateUser":
                        method.invoke(parameter, getCurrentUserId());
                        break;
                    case "setCreateTime":
                    case "setUpdateTime":
                        method.invoke(parameter, new Date());
                        break;
                    case "setUpdateUserType":
                    case "setCreateUserType":
                        method.invoke(parameter, getCurrentUserType());
                        break;
                    default:
                        break;
                }
            } else if (SqlCommandType.UPDATE.equals(sqlCommandType)) {
                String name = method.getName();
                switch (name) {
                    case "setUpdateUser":
                        method.invoke(parameter, getCurrentUserId());
                        break;
                    case "setUpdateTime":
                        method.invoke(parameter, new Date());
                        break;
                    case "setUpdateUserType":
                        method.invoke(parameter, getCurrentUserType());
                        break;
                    default:
                        break;
                }
            }
        }
    }

    /**
     * 获取当前用户类型
     *
     * @return int
     * @author liulin
     * @date 2024-11-14 15:05:55
     **/
    private Integer getCurrentUserType() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null) {
            return null;
        }
        Object principal = authentication.getPrincipal();
        if (principal instanceof CarfiSessionUser) {
            return 1;
        }
        return null;
    }

    /**
     * 获取当前用户id
     *
     * @return String
     * @author liulin
     * @date 2024-04-22 13:42:37
     **/
    private String getCurrentUserId() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null) {
            return null;
        }
        Object principal = authentication.getPrincipal();
        if (principal instanceof CarfiSessionUser) {
            return CarfiUserUtils.getSysUser().getUserId();
        }
        return null;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // comment explaining why the method is empty
    }
}
@Configuration
@ConditionalOnBean({SqlSessionFactory.class})
@AutoConfigureAfter({MybatisAutoConfiguration.class})
@Lazy(false)
public class SetMySqlValueAutoConfiguration implements InitializingBean {

    @Resource
    private List<SqlSessionFactory> sqlSessionFactoryList;
    @Override
    public void afterPropertiesSet() throws Exception {
        MyBatisInterceptorConfig interceptor = new MyBatisInterceptorConfig();
        for (SqlSessionFactory sqlSessionFactory : this.sqlSessionFactoryList) {
            org.apache.ibatis.session.Configuration configuration = sqlSessionFactory.getConfiguration();
            if (!this.containsInterceptor(configuration, interceptor)) {
                configuration.addInterceptor(interceptor);
            }
        }
    }

    private boolean containsInterceptor(org.apache.ibatis.session.Configuration configuration, Interceptor interceptor) {
        try {
            return configuration.getInterceptors().contains(interceptor);
        } catch (Exception var4) {
            return false;
        }
    }
}

四、配置解释


  1. DruidDataSourceAutoConfigure

    • 为什么要在启动类上排除DruidDataSourceAutoConfigure,是因为这个配置类com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure是下面这个样子在这里插入图片描述
    • 它自动装配了一堆druid的默认配置,包括数据源,监控界面,druid AOP切面等等类,其中DataSourceProperties导致spring.datasource下面必须要有默认数据库配置,否则就会报错,所以其实这里可以弄一个主数据源放上去,当然意义不是很大。
    ↩︎
  2. DruidAutoConfiguration

    • 因为我排除了DruidDataSourceAutoConfigure,所以必须要把监控界面,druid AOP切面等等重新引入,否则druid的监控界面就没法正常使用了,druid监控界面进入路径: http://127.0.0.1:8080/login.html
    ↩︎
  3. DruidConfigTestOne

    • 这个就很简单咯,就是配置数据源,sqlSessionFactory,事务管理器等一堆和数据配置有关的,@Primary保证是主数据源加载
    ↩︎
  4. DruidConfigTestTwo

    • 和DruidConfigTestOne作用一致,就是副数据源
    ↩︎
  5. Druid 的 yml配置

    • yml为什么可以像我上面这么配置,其实主要还是看DruidDataSourceAutoConfigure,他里面@EnableConfigurationProperties,@Import都可以点进去,可以看里面的字段属性,实际与yml的配置相关
    ↩︎
  6. PageHelper的yml配置

    • 这个主要见PageHelperAutoConfiguration.java,com.github.pagehelper.autoconfigure.PageHelperAutoConfiguration在这里插入图片描述
    • 可以看到实现了InitializingBean,并且在afterPropertiesSet方法中设置了拦截器PageInterceptor,com.github.pagehelper.PageInterceptor
    ↩︎
  7. MyBatisInterceptorConfig

    • 这个没啥好说的,就是在新增,更新时自动注入一些自定义字段值
    ↩︎
  8. SetMySqlValueAutoConfiguration

    • 这个是因为MybatisAutoConfiguration.java类,org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration中的sqlSessionFactory被我自定义的sqlSessionFactory干扰了导致无法实现mybatis的拦截器注入,所以我就仿照PageHelperAutoConfiguration实现了一次自动注入,这样mybatis的自定义拦截器就不会因为自定义sqlSessionFactory而失效了。MybatisAutoConfiguration的sqlSessionFactory是下面这样的在这里插入图片描述
    ↩︎

相关文章:

  • mindsdb AI 开源的查询引擎 - 用于构建 AI 的平台,该平台可以学习和回答大规模联合数据的问题。
  • 海洋大地测量基准与水下导航系列之八我国海洋水下定位装备发展现状
  • Doris数据库建表语法以及分区分桶简介
  • DeepSeek vs Grok vs ChatGPT:三大AI工具优缺点深度解析
  • 【数学建模】(智能优化算法)萤火虫算法(Firefly Algorithm)详解与实现
  • 【leetcode hot 100 32】最长有效括号
  • ArrayBlockingQueue的使用
  • 英语学习4.9
  • 基于php的成绩分析和预警与预测网站(源码+lw+部署文档+讲解),源码可白嫖!
  • 十四种逻辑器件综合对比——《器件手册--逻辑器件》
  • 记录centos8安装宝塔过程(两个脚本)
  • 【微知】Mellanox网卡网线插入后驱动的几个日志?(Cable plugged;IPv6 ... link becomes ready)
  • Oracle 23ai Vector Search 系列之5 向量索引(Vector Indexes)
  • 【VitePress】新增md文件后自动更新侧边栏导航
  • LeetCode 1223 投骰子模拟
  • 安卓AssetManager【一】-资源的查找过程
  • 从MySQL快速上手数据仓库Hive
  • 论文阅读笔记——Multi-Token Attention
  • 华为机试—最大最小路
  • 为什么在删除数据库存在‘if exists‘语句
  • wordpress网站怎么百度的到/杭州关键词推广优化方案
  • 协会网站建设制作/百度网盘搜索
  • 网站建设嘉兴公司电话/青岛seo建站
  • 想要注册一个公司网站怎么做/怎么引流到微信呢
  • 漳州正规网站建设价格/长尾关键词挖掘爱站工具
  • 阜蒙县建设镇网站/无需下载直接进入的网站的代码