有任务调度需求的人对Quartz应该不会陌生,目前,Quartz的最新版本是2.2.0,该文章也是基于2.2.0版本。为了保证任务调度系统的HA,对于JobStore我采用了数据库存储的方式。系统出现故障时,系统中正在执行的任务会在数据库中保存,并在系统恢复正常后,Quartz根据misfire的处理策略对任务进行重新调度。
Quartz2.2.0默认使用的数据库连接池为c3p0。在OSGI环境中,使用该连接池时,会出现连不上数据库的错误。为了解决该问题:主要有两种思路,(1)查看C3P0的源码,修改其中不适合OSGI环境的地方。(2)对Quartz2.2.0提供其它数据库连接池的支持,比如dbcp。对于第一种方法,C3P0的源码繁多,对于工期紧的项目,工作量大,可行度压力大。对于第二种方法,通过了解Quartz2.2.0的源码,发现可行性较高。
在Quartz中,首先我们看看Quartz是如何使用连接池以及如何设置默认的数据库连接池的。
在StdSchedulerFactory类的instantiate()方法中,有如下一段:
1 // Set up any DataSources 2 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 3 4 String[] dsNames = cfg.getPropertyGroups(PROP_DATASOURCE_PREFIX); 5 for (int i = 0; i < dsNames.length; i++) { 6 PropertiesParser pp = new PropertiesParser(cfg.getPropertyGroup( 7 PROP_DATASOURCE_PREFIX + "." + dsNames[i], true)); 8 9 String cpClass = pp.getStringProperty(PROP_CONNECTION_PROVIDER_CLASS, null); 10 11 // custom connectionProvider... 12 if(cpClass != null) { 13 ConnectionProvider cp = null; 14 try { 15 cp = (ConnectionProvider) loadHelper.loadClass(cpClass).newInstance(); 16 } catch (Exception e) { 17 initException = new SchedulerException("ConnectionProvider class '" + cpClass 18 + "' could not be instantiated.", e); 19 throw initException; 20 } 21 22 try { 23 // remove the class name, so it isn't attempted to be set 24 pp.getUnderlyingProperties().remove( 25 PROP_CONNECTION_PROVIDER_CLASS); 26 27 setBeanProps(cp, pp.getUnderlyingProperties()); 28 cp.initialize(); 29 } catch (Exception e) { 30 initException = new SchedulerException("ConnectionProvider class '" + cpClass 31 + "' props could not be configured.", e); 32 throw initException; 33 } 34 35 dbMgr = DBConnectionManager.getInstance(); 36 dbMgr.addConnectionProvider(dsNames[i], cp); 37 } else { 38 String dsJndi = pp.getStringProperty(PROP_DATASOURCE_JNDI_URL, null); 39 40 if (dsJndi != null) { 41 boolean dsAlwaysLookup = pp.getBooleanProperty( 42 PROP_DATASOURCE_JNDI_ALWAYS_LOOKUP); 43 String dsJndiInitial = pp.getStringProperty( 44 PROP_DATASOURCE_JNDI_INITIAL); 45 String dsJndiProvider = pp.getStringProperty( 46 PROP_DATASOURCE_JNDI_PROVDER); 47 String dsJndiPrincipal = pp.getStringProperty( 48 PROP_DATASOURCE_JNDI_PRINCIPAL); 49 String dsJndiCredentials = pp.getStringProperty( 50 PROP_DATASOURCE_JNDI_CREDENTIALS); 51 Properties props = null; 52 if (null != dsJndiInitial || null != dsJndiProvider 53 || null != dsJndiPrincipal || null != dsJndiCredentials) { 54 props = new Properties(); 55 if (dsJndiInitial != null) { 56 props.put(PROP_DATASOURCE_JNDI_INITIAL, 57 dsJndiInitial); 58 } 59 if (dsJndiProvider != null) { 60 props.put(PROP_DATASOURCE_JNDI_PROVDER, 61 dsJndiProvider); 62 } 63 if (dsJndiPrincipal != null) { 64 props.put(PROP_DATASOURCE_JNDI_PRINCIPAL, 65 dsJndiPrincipal); 66 } 67 if (dsJndiCredentials != null) { 68 props.put(PROP_DATASOURCE_JNDI_CREDENTIALS, 69 dsJndiCredentials); 70 } 71 } 72 JNDIConnectionProvider cp = new JNDIConnectionProvider(dsJndi, 73 props, dsAlwaysLookup); 74 dbMgr = DBConnectionManager.getInstance(); 75 dbMgr.addConnectionProvider(dsNames[i], cp); 76 } else { 77 String dsDriver = pp.getStringProperty(PoolingConnectionProvider.DB_DRIVER); 78 String dsURL = pp.getStringProperty(PoolingConnectionProvider.DB_URL); 79 80 if (dsDriver == null) { 81 initException = new SchedulerException( 82 "Driver not specified for DataSource: " 83 + dsNames[i]); 84 throw initException; 85 } 86 if (dsURL == null) { 87 initException = new SchedulerException( 88 "DB URL not specified for DataSource: " 89 + dsNames[i]); 90 throw initException; 91 } 92 try { 93 PoolingConnectionProvider cp = new PoolingConnectionProvider(pp.getUnderlyingProperties()); 94 dbMgr = DBConnectionManager.getInstance(); 95 dbMgr.addConnectionProvider(dsNames[i], cp); 96 } catch (SQLException sqle) { 97 initException = new SchedulerException( 98 "Could not initialize DataSource: " + dsNames[i], 99 sqle);100 throw initException;101 }102 }103 104 }105 106 }
从上面这段源码可以看出,如果在quartz.properties配置文件中没有设置PROP_CONNECTION_PROVIDER_CLASS或PROP_DATASOURCE_JNDI_URL项的话,则会使用默认的连接池PoolingConnectionProvider。那PoolingConnectionProvider中使用的是什么连接池呢?我们可以看看PoolingConnectionProvider的描述和实现:
1 /** 2 *3 * A
6 * 7 *ConnectionProvider
implementation that creates its own 4 * pool of connections. 5 *8 * This class uses C3PO (http://www.mchange.com/projects/c3p0/index.html) as 9 * the underlying pool implementation.
10 * 11 * @see DBConnectionManager12 * @see ConnectionProvider13 * 14 * @author Sharada Jambula15 * @author James House16 * @author Mohammad Rezaei17 */18 public class PoolingConnectionProvider implements ConnectionProvider
从类描述中我们就可以看出,该类使用的是C3P0的连接池技术,也就是说Quartz2.2.0默认使用的是C3P0连接池。然后我们看到该类实现了ConnectionProvider接口,首先看看ConnectionProvider的接口定义:
1 /** 2 * Implementations of this interface used by DBConnectionManager
3 * to provide connections from various sources. 4 * 5 * @see DBConnectionManager 6 * @see PoolingConnectionProvider 7 * @see JNDIConnectionProvider 8 * @see org.quartz.utils.weblogic.WeblogicConnectionProvider 9 * 10 * @author Mohammad Rezaei11 */12 public interface ConnectionProvider {13 /*14 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~15 * 16 * Interface.17 * 18 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~19 */20 21 /**22 * @return connection managed by this provider23 * @throws SQLException24 */25 Connection getConnection() throws SQLException;26 27 28 void shutdown() throws SQLException;29 30 void initialize() throws SQLException;31 }
从接口的描述可以知道,只需要实现该类,然后使用自定义的连接池技术就可以解决文中开头提到的问题了。
思路明确后,我们开始实现,我依照PoolingConnectionProvider的实现写了写了我自己的dbcp连接池的实现类,代码如下:
initialize方法:
1 private void initialize( 2 String dbDriver, 3 String dbURL, 4 String dbUser, 5 String dbPassword, 6 int maxConnections, 7 int maxStatementsPerConnection, 8 String dbValidationQuery, 9 boolean validateOnCheckout,10 int idleValidationSeconds,11 int maxIdleSeconds) throws SQLException, SchedulerException {12 if (dbURL == null) {13 throw new SQLException(14 "DBPool could not be created: DB URL cannot be null");15 }16 17 if (dbDriver == null) {18 throw new SQLException(19 "DBPool '" + dbURL + "' could not be created: " +20 "DB driver class name cannot be null!");21 }22 23 if (maxConnections < 0) {24 throw new SQLException(25 "DBPool '" + dbURL + "' could not be created: " + 26 "Max connections must be greater than zero!");27 }28 29 30 datasource = new BasicDataSource(); 31 datasource.setDriverClassName(dbDriver);32 33 datasource.setUrl(dbURL); 34 datasource.setUsername(dbUser); 35 datasource.setPassword(dbPassword);36 datasource.setMaxActive(maxConnections);37 datasource.setMinIdle(1);38 datasource.setMaxWait(maxIdleSeconds);39 datasource.setMaxOpenPreparedStatements(maxStatementsPerConnection);40 41 if (dbValidationQuery != null) {42 datasource.setValidationQuery(dbValidationQuery);43 if(!validateOnCheckout)44 datasource.setTestOnBorrow(true);45 else46 datasource.setTestOnReturn(true);47 datasource.setTimeBetweenEvictionRunsMillis(idleValidationSeconds);48 }49 }
1 public Connection getConnection() throws SQLException { 2 return datasource.getConnection(); 3 } 4 5 public void shutdown() throws SQLException { 6 datasource.close(); 7 } 8 9 public void initialize() throws SQLException {10 // do nothing, already initialized during constructor call11 try {12 this.initialize(this.driver, this.URL, this.user, this.password, 13 maxConnections, maxStatementsPerConnection, dbValidationQuery, 14 validateOnCheckout, idleValidationSeconds, maxIdleSeconds);15 } catch (SchedulerException e) {16 // TODO Auto-generated catch block17 throw new SQLException(e);18 }19 }
然后在quartz.properties配置文件中配置org.quartz.dataSource.dbcpDS.connectionProvider.class属性,如下:
org.quartz.dataSource.dbcpDS.connectionProvider.class: com.dawning.cloudview.app.scheduleframe.service.scheduleframe.pool.DbcpConnectionProvider
启动OSGI环境,加载DBCP所需的bundle,然后测试。通过测试,发现能正常连上数据库。该方法可行。