SpringBoot整合c3p0 - automaticTestTable not exist
在SpringBoot2.1.3版本中集成c3p0数据库连接池:
application.properties:
c3p0.jdbcUrl=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=GMT c3p0.user=root c3p0.password=root c3p0.driverClass=com.mysql.jdbc.Driver c3p0.miniPoolSize=1 c3p0.maxPoolSize=20 c3p0.initialPoolSize=1 c3p0.maxIdleTime=25000 c3p0.acquireIncrement=1 c3p0.testConnectionOnCheckin=true c3p0.automaticTestTable=c3p0testtable c3p0.acquireRetryAttempts=30 c3p0.acquireRetryDelay=1000 c3p0.idleConnectionTestPeriod=60 c3p0.checkoutTimeout=3000
@Configuration Java类:
import com.mchange.v2.c3p0.ComboPooledDataSource; 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.context.annotation.Primary; import javax.annotation.Resource; import javax.sql.DataSource; @Configuration public class DataSourceConfig { @Bean(name = "dataSource") @Qualifier(value = "dataSource") @Primary @ConfigurationProperties(prefix = "c3p0") public DataSource dataSource() { return DataSourceBuilder.create().type(com.mchange.v2.c3p0.ComboPooledDataSource.class).build(); } }
集成完毕后启动成功,在我本地执行Sql语句没有问题,但是在同事电脑上执行Sql语句时报如下错误:
Attempted to convert SQLException to SQLException. Leaving it alone. [SQLState: 42S02; errorCode: 1146]
java.sql.SQLSyntaxErrorException: Table 'demo.c3p0testtable' doesn't exist
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:120) ~[mysql-connector-java-8.0.15.jar:8.0.15]
......
原因分析:
c3p0testtable为上述配置:c3p0.automaticTestTable=c3p0testtable
该配置是c3p0用于测试数据库是否连通而创建的table,通过跟踪代码,发现在初始化验证该table是否存在时返回true(存在),故在初始化时没有创建该table而导致后续查询该table导致报错(表格不存在)
初始验证automaticTestTable是否存在:
C3P0PooledConnectionPoolManager
-> createPooledConnectionPool
-> initializeAutomaticTestTable
-> DatabaseMetaData.getTables( null, null, automaticTestTable, new String[] {"TABLE"} );
上述代码中查询数据库元数据DatabaseMetaData.getTables( null, null, automaticTestTable, new String[] {"TABLE"} );
实际测试中查询的为jdbcUrl下的所有数据库,而非jdbcUrl中指定的demo数据库(jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=GMT
)
SpringBoot2.1.3引入的mysql-connector-java为8.0.15,而我们项目之前使用的mysql-connector-java为5.x版本,
8.x版本msyql驱动将参数 nullCatalogMeansCurrent 的默认值由true改为了false,如果你使用DatabaseMetaData.getTables获取表的信息,8.x版本驱动将返回所有库的的所有表,而不仅是当前jdbcUrl中指定的当前数据库。
// 8.x版本默认会返回所有库的表,而不仅是datasource指定的库
databaseMetaData.getTables(null, null, null, new String[]{"TABLE"})
在我本地之所以能成功是因为我显式引入了mysql-connector-java 5.x版本,而同事直接依赖springboot2.1.3中的8.x版本,当在同事电脑上通过上述getTables方法验证c3p0testtable该表是否存在时,查询的是同事本地电脑上的所有数据库,而不是仅仅查询demo数据库,又因为之前同事本地数据库中的其他项目中同样使用c3p0连接池,故在本地其他数据库(非demo库)中已经存在c3p0testtable表,所以在同事电脑上验证该c3p0testtable表是否存在时为true(查询所有库中包含其他库,而其他库中包含c3p0testtable表),导致之后再进行验证查询c3p0testtable表时导致Table 'demo.c3p0testtable' doesn't exist,总的来说就是:验证是否存在的库和最终进行查询的库不一致
解决方案就是在连接池初始化时,将参数nullCatalogMeansCurrent 改为true,
解决方案1:
修该application.properties中jdbcUrl,设置nullCatalogMeansCurrent=true
c3p0.jdbcUrl=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=GMT&
nullCatalogMeansCurrent=true
......
解决方案2:
修该application.properties,添加数据源连接属性nullCatalogMeansCurrent=true
c3p0.jdbcUrl=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=GMT ...... c3p0.properties.nullCatalogMeansCurrent=true
补充了解(hikari连接池属性设置):
spring.datasource.hikari.dataSourceProperties.nullCatalogMeansCurrent=true