典型 Spring + Hibernate + Quartz 架構中,兩大框架各自管理連接池:
hibernate.connection.provider_class (C3P0/HikariCP/DBCP) + SessionFactoryorg.quartz.dataSource.myDS.provider_class (PoolingConnectionProvider/DBCP) + DBConnectionManager弊端:連接總數雙倍、配置分散、監控割裂、事務邊界難以統一。
實現 Quartz ConnectionProvider 介面,內部委派給 Hibernate SessionFactory 的 ConnectionProvider,實現 **物理連接複用、事務上下文共享、配置單一化**。
HibernateConnectionProviderpackage com.demo.quartz;
import org.hibernate.SessionFactory;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.quartz.utils.ConnectionProvider;
import java.sql.Connection;
import java.sql.SQLException;
public class HibernateConnectionProvider implements ConnectionProvider {
private static ThreadLocal threadLocal = new ThreadLocal<>();
@Override
public Connection getConnection() throws SQLException {
// 1. 獲取當前線程綁定的 Hibernate Session (Spring 事務管理)
SessionFactory sessionFactory = (SessionFactory) Global.getSessionFactory(); // 從 Spring 容器獲取
ConnectionProvider hibernateProvider = sessionFactory.getSessionFactoryOptions()
.getServiceRegistry().getService(ConnectionProvider.class);
Connection conn = hibernateProvider.getConnection();
threadLocal.set(conn); // 綁定線程,確保同一事務複用
return conn;
}
@Override
public void shutdown() throws SQLException {
Connection conn = threadLocal.get();
if (conn != null) {
// 注意:不直接關閉,歸還給 Hibernate 連接池
// 實際應由 Spring 事務管理器在事務結束時釋放
threadLocal.remove();
}
}
}
關鍵點:
SessionFactory (可注入或靜態工具類)getConnection() 給 Hibernate 提供者ThreadLocal 確保同一線程 (同一事務) 複用同一連接shutdown() **不關閉連接**,交由 Spring 事務同步管理器統一釋放StdSchedulerFactory 支援自定義 Provider原版 instantiate() 中 //Set up any DataSources 區塊僅支援內建 Provider。需擴展 providerClass 判斷邏輯:
String providerClass = pp.getStringProperty(PROP_DATASOURCE_PROVIDER_CLASS, null);
if (providerClass == null || providerClass.equals(PoolingConnectionProvider.class.getName())) {
// 原有 DBCP 邏輯...
} else if (providerClass.equals(JNDIConnectionProvider.class.getName())) {
// 原有 JNDI 邏輯...
} else if (providerClass.equals(HibernateConnectionProvider.class.getName())) {
// 新增:實例化自定義 Provider
HibernateConnectionProvider cp = new HibernateConnectionProvider();
dbMgr.addConnectionProvider(dsNames[i], cp);
} else {
// 嘗試反射實例化,支援任意自定義 Provider
try {
Class> clazz = Class.forName(providerClass);
ConnectionProvider cp = (ConnectionProvider) clazz.getDeclaredConstructor().newInstance();
dbMgr.addConnectionProvider(dsNames[i], cp);
} catch (Exception e) {
throw new SchedulerException("Provider 實例化失敗: " + providerClass, e);
}
}
建議:提交 PR 給 Quartz 社區支援 SPI 機制,避免修改源碼升級困難。若必須修改,建議繼承 StdSchedulerFactory 覆寫 instantiate()。
quartz.properties# 使用 Hibernate 共享連接池
org.quartz.dataSource.myDS.provider_class = com.demo.quartz.HibernateConnectionProvider
# 以下參數將被忽略 (由 Hibernate 配置決定)
# org.quartz.dataSource.myDS.driver = ...
# org.quartz.dataSource.myDS.URL = ...
# org.quartz.dataSource.myDS.user = ...
# org.quartz.dataSource.myDS.password = ...
# org.quartz.dataSource.myDS.maxConnections = ...
# 調度器使用該數據源
org.quartz.jobStore.dataSource = myDS
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 20000
更優雅的方案是完全交由 Spring 管理,避免修改 Quartz 源碼:
<bean id="schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="dataSource" ref="dataSource"/> <!-- Spring 管理的 DataSource (HikariCP) -->
<property name="transactionManager" ref="transactionManager"/>
<property name="overwriteExistingJobs" value="true"/>
<property name="quartzProperties">
<props>
<prop key="org.quartz.jobStore.class">org.quartz.impl.jdbcjobstore.JobStoreTX</prop>
<prop key="org.quartz.jobStore.tablePrefix">QRTZ_</prop>
<prop key="org.quartz.jobStore.isClustered">true</prop>
</props>
</property>
</bean>
Spring SchedulerFactoryBean 內部自動將 dataSource 包裝為 Quartz 所需 ConnectionProvider,**零代碼修改、完美整合 Spring 事務**。
| 方案 | 優點 | 缺點 | 推薦場景 |
|---|---|---|---|
| 修改 StdSchedulerFactory + 自定義 Provider | 不依賴 Spring、Quartz 獨立運行 | 升級 Quartz 需重複修改、源碼維護成本高 | 非 Spring 環境、舊版 Quartz 1.x/2.x |
| Spring SchedulerFactoryBean (推薦) | 零侵入、自動事務綁定、配置集中、升級無感 | 強依賴 Spring 容器 | Spring/Spring Boot 專案、新專案 |
| JNDI 共享 DataSource | 標準化、容器管理、支援分布式事務 | 需應用伺服器、配置相對複雜 | Java EE/Jakarta EE 環境、WebLogic/WebSphere |
@Transactional 或 TransactionTemplate 包裹 executeInternal。leakDetectionThreshold,監控 Quartz 執行前後連接池狀態。isClustered=true 時,確保所有節點時間同步 (NTP)、資料庫鎖表 QRTZ_LOCKS 正常。initializeSchema 參數)。統一連接池不僅是資源節約,更是 **事務一致性、運維簡潔性、架構內聚性** 的綜合體現。在 Spring 生態中,**SchedulerFactoryBean 是標準答案**,以零侵入方式實現 Quartz 與 Hibernate/JPA 的完美融合。唯有非 Spring 環境或極舊版本 Quartz 才需考慮源碼修改方案,且應封裝為獨立庫隔離變更影響。