Skip to content

[xxx] 整合MyBatis-plus的dynamic多数据源发生Too many connections #847

Open

Description

@AFatFox

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类中的代码全部注释掉了

报错信息:

Image

Activity

  1. AFatFox commented on Sep 28, 2025

    @AFatFox
    Author

    我似乎给解决了,我把DemoSQLExecutor的getConnection又改了一下(代码在最后面),相比于原版,我没再使用connectionMap这个东西,直接用的DynamicDataSourceProvider.loadDataSources()里的数据源map,然后让前端传对应数据源配置的名字,然后通过ds = DynamicDataSourceProvider.loadDataSources().get(requested);的方式获取到对应的DataSource
    最重要的是,我把super.getConnection方法放在了这个方法的下面,并做了一些修改,仅保留了图里圈出来的部分

    但是我总感觉不妥,似乎要修改一下,而不是直接一刀切掉没圈出来的部分

    Image

    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;
    }
    
  2. TommyLemon commented on Oct 2, 2025

    @TommyLemon
    Member

    @AFatFox 不用 connectionMap 的话,需要重写 close 方法,自己 close connection

  3. Xuxin19960702 commented on Jan 4, 2026

    @Xuxin19960702

    @TommyLemon 你好,就这个issue,请问下connectionMap为什么不设计线程安全,批量处理的时候并发可能导致出问题。还是设计之初就是不考虑这种场景(批量处理应该一个SQL解决),场景就类似于:多条记录循环update状态。

  4. TommyLemon commented on Jan 25, 2026

    @TommyLemon
    Member

    @TommyLemon 你好,就这个issue,请问下connectionMap为什么不设计线程安全,批量处理的时候并发可能导致出问题。还是设计之初就是不考虑这种场景(批量处理应该一个SQL解决),场景就类似于:多条记录循环update状态。

    @Xuxin19960702 每个线程都单独创建实例并且不共享,不存在这种多线程并发问题,这个 issue 提到的问题也不是多线程并发导致的

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions