在遷移老系統至 Hibernate 時,常遇到雙連接池並存問題:業務系統已有成熟的連接池(如 DBCP、Druid、自研池),而 Hibernate 配置文件中又需單獨配置數據源。這導致連接參數分散維護、資源重複佔用、事務管理混亂等問題。Hibernate 提供 ConnectionProvider SPI 接口,允許開發者注入現有連接池,實現無縫整合。
Hibernate 內部通過 org.hibernate.connection.ConnectionProvider 介面獲取數據庫連接。默認實現包括 C3P0ConnectionProvider、ProxoolConnectionProvider 等。自定義實現只需實現該介面,並委託給現有連接池管理器即可。
package com.shine.sourcedesk.jbpm;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import org.hibernate.HibernateException;
import org.hibernate.connection.ConnectionProvider;
import com.shine.framework.jdbc.ConnectionManager; // 現有連接池管理器
public class NmsConnectionProvider implements ConnectionProvider {
@Override
public void configure(Properties props) throws HibernateException {
// 可讀取 Hibernate 配置傳遞的參數,如需初始化連接池可在此處理
}
@Override
public Connection getConnection() throws SQLException {
// 委託給現有連接池獲取連接
return ConnectionManager.getConnection();
}
@Override
public void closeConnection(Connection conn) throws SQLException {
// 歸還連接到現有連接池
ConnectionManager.close(conn);
}
@Override
public void close() throws HibernateException {
// SessionFactory 關閉時調用,可選擇是否關閉底層連接池
// 注意:若連接池由容器管理,此處不應關閉
}
@Override
public boolean supportsAggressiveRelease() {
// 返回 false 表示不積極釋放連接,由外部連接池管理生命週期
return false;
}
@Override
public boolean isUnwrappableAs(Class<?> unwrapType) {
return false;
}
@Override
public <T> T unwrap(Class<T> unwrapType) {
return null;
}
}
<!-- 移除原有數據源配置 -->
<!-- <property name="connection.driver_class">...</property> -->
<!-- <property name="connection.url">...</property> -->
<!-- <property name="connection.username">...</property> -->
<!-- <property name="connection.password">...</property> -->
<!-- 指定自定義 ConnectionProvider -->
<property name="hibernate.connection.provider_class">
com.shine.sourcedesk.jbpm.NmsConnectionProvider
</property>
package com.shine.framework.jdbc;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSource; // 以 Druid 為例
public class ConnectionManager {
private static DataSource dataSource;
static {
DruidDataSource ds = new DruidDataSource();
ds.setUrl("jdbc:mysql://localhost:3306/nms_db");
ds.setUsername("nms_user");
ds.setPassword("nms_pass");
ds.setInitialSize(10);
ds.setMaxActive(100);
ds.setMinIdle(5);
ds.setMaxWait(60000);
ds.setValidationQuery("SELECT 1");
ds.setTestOnBorrow(true);
dataSource = ds;
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public static void close(Connection conn) {
if (conn != null) {
try { conn.close(); } catch (SQLException e) { /* log */ }
}
}
public static void shutdown() {
if (dataSource instanceof DruidDataSource) {
((DruidDataSource) dataSource).close();
}
}
}
若連接池由 Spring 管理 (如 DataSource Bean),可通過 JNDI 或直接注入方式獲取:
public class SpringJndiConnectionProvider implements ConnectionProvider {
private DataSource dataSource;
@Override
public void configure(Properties props) {
try {
InitialContext ctx = new InitialContext();
dataSource = (DataSource) ctx.lookup("java:comp/env/jdbc/MyDataSource");
} catch (NamingException e) {
throw new HibernateException("JNDI lookup failed", e);
}
}
@Override
public Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
// 其他方法同上...
}
public class SpringBeanConnectionProvider implements ConnectionProvider {
private static DataSource dataSource; // 由 Spring 設置
public static void setDataSource(DataSource ds) {
dataSource = ds;
}
@Override
public Connection getConnection() throws SQLException {
if (dataSource == null)
throw new IllegalStateException("DataSource not injected");
return dataSource.getConnection();
}
// 其他方法同上...
}
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 初始化時注入到 Provider -->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="com.xxx.SpringBeanConnectionProvider.setDataSource"/>
<property name="arguments"><ref bean="dataSource"/></property>
</bean>
整合後,Hibernate 使用的連接來自同一連接池,配合 Spring 的 HibernateTransactionManager 或 DataSourceTransactionManager 可實現統一事務控制:
<bean id="transactionManager"
class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- 或使用 DataSourceTransactionManager 管理原生 JDBC 與 Hibernate 混合事務 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
| 問題 | 原因 | 解決方案 |
|---|---|---|
| 連接洩漏 | closeConnection 未正確歸還 | 確保調用連接池的 close/return 方法,而非物理關閉 |
| 事務不生效 | 連接非同一事務上下文 | 使用同一 TransactionManager,確保連接綁定線程 |
| 並發性能下降 | 連接池參數未調優 | 根據業務 QPS 設置 maxActive、minIdle、maxWait |
| 序列化異常 | 連接對象跨 JVM 傳遞 | 避免在分布式環境直接傳遞 Connection 對象 |
通過實現 ConnectionProvider 接口,Hibernate 可零侵入式接入現有連接池體系,實現資源統一管理、配置集中維護、事務邊界清晰。此方案特別適合漸進式遷移、微服務改造、混合持久化技術棧等場景。