[xxx] 整合MyBatis-plus的dynamic多数据源发生Too many connections
Description
我在使用多数据源的情况下,尝试整合MyBatis-plus的多数据源的情况下出现了点问题,是这样的,我们项目中除了使用apijson外,也需要自己进行写一些增删改的接口,而且也要支持多数据源,所以这里我尝试整合MyBatis-plus的多数据源,我初步是整合成功了,通过apijson和自己写的接口,都可进行多数据源的增删改查,但是出现了一些问题,当项目使用一段时间,控制台就会报错:Too many connections(MySQL 错误码 1040)就是数据库连接太多了,比如我通过一直调用apijson的get方法,调用一两百次,就会复现这个问题,我怀疑是没有正常关闭连接的问题,但是我始终找不到问题点在哪里,为了实现两者整合,我重写了DemoSQLExecutor的getConnection方法,以可以从MyBatis-plus的多数据源地方获取DataSource,然后取消了原本的通过DemoDataSourceConfig的@Bean方式注入DruidDataSource的代码,然后修改了一下配置,改动如下 请问Tommy哥,这种情况有办法解决吗? 环境:Java21+SpringBoot3.2.5+MySQL8.0.24+dynamic-datasource4.3.1(MyBatis-plus的多数据源包)
重写的DemoSQLExecutor的getConnection方法 @Override public Connection getConnection(SQLConfig config) throws Exception { String key = config.getDatasource() + "-" + config.getDatabase(); Connection c = connectionMap.get(key); if (c == null || c.isClosed()) {
// 获取 Spring 上下文和 Environment
ApplicationContext ctx = DemoApplication.getApplicationContext();
if (ctx == null) {
throw new IllegalStateException("无法获取 Spring ApplicationContext");
}
Environment env = ctx.getEnvironment();
// 3) 获取要使用的 dataSource 名(前端通过@datasource来指定)
String requested = config.getDatasource();
DataSource ds = null;
Map<String, DataSource> provided = new HashMap<>();
// 如果容器有 DynamicDataSourceProvider bean(starter 情况),直接取出 provider 管理的 map
if (ctx.getBeanNamesForType(DynamicDataSourceProvider.class).length > 0) {
DynamicDataSourceProvider provider = ctx.getBean(DynamicDataSourceProvider.class);
provided = provider.loadDataSources();
ds = provided.get(requested);
}
// 4) 如果没找到,尝试从配置读取默认数据源名(spring.datasource.dynamic.primary)
if (ds == null) {
String primaryName = null;
try {
primaryName = env.getProperty("spring.datasource.dynamic.primary");
} catch (Throwable ignored) {}
if (primaryName != null && !primaryName.isBlank()) {
ds = provided.get(primaryName);
}
}
// 5) 最后兜底:如果 ds 仍然为空且 dsMap 只有一个元素,则取唯一那个
if (ds == null && provided.size() == 1) {
ds = provided.values().iterator().next();
}
// 7) 获取连接并缓存(保留原先的 connectionMap 逻辑)
connectionMap.put(key, ds == null ? null : ds.getConnection());
}
// 保持原来的逻辑:必须最后执行 super.getConnection(config)
return super.getConnection(config);
}
配置: spring: datasource: dynamic: primary: druid # 指定默认数据源名称 datasource: druid: driverClassName: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/office_automation?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8 username: root password: root type: com.alibaba.druid.pool.DruidDataSource druid-test: driverClassName: com.mysql.cj.jdbc.Driver url: jdbc:mysql://14.70.22.777:33336/office_automation?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8 username: ENC(fXGOMv7Vggup2KAbAyHoI6Mzd7lqblJdxi1CSt8CZvAjGHen5rAj6HiwKt5W) password: ENC(n24kDLyLtsv2HJCpwhKzIJycd/UD+NYy88c2haiS7mIUj0S/2Am8sQ==) type: com.alibaba.druid.pool.DruidDataSource
MyBatis-plus的多数据源依赖包的坐标:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<version>4.3.1</version>
</dependency>
DemoDataSourceConfig类中的代码全部注释掉了
报错信息:
我似乎给解决了,我把DemoSQLExecutor的getConnection又改了一下(代码在最后面),相比于原版,我没再使用connectionMap这个东西,直接用的DynamicDataSourceProvider.loadDataSources()里的数据源map,然后让前端传对应数据源配置的名字,然后通过ds = DynamicDataSourceProvider.loadDataSources().get(requested);的方式获取到对应的DataSource 最重要的是,我把super.getConnection方法放在了这个方法的下面,并做了一些修改,仅保留了图里圈出来的部分
但是我总感觉不妥,似乎要修改一下,而不是直接一刀切掉没圈出来的部分
DemoSQLExecutor的getConnection // 适配MyBatis-Plus动态数据源,统一使用数据源连接池管理连接 @Override public Connection getConnection(SQLConfig config) throws Exception { // 获取 Spring 上下文和 Environment ApplicationContext ctx = DemoApplication.getApplicationContext(); if (ctx == null) { throw new IllegalStateException("无法获取 Spring ApplicationContext"); } Environment env = ctx.getEnvironment();
// 获取要使用的 dataSource 名(前端通过@datasource来指定)
String requested = config.getDatasource();
DataSource ds = null;
Map<String, DataSource> provided = new HashMap<>();
// 如果容器有 DynamicDataSourceProvider bean,直接取出 provider 管理的 map
if (ctx.getBeanNamesForType(DynamicDataSourceProvider.class).length > 0) {
DynamicDataSourceProvider provider = ctx.getBean(DynamicDataSourceProvider.class);
provided = provider.loadDataSources();
ds = provided.get(requested);
}
// 如果没找到,尝试从配置读取默认数据源名
if (ds == null) {
String primaryName = null;
try {
primaryName = env.getProperty("spring.datasource.dynamic.primary");
} catch (Throwable ignored) {}
if (primaryName != null && !primaryName.isBlank()) {
ds = provided.get(primaryName);
}
}
// 最后兜底:如果 ds 仍然为空且 dsMap 只有一个元素,则取唯一那个
if (ds == null && provided.size() == 1) {
ds = provided.values().iterator().next();
}
if (ds == null) {
throw new IllegalStateException("无法找到有效的数据源: " + requested +
", 可用数据源: " + provided.keySet());
}
// 直接从数据源获取连接,不缓存 - 让连接池管理连接生命周期
// 这样可以避免连接泄漏,连接池会自动处理连接的创建、复用和关闭
Connection connection = ds.getConnection();
// TDengine 驱动内部事务处理方法都是空实现,手动 commit 无效
int ti = config.isTDengine() ? Connection.TRANSACTION_NONE : getTransactionIsolation();
//java.sql.SQLException: Transaction isolation level NONE not supported by MySQL
if (ti != Connection.TRANSACTION_NONE) {
begin(ti);
}
return connection;
}
@AFatFox 不用 connectionMap 的话,需要重写 close 方法,自己 close connection