题记: 遇到个使用Spring xml方式配置Hikari,因为一个Bean的命名原因导致爆出 SQLFeatureNotSupportedException,追溯原因过程其实很简单,但是找到问题所在确实浪费不少时间。 2. 先看源码。 由于某些原因,只能使用 Spring XML方式配置 Hikari的 datasource,我自己使用jdk 1.7版本,不过粗看了下适用1.8+的Hikari代码,应该也是存在这个问题的。
参考Hikari官方的参数配置,如果在你的xml里这么配置 Hikari的 datasource
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <bean id ="jdbcTemplate" class ="org.springframework.jdbc.core.JdbcTemplate" > <property name ="dataSource" ref ="dataSource" > </property > </bean > <bean id ="dataSource" class ="com.zaxxer.hikari.HikariDataSource" destroy-method ="close" > <property name ="driverClassName" value ="com.mysql.jdbc.Driver" /> <property name ="jdbcUrl" value ="${jdbc.url}" /> <property name ="username" value ="${jdbc.username}" /> <property name ="password" value ="${jdbc.password}" /> <property name ="maximumPoolSize" value ="20" /> <property name ="minimumIdle" value ="2" /> <property name ="connectionTestQuery" value ="select 1 " /> <property name ="dataSourceProperties" > <props > <prop key ="cachePrepStmts" > true</prop > <prop key ="prepStmtCacheSize" > 250</prop > <prop key ="prepStmtCacheSqlLimit" > 2048</prop > <prop key ="useServerPrepStmts" > true</prop > </props > </property > </bean >
那么,当你的程序运行到到从该dataSource取connection时候,很可能会遇到下面这样的异常:
1 2 3 4 5 6 7 8 9 10 11 12 Caused by: java.sql.SQLFeatureNotSupportedException at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:119) at com.zaxxer.hikari.pool.PoolBase.newConnection(PoolBase.java:341) at com.zaxxer.hikari.pool.PoolBase.newPoolEntry(PoolBase.java:193) at com.zaxxer.hikari.pool.HikariPool.createPoolEntry(HikariPool.java:428) at com.zaxxer.hikari.pool.HikariPool.checkFailFast(HikariPool.java:499) at com.zaxxer.hikari.pool.HikariPool.<init>(HikariPool.java:112) at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:97) at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:111) at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:77) ... 7 more
即这个dataSource不能使用。 我先说一下解决办法: 这个异常其实是 id=”dataSource”/ref=”dataSource” 导致的,如果改一下名字,任何非 dataSource的名字,就会神奇的发现,程序正常运行了。 3. 原因呢? 看 HikariDataSource.java
1 2 3 4 5 6 @Override public Connection getConnection (String username, String password) throws SQLException{ throw new SQLFeatureNotSupportedException (); }
发现这里并没有错,的的确确该抛异常了,所以,正常的 getConnection()不是在这个类/方法里。 实际上,正常是在 Hikari的 DriverDataSource 这个类里
1 2 3 4 5 6 7 8 9 10 11 @Override public Connection getConnection () throws SQLException{ return driver.connect(jdbcUrl, driverProperties); } @Override public Connection getConnection (String username, String password) throws SQLException{ return getConnection(); }
那么是什么导致 DataSource使用了错误的实现类? 从HikariDataSource构造函数入口,到 getConnection 看起,追踪到 -> HikariPool(this) -> PoolBase(final HikariConfig config) -> initializeDataSource()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 private void initializeDataSource () { final String jdbcUrl = config.getJdbcUrl(); final String username = config.getUsername(); final String password = config.getPassword(); final String dsClassName = config.getDataSourceClassName(); final String driverClassName = config.getDriverClassName(); final Properties dataSourceProperties = config.getDataSourceProperties(); DataSource dataSource = config.getDataSource(); if (dsClassName != null && dataSource == null ) { dataSource = createInstance(dsClassName, DataSource.class); PropertyElf.setTargetFromProperties(dataSource, dataSourceProperties); } else if (jdbcUrl != null && dataSource == null ) { dataSource = new DriverDataSource (jdbcUrl, driverClassName, dataSourceProperties, username, password); } if (dataSource != null ) { setLoginTimeout(dataSource); createNetworkTimeoutExecutor(dataSource, dsClassName, jdbcUrl); } this .dataSource = dataSource; }
这里似乎看起来正常,但是,如果我们仔细看 DataSource dataSource = config.getDataSource(),也就是说 HikariConfig 其实有 get/setDataSource属性 再看看我们xml里定义的bean,bean id=”dataSource” class=”com.zaxxer.hikari.HikariDataSource”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class HikariDataSource extends HikariConfig implements DataSource , Closeable{ .... } public class HikariConfig implements HikariConfigMXBean { ... private DataSource dataSource; public void setDataSource (DataSource dataSource) { this .dataSource = dataSource; } ... }
发现没有,HikariDataSource 其实还有一个 setDataSource 属性,即我们xml里定义的 id=”dataSource” 其实还是会将自己注入给自己的! 这也就是为什么上文中 initializeDataSource 方法里,最终实例化的不是 DriverDataSource 而是自己。
所以,改一个名字就可以了。
关于 SpringBoot 这令我好奇,默认支持使用Hikari作为datasource的SpringBoot是怎么实例化HikariDataSource?会有上述问题吗? 这部分代码在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration @ConditionalOnClass(HikariDataSource.class) @ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource", matchIfMissing = true) static class Hikari extends DataSourceConfiguration { @Bean @ConfigurationProperties(prefix = "spring.datasource.hikari") public HikariDataSource dataSource (DataSourceProperties properties) { return createDataSource(properties, HikariDataSource.class); } } org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration protected <T> T createDataSource (DataSourceProperties properties, Class<? extends DataSource> type) { return (T) properties.initializeDataSourceBuilder().type(type).build(); } ...
不深入了,看下去,反射生成,并且未设置datasource属性,即实际是没有这个问题的。