一、添加依赖
<!-- 动态数据源-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.200</version>
<scope>runtime</scope>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>
二、yml添加配置
#数据源配置
spring:
datasource:
master:
jdbcUrl: jdbc:mysql://192.168.xx.xx:3306/bi?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
second:
jdbcUrl: jdbc:mysql://192.168.xx.xx:3306/duty_prod?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
username: root
password: 123456
driverClassName: com.mysql.cj.jdbc.Driver
druid:
#连接池初始化时创建的数据库连接数量
initial-size: 10
#连接池中保持最小的空闲连接数量。如果空闲连接数量少于这个值,连接池会创建新的连接来补充
min-idle: 5
#
max-active: 80
#配置获取链接等待超时的时间
max-wait: 10
type: com.alibaba.druid.pool.DruidDataSource
mvc:
pathmatch:
matching-strategy: ant_path_matcher
jackson:
time-zone: GMT+8
Url中的mysql服务器地址需要根据你自己的改动。
三、添加配置类
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.hdx.biDataApi.utils.CommonCast;
import org.apache.ibatis.session.SqlSessionFactory;
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;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DataBaseConfig {
@Bean("primary")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
@Bean("secondary")
@ConfigurationProperties(prefix = "spring.datasource.second")
public DataSource dataSource2() {
return DataSourceBuilder.create().build();
}
@Bean
public DataSource dynamicDataSource() {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(CommonCast.PRIMARY, dataSource());
targetDataSources.put(CommonCast.SECONDARY, dataSource2());
DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setTargetDataSources(targetDataSources);
dataSource.setDefaultTargetDataSource(dataSource());
return dataSource;
}
@Bean("SqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(DataSource dynamicDataSource) throws Exception {
MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
sessionFactory.setDataSource(dynamicDataSource);
MybatisConfiguration mybatisConfiguration = new MybatisConfiguration();
sessionFactory.setConfiguration(mybatisConfiguration);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mybatis/**/*.xml"));
return sessionFactory.getObject();
}
}
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.util.Utils;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.*;
import java.io.IOException;
/**
* @Description: DruidConfig配置类
*/
@Configuration
@AutoConfigureAfter(DruidDataSourceAutoConfigure.class)
public class DruidConfig {
/**
* 带有广告的common.js全路径,druid-1.1.14
*/
private static final String FILE_PATH = "support/http/resources/js/common.js";
/**
* 原始脚本,触发构建广告的语句
*/
private static final String ORIGIN_JS = "this.buildFooter();";
/**
* 替换后的脚本
*/
private static final String NEW_JS = "//this.buildFooter();";
/**
* 去除Druid监控页面的广告
*
* @param properties DruidStatProperties属性集合
* @return {@link FilterRegistrationBean}
*/
@Bean
@ConditionalOnWebApplication
@ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled", havingValue = "true")
public FilterRegistrationBean<RemoveAdFilter> removeDruidAdFilter(
DruidStatProperties properties) throws IOException {
// 获取web监控页面的参数
DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
// 提取common.js的配置路径
String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
String commonJsPattern = pattern.replaceAll("\*", "js/common.js");
// 获取common.js
String text = Utils.readFromResource(FILE_PATH);
// 屏蔽 this.buildFooter(); 不构建广告
final String newJs = text.replace(ORIGIN_JS, NEW_JS);
FilterRegistrationBean<RemoveAdFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new RemoveAdFilter(newJs));
registration.addUrlPatterns(commonJsPattern);
return registration;
}
/**
* 删除druid的广告过滤器
*
* @author BBF
*/
private class RemoveAdFilter implements Filter {
private final String newJs;
public RemoveAdFilter(String newJs) {
this.newJs = newJs;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(request, response);
// 重置缓冲区,响应头不会被重置
response.resetBuffer();
response.getWriter().write(newJs);
}
}
}
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceHolder.getDataSource();
}
}
public class DataSourceHolder {
/**
* 线程ThreadLocal
*/
private static final ThreadLocal<String> dataSources = new InheritableThreadLocal();
/**
* 设置数据源
* @param datasource
*/
public static void setDataSource(String datasource) {
dataSources.set(datasource);
}
/**
* 获取数据源
* @return
*/
public static String getDataSource() {
return dataSources.get();
}
/**
* 清除数据源
*/
public static void clearDataSource() {
dataSources.remove();
}
}
需要添加以上四个配置类
四、使用方法
在ServiceImpl实现类里,查询方法中添加
调用主数据库:
DataSourceHolder.setDataSource("primary");
调用第二个数据库:
DataSourceHolder.setDataSource("secondary");
调用主数据库可以不用特定写,即便不屑也会默认调用主数据库
public List<String> getStationsId(String generation_type) {
//使用主数据库
DataSourceHolder.setDataSource("primary");
List<String> test1 = testService.selectStationsId(id);
return test1;
}
public List<String> findDefectData2(List<String> stationsId) {
//使用第二个数据库
DataSourceHolder.setDataSource("secondary");
List<testEntity> defectEntities = testMapper.selectList(new QueryWrapper<testEntity>()
.in("data_id", stationsId)
);
以上就是多数据源查询数据方法,yml配置中可以添加多个数据源,不止两个,但同样配置类中也需要对应的添加配置。
-------------------------------------------------更新----------------------------------------------------------
长时间使用多数据查询会出现高并发的问题
例如:
在使用master数据源查询时,查询还未结束,这个时候开始查询secondary数据源查询时,会出现副数据源库里的表使用了主数据源查询。
master数据源: database1库 里面有 table1,table2 secondary数据源: database2库 里面有 table3,table4
查询database1.table1 时候(高频查询)若此过程查询 secondary.table3,有概率会因为开启了database1的查询,还没释放该数据源的连接。就会报错,提示:database1.table3 doesn't exist。
这个时候需要加个mybatis全局拦截器,作用是查询结束后立马释放数据源,从而避免查询database1时候,查询完成了,但是没有释放数据源时,开始查询database2的情况。
拦截器:
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.sql.Connection;
@Component
@Intercepts({@Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class}
), @Signature(
type = StatementHandler.class,
method = "getBoundSql",
args = {}
), @Signature(
type = Executor.class,
method = "update",
args = {MappedStatement.class, Object.class}
), @Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
), @Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
)})
public class MyInterceptor extends MybatisPlusInterceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
try {
return invocation.proceed();
} finally {
DataSourceHolder.clearDataSource();
}
}
}
-------------------------------------------------再次更新(2024.07.12)------------------------------------
使用拦截器后,查询操作是成功的,但如果使用插入或者更新操作,如savebatch(),就会失败,插入一条后拦截器就会清空数据源,会导致插入数据失败。这个时候需要修改拦截器,改成以下这样
@Override
public Object intercept(Invocation invocation) throws Throwable {
try {
return invocation.proceed();
}
}
然后 每次数据库交换数据操作后进行手动数据源清除。例如:
DataSourceHolder.setDataSource(CommonCast.PRIMARY);
List<Entity> entities = entityMapper.selectList(new QueryWrapper<Entity>());
DataSourceHolder.clearDataSource();
return entities;
这样既可以使用多数据源,也可以进行更新或者插入操作。
以上方法若有不对的地方,欢迎指正😀