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"} );

SpringBoot整合c3p0 - automaticTestTable not exist

上述代码中查询数据库元数据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