Spring整合Hikari时ProxyDataSource.getConnection抛出NPE异常
问题现象
将数据库连接的XML配置转为Spring Java Bean配置后,应用上下文启动后执行数据库操作抛出空指针异常,异常栈如下:
java.lang.NullPointerException: null at net.ttddyy.dsproxy.support.ProxyDataSource.getConnection(ProxyDataSource.java:63) at org.springframework.jdbc.datasource.DataSourceUtils.fetchConnection(DataSourceUtils.java:158) at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:116) at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:79) at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:371) at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:418)
调试时未发现connection实例为null,相关配置代码如下:
配置类基础定义
@Configuration @Order(1) @Primary public class OLTPHikariDatasourceConfig { private static final String NATIVE = "native";
HikariConfig Bean定义
@Bean(name = "hikariConfigDataSource") public HikariConfig dataSource(@Value("${jdbc.url}") String jdbcUrl, @Value("${jdbc.username}") String userName, @Value("${jdbc.password}") String jdbcPassword, @Value("${jdbc.connection.type:native}") String jdbcConnectionType, @Value("${oltp.auto.commit:true}") boolean autoCommit, @Value("${c3p0.connections.min}") int minIdle, @Value("${c3p0.connections.max}") int maxIdle ){ HikariConfig hikariConfig = new HikariConfig(); hikariConfig.setDriverClassName("org.postgresql.Driver"); hikariConfig.setJdbcUrl(jdbcUrl); hikariConfig.setUsername(userName); if(NATIVE.equals(jdbcConnectionType)) { hikariConfig.setPassword(jdbcPassword); } else { } hikariConfig.setPoolName("OLTP"); hikariConfig.setMaximumPoolSize(maxIdle); hikariConfig.setMinimumIdle(minIdle); hikariConfig.setRegisterMbeans(true); hikariConfig.setAutoCommit(autoCommit); hikariConfig.setDataSourceProperties(getDataSourceProperties()); HikariDataSource dataSource = new HikariDataSource(hikariConfig); return dataSource; } private Properties getDataSourceProperties(){ Properties properties = new Properties(); properties.setProperty("characterEncoding", "UTF-8"); properties.setProperty("useUnicode", "yes"); properties.setProperty("rewriteBatchedStatements", "true"); return properties; }
HikariDataSource Bean定义
@Primary @Bean(name = "dataSourceBase", destroyMethod = "close") public HikariDataSource hikarDataSource(HikariConfig hikariConfigDataSource){ HikariDataSource hikariDataSource = new HikariDataSource(hikariConfigDataSource); return hikariDataSource; }
监听器相关Bean定义
@Bean(name = "dbAccessListener") public DBAccessListener DBAccessListener(DatoFeatureFlagService datoFeatureFlagService) { DBAccessListener dbAccessListener = new DBAccessListener(datoFeatureFlagService); return dbAccessListener; } @Bean(name = "queryListener") public ChainListener queryListener(DBAccessListener dbAccessListener) { ChainListener queryListener = new ChainListener(); queryListener.addListener(dbAccessListener); return queryListener; }
数据源代理相关Bean定义
@Bean(name = "proxyConfigSupport") public ProxyConfigSpringXmlSupport proxyDataSourceSupport(ChainListener queryListener) { ProxyConfigSpringXmlSupport proxyConfigSpringXmlSupport = new ProxyConfigSpringXmlSupport(); proxyConfigSpringXmlSupport.setDataSourceName("dataSourceDefaultName"); proxyConfigSpringXmlSupport.setQueryListener(queryListener); return proxyConfigSpringXmlSupport; } @Bean(name = "proxyConfig") public ProxyConfig proxyConfig(ProxyConfigSpringXmlSupport proxyConfigSpringXmlSupport) { return proxyConfigSpringXmlSupport.create(); } @Bean(name = "dataSource") public ProxyDataSource proxyDataSource(HikariDataSource hikariDataSource, ProxyConfig proxyConfig){ ProxyDataSource proxyDataSource = new ProxyDataSource(); proxyDataSource.setDataSource(hikariDataSource); proxyDataSource.setProxyConfig(proxyConfig); return proxyDataSource; } @Bean(name = "jdbcTemplate") public JdbcTemplate proxyDataSource(ProxyDataSource proxyDataSource){ JdbcTemplate jdbcTemplate = new JdbcTemplate(); jdbcTemplate.setDataSource(proxyDataSource); return jdbcTemplate; } }
调试截图

问题根因及修复方案
空指针触发点在datasource-proxy的ProxyDataSource.getConnection()方法第63行,该行逻辑是从代理配置中获取拦截器链生成代理连接,拦截器链实例为null导致NPE,由3个配置错误共同引发:
- HikariConfig Bean逻辑错误
hikariConfigDataSource方法声明返回HikariConfig类型,但方法内部直接创建了HikariDataSource实例返回,导致后续创建主数据源时,传入的是已经初始化完成的数据源实例而非配置对象,引发配置拷贝异常,同时额外创建了一个未被管理的数据源连接池浪费资源。
修复方式:删除方法内new HikariDataSource(hikariConfig)的逻辑,配置完HikariConfig参数后直接返回该配置实例。 - @Configuration类内Bean方法重名引发CGLIB代理异常
dataSource和jdbcTemplate两个Bean对应的方法名都叫proxyDataSource(重载方法),Spring对@Configuration类做CGLIB代理时会出现方法拦截逻辑错乱,导致ProxyDataSource实例创建时ProxyConfig属性注入失败,拦截器链未初始化。
修复方式:将jdbcTemplate对应的Bean方法重命名为jdbcTemplate,避免和ProxyDataSource的创建方法重名。 - @Primary注解位置错误
@Primary注解直接标注在配置类上,会导致该配置类下所有Bean都被标记为Primary,容易出现同类型Bean注入冲突。
修复方式:删除类上的@Primary注解,仅在主数据源dataSourceBase的@Bean方法上保留该注解即可。
额外优化点:
- 当前配置使用PostgreSQL驱动,但
getDataSourceProperties()中设置的characterEncoding、useUnicode、rewriteBatchedStatements均为MySQL专属参数,建议删除避免驱动警告。 - 非native连接模式的分支未设置数据库密码,需要补全对应逻辑,否则会出现数据库认证失败。
修复后的核心代码示例:
@Configuration @Order(1) public class OLTPHikariDatasourceConfig { private static final String NATIVE = "native"; @Bean(name = "hikariConfigDataSource") public HikariConfig dataSource(@Value("${jdbc.url}") String jdbcUrl, @Value("${jdbc.username}") String userName, @Value("${jdbc.password}") String jdbcPassword, @Value("${jdbc.connection.type:native}") String jdbcConnectionType, @Value("${oltp.auto.commit:true}") boolean autoCommit, @Value("${c3p0.connections.min}") int minIdle, @Value("${c3p0.connections.max}") int maxIdle ){ HikariConfig hikariConfig = new HikariConfig(); hikariConfig.setDriverClassName("org.postgresql.Driver"); hikariConfig.setJdbcUrl(jdbcUrl); hikariConfig.setUsername(userName); if(NATIVE.equals(jdbcConnectionType)) { hikariConfig.setPassword(jdbcPassword); } else { // 补全非native模式的密码设置逻辑 } hikariConfig.setPoolName("OLTP"); hikariConfig.setMaximumPoolSize(maxIdle); hikariConfig.setMinimumIdle(minIdle); hikariConfig.setRegisterMbeans(true); hikariConfig.setAutoCommit(autoCommit); return hikariConfig; } @Primary @Bean(name = "dataSourceBase", destroyMethod = "close") public HikariDataSource hikariDataSource(HikariConfig hikariConfigDataSource){ return new HikariDataSource(hikariConfigDataSource); } // 其他监听器、ProxyConfig相关Bean代码保持不变 @Bean(name = "dataSource") public ProxyDataSource proxyDataSource(HikariDataSource hikariDataSource, ProxyConfig proxyConfig){ ProxyDataSource proxyDataSource = new ProxyDataSource(); proxyDataSource.setDataSource(hikariDataSource); proxyDataSource.setProxyConfig(proxyConfig); return proxyDataSource; } // 方法重命名,避免重名 @Bean(name = "jdbcTemplate") public JdbcTemplate jdbcTemplate(ProxyDataSource proxyDataSource){ JdbcTemplate jdbcTemplate = new JdbcTemplate(); jdbcTemplate.setDataSource(proxyDataSource); return jdbcTemplate; } }
内容的提问来源于stack exchange,提问作者Hard Worker
相关产品推荐
相关产品推荐

