有任务调度需求的人对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 DataSources2 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

3

4 String[] dsNames =cfg.getPropertyGroups(PROP_DATASOURCE_PREFIX);5 for (int i = 0; i < dsNames.length; i++) {6 PropertiesParser pp = newPropertiesParser(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 '" +cpClass18 + "' could not be instantiated.", e);19 throwinitException;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 '" +cpClass31 + "' props could not be configured.", e);32 throwinitException;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 !=dsJndiProvider53 || null != dsJndiPrincipal || null !=dsJndiCredentials) {54 props = newProperties();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 = newJNDIConnectionProvider(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 = newSchedulerException(82 "Driver not specified for DataSource: "

83 +dsNames[i]);84 throwinitException;85 }86 if (dsURL == null) {87 initException = newSchedulerException(88 "DB URL not specified for DataSource: "

89 +dsNames[i]);90 throwinitException;91 }92 try{93 PoolingConnectionProvider cp = newPoolingConnectionProvider(pp.getUnderlyingProperties());94 dbMgr =DBConnectionManager.getInstance();95 dbMgr.addConnectionProvider(dsNames[i], cp);96 } catch(SQLException sqle) {97 initException = newSchedulerException(98 "Could not initialize DataSource: " +dsNames[i],99 sqle);100 throwinitException;101 }102 }103

104 }105

106 }

从上面这段源码可以看出,如果在quartz.properties配置文件中没有设置PROP_CONNECTION_PROVIDER_CLASS或PROP_DATASOURCE_JNDI_URL项的话,则会使用默认的连接池PoolingConnectionProvider。那PoolingConnectionProvider中使用的是什么连接池呢?我们可以看看PoolingConnectionProvider的描述和实现:

1 /**

2 *

3 * A ConnectionProvider implementation that creates its own4 * pool of connections.5 *

6 *7 *

8 * This class uses C3PO (http://www.mchange.com/projects/c3p0/index.html) as9 * the underlying pool implementation.

10 *11 *@seeDBConnectionManager12 *@seeConnectionProvider13 *14 *@authorSharada Jambula15 *@authorJames House16 *@authorMohammad Rezaei17 */

18 public class PoolingConnectionProvider implements ConnectionProvider

从类描述中我们就可以看出,该类使用的是C3P0的连接池技术,也就是说Quartz2.2.0默认使用的是C3P0连接池。然后我们看到该类实现了ConnectionProvider接口,首先看看ConnectionProvider的接口定义:

1 /**

2 * Implementations of this interface used by DBConnectionManager3 * to provide connections from various sources.4 *5 *@seeDBConnectionManager6 *@seePoolingConnectionProvider7 *@seeJNDIConnectionProvider8 *@seeorg.quartz.utils.weblogic.WeblogicConnectionProvider9 *10 *@authorMohammad Rezaei11 */

12 public interfaceConnectionProvider {13 /*

14 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~15 *16 * Interface.17 *18 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~19 */

20

21 /**

22 *@returnconnection managed by this provider23 *@throwsSQLException24 */

25 Connection getConnection() throwsSQLException;26

27

28 void shutdown() throwsSQLException;29

30 void initialize() throwsSQLException;31 }

View Code

从接口的描述可以知道,只需要实现该类,然后使用自定义的连接池技术就可以解决文中开头提到的问题了。

思路明确后,我们开始实现,我依照PoolingConnectionProvider的实现写了写了我自己的dbcp连接池的实现类,代码如下:

initialize方法:

1 private voidinitialize(2 String dbDriver,3 String dbURL,4 String dbUser,5 String dbPassword,6 intmaxConnections,7 intmaxStatementsPerConnection,8 String dbValidationQuery,9 booleanvalidateOnCheckout,10 intidleValidationSeconds,11 int maxIdleSeconds) throwsSQLException, SchedulerException {12 if (dbURL == null) {13 throw newSQLException(14 "DBPool could not be created: DB URL cannot be null");15 }16

17 if (dbDriver == null) {18 throw newSQLException(19 "DBPool '" + dbURL + "' could not be created: " +

20 "DB driver class name cannot be null!");21 }22

23 if (maxConnections < 0) {24 throw newSQLException(25 "DBPool '" + dbURL + "' could not be created: " +

26 "Max connections must be greater than zero!");27 }28

29

30 datasource = newBasicDataSource();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 else

46 datasource.setTestOnReturn(true);47 datasource.setTimeBetweenEvictionRunsMillis(idleValidationSeconds);48 }49 }

View Code

1 public Connection getConnection() throwsSQLException {2 returndatasource.getConnection();3 }4

5 public void shutdown() throwsSQLException {6 datasource.close();7 }8

9 public void initialize() throwsSQLException {10 //do nothing, already initialized during constructor call

11 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 block

17 throw newSQLException(e);18 }19 }

View Code

然后在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,然后测试。通过测试,发现能正常连上数据库。该方法可行。

Logo

一站式 AI 云服务平台

更多推荐