Spring Boot + Druid 多数据源配置

项目开发中存在不止一个数据库的情况,需要配置多数据源支持相关业务开发。本文记录 Spring Boot + Druid 多数据源的手动配置方式(与单个 Druid 配置的使用方式相同),按 Mapper 包路径隔离不同数据源。

添加依赖

1<dependency>
2    <groupId>com.alibaba</groupId>
3    <artifactId>druid-spring-boot-starter</artifactId>
4    <version>1.1.14</version>
5</dependency>

配置文件

第一个数据源:

# 第一个数据库配置
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai

# 连接池指定 springboot版本默认使用HikariCP 此处要替换成Druid
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

## 初始化连接池的连接数量 大小,最小,最大
spring.datasource.druid.initial-size=1
spring.datasource.druid.min-idle=1
spring.datasource.druid.max-active=20
## 配置获取连接等待超时的时间
spring.datasource.druid.max-wait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.druid.time-between-eviction-runs-millis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.druid.min-evictable-idle-time-millis=300000
spring.datasource.druid.validation-query=SELECT 'x'
spring.datasource.druid.test-on-borrow=false
spring.datasource.druid.test-on-return=false
spring.datasource.druid.filter.stat.log-slow-sql=true
# 是否缓存preparedStatement,也就是PSCache  官方建议MySQL下建议关闭   个人建议如果想用SQL防火墙 建议打开
spring.datasource.druid.pool-prepared-statements=false
spring.datasource.druid.max-pool-prepared-statement-per-connection-size=20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
spring.datasource.druid.filters=stat,wall
spring.datasource.druid.test-while-idle=true
# Spring监控,对内部各接口调用的监控
spring.datasource.druid.aop-patterns=com.felix.project.controller.*,com.felix.project.mapper.*,com.felix.project.service.*

第二个数据源(前缀换成 spring.datasource.felix,其余项类似):

# 第二个数据库配置
spring.datasource.felix.username=root
spring.datasource.felix.password=root
spring.datasource.felix.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.felix.url=jdbc:mysql://localhost:3306/test1?characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
# 连接池指定 springboot版本默认使用HikariCP 此处要替换成Druid
spring.datasource.felix.type=com.alibaba.druid.pool.DruidDataSource
## 初始化连接池的连接数量 大小,最小,最大
spring.datasource.felix.druid.initial-size=1
spring.datasource.felix.druid.min-idle=1
spring.datasource.felix.druid.max-active=20
## 配置获取连接等待超时的时间
spring.datasource.felix.druid.max-wait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.felix.druid.time-between-eviction-runs-millis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.felix.druid.min-evictable-idle-time-millis=300000
spring.datasource.felix.druid.validation-query=SELECT 'x'
spring.datasource.felix.druid.test-on-borrow=false
spring.datasource.felix.druid.test-on-return=false
spring.datasource.felix.druid.filter.stat.log-slow-sql=true
# 是否缓存preparedStatement,也就是PSCache  官方建议MySQL下建议关闭   个人建议如果想用SQL防火墙 建议打开
spring.datasource.felix.druid.pool-prepared-statements=false
spring.datasource.felix.druid.max-pool-prepared-statement-per-connection-size=20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
spring.datasource.felix.druid.filters=stat,wall
spring.datasource.felix.druid.test-while-idle=true
# Spring监控,对内部各接口调用的监控
spring.datasource.felix.druid.aop-patterns=com.felix.project.controller.*,com.felix.project.mapper.*,com.felix.project.service.*

mybatis.mapper-locations=classpath:mapper/*.xml,classpath:mapper2/*.xml
mybatis.type-aliases-package=com.felix.project.model
mapper.not-empty=false
mapper.identity=MYSQL

yml 配置文件写法类似。

第一个数据源配置类

新建两个 Druid 配置类,采用手动配置的方式,避免 Druid 监控不到 SQL:

  1import com.alibaba.druid.pool.DruidDataSource;
  2import org.apache.ibatis.session.SqlSessionFactory;
  3import org.mybatis.spring.SqlSessionFactoryBean;
  4import org.springframework.beans.factory.annotation.Qualifier;
  5import org.springframework.beans.factory.annotation.Value;
  6import org.springframework.context.annotation.Bean;
  7import org.springframework.context.annotation.Configuration;
  8import org.springframework.context.annotation.Primary;
  9import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
 10import org.springframework.jdbc.datasource.DataSourceTransactionManager;
 11import tk.mybatis.spring.annotation.MapperScan;
 12
 13import javax.sql.DataSource;
 14import java.sql.SQLException;
 15
 16@Configuration
 17@MapperScan(basePackages = "com.felix.project.mapper", sqlSessionFactoryRef = "masterSqlSessionFactory")
 18public class DruidConfig {
 19
 20    @Value("${spring.datasource.url}")
 21    private String url;
 22    @Value("${spring.datasource.username}")
 23    private String username;
 24    @Value("${spring.datasource.password}")
 25    private String password;
 26    @Value("${spring.datasource.driver-class-name}")
 27    private String driverClassName;
 28    @Value("${spring.datasource.druid.initial-size}")
 29    private int initialSize;
 30    @Value("${spring.datasource.druid.min-idle}")
 31    private int minIdle;
 32    @Value("${spring.datasource.druid.max-active}")
 33    private int maxActive;
 34    @Value("${spring.datasource.druid.max-wait}")
 35    private int maxWait;
 36    @Value("${spring.datasource.druid.time-between-eviction-runs-millis}")
 37    private int timeBetweenEvictionRunsMillis;
 38    @Value("${spring.datasource.druid.min-evictable-idle-time-millis}")
 39    private int minEvictableIdleTimeMillis;
 40    @Value("${spring.datasource.druid.validation-query}")
 41    private String validationQuery;
 42    @Value("${spring.datasource.druid.test-while-idle}")
 43    private boolean testWhileIdle;
 44    @Value("${spring.datasource.druid.test-on-borrow}")
 45    private boolean testOnBorrow;
 46    @Value("${spring.datasource.druid.test-on-return}")
 47    private boolean testOnReturn;
 48    @Value("${spring.datasource.druid.pool-prepared-statements}")
 49    private boolean poolPreparedStatements;
 50    @Value("${spring.datasource.druid.max-pool-prepared-statement-per-connection-size}")
 51    private int maxPoolPreparedStatementPerConnectionSize;
 52    @Value("${spring.datasource.druid.filters}")
 53    private String filters;
 54    @Value("${spring.datasource.druid.aop-patterns}")
 55    private String[] aopPatterns;
 56
 57    /**
 58     * 创建数据源
 59     *
 60     * @return DataSource
 61     */
 62    @Bean(name = "masterDataSource")
 63    @Primary
 64    public DataSource masterDataSource() {
 65        DruidDataSource datasource = new DruidDataSource();
 66        datasource.setUrl(url);
 67        datasource.setUsername(username);
 68        datasource.setPassword(password);
 69        datasource.setDriverClassName(driverClassName);
 70        datasource.setInitialSize(initialSize);
 71        datasource.setMinIdle(minIdle);
 72        datasource.setMaxActive(maxActive);
 73        datasource.setMaxWait(maxWait);
 74        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
 75        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
 76        datasource.setValidationQuery(validationQuery);
 77        datasource.setTestWhileIdle(testWhileIdle);
 78        datasource.setTestOnBorrow(testOnBorrow);
 79        datasource.setTestOnReturn(testOnReturn);
 80        datasource.setPoolPreparedStatements(poolPreparedStatements);
 81        try {
 82            datasource.setFilters(filters);
 83        } catch (SQLException e) {
 84            e.printStackTrace();
 85        }
 86        return datasource;
 87    }
 88
 89    // 创建该数据源的事务管理
 90    @Primary
 91    @Bean(name = "primaryTransactionManager")
 92    public DataSourceTransactionManager primaryTransactionManager() throws SQLException {
 93        return new DataSourceTransactionManager(masterDataSource());
 94    }
 95
 96    // 创建 Mybatis 的连接会话工厂实例
 97    @Primary
 98    @Bean(name = "masterSqlSessionFactory")
 99    public SqlSessionFactory primarySqlSessionFactory(@Qualifier("masterDataSource") DataSource primaryDataSource) throws Exception {
100        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
101        sessionFactory.setDataSource(primaryDataSource);  // 设置数据源 bean
102        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
103                .getResources("classpath:mapper/*.xml"));  // 设置 mapper 文件路径
104        return sessionFactory.getObject();
105    }
106}

第二个数据源配置类

与第一个基本相同,主要区别在于:主数据源需要加 @Primary 注解,第二个数据源换一套 @Value 前缀和 bean 名称,@MapperScan 指向另一个 mapper 包:

  1package com.felix.project.config;
  2
  3import com.alibaba.druid.pool.DruidDataSource;
  4import org.apache.ibatis.session.SqlSessionFactory;
  5import org.mybatis.spring.SqlSessionFactoryBean;
  6import org.springframework.beans.factory.annotation.Qualifier;
  7import org.springframework.beans.factory.annotation.Value;
  8import org.springframework.context.annotation.Bean;
  9import org.springframework.context.annotation.Configuration;
 10import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
 11import org.springframework.jdbc.datasource.DataSourceTransactionManager;
 12import tk.mybatis.spring.annotation.MapperScan;
 13
 14import javax.sql.DataSource;
 15import java.sql.SQLException;
 16
 17@Configuration
 18@MapperScan(basePackages = "com.felix.project.mapper2", sqlSessionFactoryRef = "secondSqlSessionFactory")
 19public class DruidFelicConfig {
 20
 21    @Value("${spring.datasource.felix.url}")
 22    private String url;
 23    @Value("${spring.datasource.felix.username}")
 24    private String username;
 25    @Value("${spring.datasource.felix.password}")
 26    private String password;
 27    @Value("${spring.datasource.felix.driver-class-name}")
 28    private String driverClassName;
 29    @Value("${spring.datasource.felix.druid.initial-size}")
 30    private int initialSize;
 31    @Value("${spring.datasource.felix.druid.min-idle}")
 32    private int minIdle;
 33    @Value("${spring.datasource.felix.druid.max-active}")
 34    private int maxActive;
 35    @Value("${spring.datasource.felix.druid.max-wait}")
 36    private int maxWait;
 37    @Value("${spring.datasource.felix.druid.time-between-eviction-runs-millis}")
 38    private int timeBetweenEvictionRunsMillis;
 39    @Value("${spring.datasource.felix.druid.min-evictable-idle-time-millis}")
 40    private int minEvictableIdleTimeMillis;
 41    @Value("${spring.datasource.felix.druid.validation-query}")
 42    private String validationQuery;
 43    @Value("${spring.datasource.felix.druid.test-while-idle}")
 44    private boolean testWhileIdle;
 45    @Value("${spring.datasource.felix.druid.test-on-borrow}")
 46    private boolean testOnBorrow;
 47    @Value("${spring.datasource.felix.druid.test-on-return}")
 48    private boolean testOnReturn;
 49    @Value("${spring.datasource.felix.druid.pool-prepared-statements}")
 50    private boolean poolPreparedStatements;
 51    @Value("${spring.datasource.felix.druid.max-pool-prepared-statement-per-connection-size}")
 52    private int maxPoolPreparedStatementPerConnectionSize;
 53    @Value("${spring.datasource.felix.druid.filters}")
 54    private String filters;
 55    @Value("${spring.datasource.felix.druid.aop-patterns}")
 56    private String[] aopPatterns;
 57
 58    /**
 59     * DataSource
 60     */
 61    @Bean(name = "secondDataSource")
 62    public DataSource secondDataSource() {
 63        DruidDataSource datasource = new DruidDataSource();
 64        datasource.setUrl(url);
 65        datasource.setUsername(username);
 66        datasource.setPassword(password);
 67        datasource.setDriverClassName(driverClassName);
 68        datasource.setInitialSize(initialSize);
 69        datasource.setMinIdle(minIdle);
 70        datasource.setMaxActive(maxActive);
 71        datasource.setMaxWait(maxWait);
 72        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
 73        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
 74        datasource.setValidationQuery(validationQuery);
 75        datasource.setTestWhileIdle(testWhileIdle);
 76        datasource.setTestOnBorrow(testOnBorrow);
 77        datasource.setTestOnReturn(testOnReturn);
 78        datasource.setPoolPreparedStatements(poolPreparedStatements);
 79        try {
 80            datasource.setFilters(filters);
 81        } catch (SQLException e) {
 82            e.printStackTrace();
 83        }
 84        return datasource;
 85    }
 86
 87    // 创建该数据源的事务管理
 88    @Bean(name = "secondTransactionManager")
 89    public DataSourceTransactionManager secondTransactionManager() throws SQLException {
 90        return new DataSourceTransactionManager(secondDataSource());
 91    }
 92
 93    // 创建 Mybatis 的连接会话工厂实例
 94    @Bean(name = "secondSqlSessionFactory")
 95    public SqlSessionFactory secondSqlSessionFactory(@Qualifier("secondDataSource") DataSource primaryDataSource) throws Exception {
 96        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
 97        sessionFactory.setDataSource(primaryDataSource);  // 设置数据源 bean
 98        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
 99                .getResources("classpath:mapper/*.xml"));  // 设置 mapper 文件路径
100        return sessionFactory.getObject();
101    }
102}

com.felix.project.mapper 包下的 Mapper 走第一个数据源,com.felix.project.mapper2 包下的走第二个数据源,互不影响。

开启 Druid 监控

新建一个 DruidMonitorConfig 配置类,用于开启 Druid 监控页面(默认地址 /druid/):

 1package com.felix.project.config;
 2
 3import com.alibaba.druid.support.http.StatViewServlet;
 4import com.alibaba.druid.support.http.WebStatFilter;
 5import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 6import org.springframework.boot.web.servlet.FilterRegistrationBean;
 7import org.springframework.boot.web.servlet.ServletRegistrationBean;
 8import org.springframework.context.annotation.Bean;
 9import org.springframework.context.annotation.Configuration;
10
11@Configuration
12public class DruidMonitorConfig {
13    @Bean
14    @ConditionalOnMissingBean
15    public ServletRegistrationBean druidServlet() {
16        ServletRegistrationBean servletRegistrationBean =
17                new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
18        // 白名单
19        // servletRegistrationBean.addInitParameter("allow","192.168.6.195");
20        // IP黑名单(存在共同时,deny优先于allow) : 如果满足deny的话提示:Sorry, you are not permitted to view this page.
21        // servletRegistrationBean.addInitParameter("deny","192.168.6.73");
22        // 用于登陆的账号密码
23        servletRegistrationBean.addInitParameter("loginUsername", "admin");
24        servletRegistrationBean.addInitParameter("loginPassword", "admin");
25        // 是否能重置数据
26        servletRegistrationBean.addInitParameter("resetEnable", "false");
27        return servletRegistrationBean;
28    }
29
30    @Bean
31    @ConditionalOnMissingBean
32    public FilterRegistrationBean filterRegistrationBean() {
33        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
34        // 添加过滤规则
35        filterRegistrationBean.addUrlPatterns("/*");
36        // 添加不需要忽略的格式信息
37        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
38        return filterRegistrationBean;
39    }
40}

监控页登录账号密码即上面的 loginUsername / loginPassword,生产环境务必改掉默认值,并结合白名单限制访问来源。

参考