最近开发环境总是遇到 Got minus one from a read call 的报错。
排查思路: 首先怀疑可能是连接数达到上限,于是尝试修改数据库最大连接和最小连接。
spring:
datasource: # 数据源的相关配置
dynamic:
primary: master #设置默认的数据源或者数据源组,默认值即为master
strict: false #设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候会抛出异常,不启动则使用默认数据源.
datasource:
master:
type: com.zaxxer.hikari.HikariDataSource # 数据源类型:HikariCP
driver-class-name: oracle.jdbc.OracleDriver
url: jdbc:oracle:thin:@xxxxx:1521/xxxxx
username: xxxxx
password: xxxxx
hikari:
connection-timeout: 30000 # 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQ
minimum-idle: 5 # 最小连接数
maximum-pool-size: 10 # 最大连接数
auto-commit: true # 自动提交
idle-timeout: 600000 # 连接超时的最大时长(毫秒),超时则被释放(retired),默认:10分钟
pool-name: BoxServerApiPool # 连接池名字
max-lifetime: 1800000 # 连接的生命时长(毫秒),超时而且没被使用则被释放(retired),默认:30分钟
connection-test-query: SELECT 1
data-source-properties: #以下属性仅为演示(默认不会引入)
serverTimezone: Asia/Shanghai
但是发现竟然没有起作用,经过排查发现配置的最大连接数和最小连接数都是10.并不是我们配置的5和10,遂开启了bug排查之路。
首先我们使用的数据源是动态数据源,对应的依赖版本:dynamic-datasource-spring-boot-starter-3.3.1。
DynamicDataSourceAutoConfiguration是动态数据源的自动配置类。
@Configuration
@EnableConfigurationProperties({DynamicDataSourceProperties.class})
@AutoConfigureBefore({DataSourceAutoConfiguration.class})
@Import({DruidDynamicDataSourceConfiguration.class, DynamicDataSourceCreatorAutoConfiguration.class})
@ConditionalOnProperty(
prefix = "spring.datasource.dynamic",
name = {"enabled"},
havingValue = "true",
matchIfMissing = true
)
public class DynamicDataSourceAutoConfiguration {
DynamicDataSourceProperties properties;
}
读取前缀为 spring.datasource.dynamic的属性,封装到DynamicDataSourceAutoConfiguration#的成员变量DynamicDataSourceProperties;
@ConfigurationProperties(
prefix = "spring.datasource.dynamic"
)
public class DynamicDataSourceProperties {
}
使用配置文件中的spring.datasource.dynamic.datasource属性构建DynamicDataSourceProvider
@Bean
@ConditionalOnMissingBean
public DynamicDataSourceProvider dynamicDataSourceProvider() {
Map<String, DataSourceProperty> datasourceMap = this.properties.getDatasource();
return new YmlDynamicDataSourceProvider(datasourceMap);
}
com.baomidou.dynamic.datasource.DynamicRoutingDataSource#setProvider
public void setProvider(DynamicDataSourceProvider provider) {
this.provider = provider;
}
com.baomidou.dynamic.datasource.provider.YmlDynamicDataSourceProvider#loadDataSources
public Map<String, DataSource> loadDataSources() {
return this.createDataSourceMap(this.dataSourcePropertiesMap);
}
com.baomidou.dynamic.datasource.provider.AbstractDataSourceProvider#createDataSourceMap
使用配置文件中的配置创建数据源
protected Map<String, DataSource> createDataSourceMap(Map<String, DataSourceProperty> dataSourcePropertiesMap) {
Map<String, DataSource> dataSourceMap = new HashMap(dataSourcePropertiesMap.size() * 2);
Iterator var3 = dataSourcePropertiesMap.entrySet().iterator();
while(var3.hasNext()) {
Entry<String, DataSourceProperty> item = (Entry)var3.next();
DataSourceProperty dataSourceProperty = (DataSourceProperty)item.getValue();
String poolName = dataSourceProperty.getPoolName();
if (poolName == null || "".equals(poolName)) {
poolName = (String)item.getKey();
}
dataSourceProperty.setPoolName(poolName);
//创建数据源放入Map
dataSourceMap.put(poolName, this.defaultDataSourceCreator.createDataSource(dataSourceProperty));
}
return dataSourceMap;
}
@Override
public DataSource createDataSource(DataSourceProperty dataSourceProperty) {
return createDataSource(dataSourceProperty, properties.getPublicKey());
}
根据不同的dataSourceProperty选择不同的DataSourceCreator,然后使用DataSourceCreator创建对应的数据源。
@Override
public DataSource createDataSource(DataSourceProperty dataSourceProperty, String publicKey) {
DataSourceCreator dataSourceCreator = null;
/***
典型的策略模式会使用map存放策略,根据不同的key选择策略。
所有的策略实现了同1个接口,策略方法的出参/入参相同。
此处也是策略模式,spring源码中也出现过类似的用法。
support()方法主要用于判断各个子类是否支持当前类型数据的处理即判断策略是否适配当前场景。
2种策略模式的区别:选择策略的方式不同。
createDataSource()方法主要用于创建具体的dataSource
****/
for (DataSourceCreator creator : this.creators) {
if (creator.support(dataSourceProperty)) {
dataSourceCreator = creator;
break;
}
}
if (dataSourceCreator == null) {
throw new IllegalStateException("creator must not be null,please check the DataSourceCreator");
}
DataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty, publicKey);
this.runScrip(dataSource, dataSourceProperty);
return wrapDataSource(dataSource, dataSourceProperty);
}
support方法
@Override
public boolean support(DataSourceProperty dataSourceProperty) {
Class<? extends DataSource> type = dataSourceProperty.getType();
return (type == null && hikariExists) || (type != null && HIKARI_DATASOURCE.equals(type.getName()));
}
而我们用的是hikari的数据源,对应的creator就是HikariDataSourceCreator。
com.baomidou.dynamic.datasource.creator.HikariDataSourceCreator#createDataSource
```java
public DataSource createDataSource(DataSourceProperty dataSourceProperty, String publicKey) {
if (StringUtils.isEmpty(dataSourceProperty.getPublicKey())) {
dataSourceProperty.setPublicKey(publicKey);
}
//关键代码在这里 会将配置中文件中的配置转换为HikariConfig
HikariConfig config = dataSourceProperty.getHikari().toHikariConfig(this.hikariCpConfig);
config.setUsername(dataSourceProperty.getUsername());
config.setPassword(dataSourceProperty.getPassword());
config.setJdbcUrl(dataSourceProperty.getUrl());
config.setPoolName(dataSourceProperty.getPoolName());
String driverClassName = dataSourceProperty.getDriverClassName();
if (!StringUtils.isEmpty(driverClassName)) {
config.setDriverClassName(driverClassName);
}
//使用HikariConfig构建数据源
return new HikariDataSource(config);
}
知道了原理,去修改配置文件就行了
spring:
datasource: # 数据源的相关配置
dynamic:
primary: master #设置默认的数据源或者数据源组,默认值即为master
strict: false #设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候会抛出异常,不启动则使用默认数据源.
datasource:
master:
type: com.zaxxer.hikari.HikariDataSource # 数据源类型:HikariCP
driver-class-name: oracle.jdbc.OracleDriver
url: jdbc:oracle:thin:@xxxxx:1521/xxxxx
username: xxxxx
password: xxxxx
hikari:
connection-timeout: 30000 # 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQ
minIdle: 5 # 最小连接数
maxPoolSize: 10 # 最大连接数
isAutoCommit: true # 自动提交
idleTimeout: 600000 # 连接超时的最大时长(毫秒),超时则被释放(retired),默认:10分钟
poolName: BoxServerApiPool # 连接池名字
maxLifetime: 1800000 # 连接的生命时长(毫秒),超时而且没被使用则被释放(retired),默认:30分钟
connection-test-query: SELECT 1 FROM DUAL
data-source-properties: #以下属性仅为演示(默认不会引入)
serverTimezone: Asia/Shanghai