有时一个web项目中需要同时访问两个数据库,比如读写分离、分库、主从库分离等,这时,常规的springboot单数据源就无法满足需求,需要对配置进行调整。本文记录了实际工作中配置双数据源时遇到的一些问题和解决思路。
觉得不错的同学可以加我公众号,会经常分享一些技术干货,以及热点AI和科技新闻
项目框架:springboot + mybatis-plus
需求:需要对一些数据做大量的计算和统计,定时任务触发,统计比较耗时
我项目中用的Druid连接池,需要单独引入依赖
Hikari是springboot2.X以后的默认数据源连接池,不需要引入任何依赖。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<mybatisplus-boot.version>3.4.2</mybatisplus-boot.version>
<druid-boot.version>1.2.5</druid-boot.version>
<mybaits.version>3.5.7</mybaits.version>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatisplus-boot.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid-boot.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybaits.version}</version>
</dependency>
数据库连接池可能是Hikari或者Druid,下面给出两种不同的配置方式示例:
Hikari:
spring:
datasource:
db01:
jdbc-url: jdbc:dm://192.168.124.221:5236
username: panda
password: 12345678
driver-class-name: dm.jdbc.driver.DmDriver
type: com.zaxxer.hikari.HikariDataSource
#最大连接数,小于等于0会被重置为默认值10;大于零小于1会被重置为minimum-idle的值
maximum-pool-size: 50
#最小空闲连接,默认值 10,小于0或大于maximum-pool-size,都会重置为maximum-pool-size
minimum-idle: 20
#连接超时时间:毫秒,小于250毫秒,否则被重置为默认值30秒
connection-timeout: 60000
#空闲连接超时时间,默认值600000ms(10分钟),大于等于max-lifetime且max-lifetime>0,会被重置为0;
#不等于0且小于10秒,会被重置为10秒。
#只有空闲连接数大于最大连接数且空闲时间超过该值,才会被释放(自动释放过期链接)
idle-timeout: 600000
#连接最大存活时间.不等于0且小于30秒,会被重置为默认值30分钟.设置应该比mysql设置的超时时间短
max-lifetime: 640000
#连接测试查询
connection-test-query: SELECT 1 from dual
db02:
jdbc-url: jdbc:mysql://192.168.124.221:3306/panda?useSSl=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: user01
password: 12345678
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
maximum-pool-size: 10
minimum-idle: 3
connection-timeout: 60000
idle-timeout: 600000
max-lifetime: 640000
connection-test-query: SELECT 1 from dual
Druid:
spring:
datasource:
db01:
url: jdbc:dm://192.168.124.221:5236
username: panda
password: 12345678
driver-class-name: dm.jdbc.driver.DmDriver
type: com.alibaba.druid.pool.DruidDataSource
initial-size: 5 # 初始化大小
min-idle: 5 # 最小
max-active: 100 # 最大
max-wait: 60000 # 配置获取连接等待超时的时间
validation-query: select 1 from dual
time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
min-evictable-idle-time-millis: 300000 # 指定一个空闲连接最少空闲多久后可被清除,单位是毫秒
filters: config,stat # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.slowSqlMillis=200;druid.stat.logSlowSql=true;config.decrypt=false
test-while-idle: true
test-on-borrow: true
test-on-return: false
pool-prepared-statements: true # 是否缓存preparedStatement,也就是PSCache 官方建议MySQL下建议关闭 个人建议如果想用SQL防火墙 建议打开
max-pool-prepared-statement-per-connection-size: 20
db02:
url: jdbc:mysql://192.168.124.221:3306/panda?useSSl=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: user01
password: 12345678
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
initial-size: 5
min-idle: 5
max-active: 100
max-wait: 60000
validation-query: select 1 from dual
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
filters: config,stat,wall
connectionProperties: druid.stat.slowSqlMillis=200;druid.stat.logSlowSql=true;config.decrypt=false
test-while-idle: true
test-on-borrow: true
test-on-return: false
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
druid:
web-stat-filter:
enabled: true
url-pattern: /*
exclusions: /druid/*,*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico
session-stat-enable: true
session-stat-max-count: 10
stat-view-servlet:
enabled: true
url-pattern: /druid/*
reset-enable: true
login-username: admin
login-password: password
allow: 192.168.18.5,192.168.18.9
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
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 org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
@Configuration
@MapperScan(basePackages = "mapper.db01", sqlSessionFactoryRef = "db01SqlSessionFactory")
public class DataSourceConfigDB01 {
@Primary
@Bean("db01DataSource")
@ConfigurationProperties(prefix = "spring.datasource.db01")
public DataSource getDB01DataSource(){
//使用Druid连接池时,要用专门的DataSourceBuilder:
return DruidDataSourceBuilder.create().build();
//使用默认的Hikari连接池时,用默认的DataSourceBuilder:
return DataSourceBuilder.create().build();
// 这里也可以使用以下方式自己注入属性
DruidDataSource datasource = new DruidDataSource();
datasource.setUrl(url);
datasource.setUsername(username);
return datasource;
}
@Primary
@Bean("db01SqlSessionFactory")
public SqlSessionFactory db01SqlSessionFactory(@Qualifier("db01DataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/db01/*.xml"));
return bean.getObject();
}
@Primary
@Bean("db01SqlSessionTemplate")
public SqlSessionTemplate db01SqlSessionTemplate(@Qualifier("db01SqlSessionFactory") SqlSessionFactory sqlSessionFactory){
return new SqlSessionTemplate(sqlSessionFactory);
}
}
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
@Configuration
@MapperScan(basePackages = "mapper.db02", sqlSessionFactoryRef = "db02SqlSessionFactory")
public class DataSourceConfigDB02 {
@Bean("db02DataSource")
@ConfigurationProperties(prefix = "spring.datasource.db02")
public DataSource getDB02DataSource(){
//使用Druid连接池时,要用专门的DataSourceBuilder:
return DruidDataSourceBuilder.create().build();
//使用默认的Hikari连接池时,用默认的DataSourceBuilder:
return DataSourceBuilder.create().build();
// 这里也可以使用以下方式自己注入属性
DruidDataSource datasource = new DruidDataSource();
datasource.setUrl(url);
datasource.setUsername(username);
return datasource;
、、、
}
@Bean("db02SqlSessionFactory")
public SqlSessionFactory db02SqlSessionFactory(@Qualifier("db02DataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/db02/*.xml"));
return bean.getObject();
}
@Bean("db02SqlSessionTemplate")
public SqlSessionTemplate db02SqlSessionTemplate(@Qualifier("db02SqlSessionFactory") SqlSessionFactory sqlSessionFactory){
return new SqlSessionTemplate(sqlSessionFactory);
}
}
mapper、service、controller 的使用和但数据源没有区别
//在配置SqlSessionFactory时需要注意
//如果使用的是Mybatis-plus
MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
//如果使用的是Mybatis
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
@Primary注解可以标注出有多个候选者有资格自动装配单值依赖项时,优先考虑的Bean对象,如果没有或者标了多个都会出现错误
这两个扫描路径要根据自己实际情况更换
@MapperScan(basePackages = {"com.mapper.master"})
sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:**/*.xml"));
希望这篇文章能帮助到大家