Friday, October 8, 2010

OSGi JDBC service

When using JDBC in OSGi there is always the problem of the database drivers. In JDBC, depending on the used database vendor (MySQL, Oracle, etc.) a specific driver is needed to perform the vendor-dependent interface to the database. This vendor-interface is hidden behind the standard Driver interface. DataSource implementations, the recommended way to talk to databases, are generally unaware of the various drivers used by the application. However, the driver class for a specific database must be visible to the implementation to be able to make connections to the database, otherwise a ClassNotFound exception will occur.
Until now, I solved this problem by creating a fragment bundle to the Apache Commons DBCP bundle. This fragment bundle contains the driver jars and adds them to the bundle class path. As a result, the class path of the DBCP bundle is extended with the jars and the driver can be found when creating a data source. Problem solved.
With the OSGi 4.2 enterprise specification, a different solution is provided: the JDBC service interface. This interface contains methods for creating Driver, DataSource, ConnectionPoolDataSource and XADataSource objects. In short, anything you may want from an enterprise point of view. Since I want to make use of standard OSGi solutions where possible for the re-factoring I am doing, a JDBC service is the way I want to go. Unfortunately, I could not find any implementation (yet?) for this service for MySQL, which is the database type I am using for the application.

As a result I wanted to create an implementation myself. However, I found that the specification about the JDBC service is at some points unclear or incorrect where it applies the properties that can be passed to the various methods of the DataSourceFactory. As a result, I made my own assumptions/changes:
  • It is possible to pass the database URL as property. However, it is also possible to pass separately the server host name, port number, protocol, etc. I assume that when an URL is passed, that one takes precedence over the individual fields. If you want the URL to be automatically constructed, use the individual properties.
  • According to the specification, it is possible to pass connection pool settings to the ConnectionPoolDataSource and XADataSource methods. But, according to the APIs of those interfaces, actual pool management is done outside the data source: the data sources just return a database connection without pooling behaviour. Pool settings are therefore meaningless for those data sources. I however took the settings for creating a connection pool for normal data sources (as returned by the createDataSource method).
  • There is no indication about the time unit for JDBC_MAX_IDLE_TIME. I assume it is in ms.
  • I really haven't a clue about the meaning of the JDBC_ROLE_NAME and JDBC_PROPERTY_CYLE properties. I silently ignore them.
I only implemented the createDataSource and createDriver methods, which is according to the specification allowed. The other two methods just throw an exception. I added connection pooling to the data source returned by createDataSource when the pool properties from the specification are passed to the properties argument. Furthermore, additional properties as normally set on a DBCP BasicDataSource can be set as well.
If you are curious, here is what you need to get it working:
  1. Download Apache Commons DBCP (my version is 1.4) and Apache Commons Pool (my version is 1.5.5). The jars from the distribution archive are already bundles so just need to be loaded into your OSGi framework.
  2. Download the MySQL driver (my version is 5.1.13). The driver jar from the distribution is a bundle as well, so again just load the jar into your OSGi environment.
  3. Download the enterprise specification code from the OSGi site and load the jar as bundle in your OSGi environment.
  4. Download the service implementation and install/start the bundle. 
Here is a code fragment that indicates the use:
DataSourceFactory factory = ... lookup service ...
Properties props = new Properties();
props.put(DataSourceFactory.JDBC_DATABASE_NAME, "database");
pops.put(DataSourceFactory.JDBC_USER, "root");
props.put(DataSourceFactory.JDBC_PASSWORD, "root");
props.put(DataSourceFactory.JDBC_SERVER_NAME, "localhost");
props.put(DataSourceFactory.JDBC_MAX_POOL_SIZE, 8);
props.put("validationQuery", "SELECT 1 FROM DUAL");
props.put("testOnBorrow", true);
DataSource source = factory.createDataSource(props);

or with Spring-DM beans (assuming you are using CM for property management):

  <bean id="dataSource" class="org.avineas.jdbc.service.DelegatingDataSource">
    <constructor-arg>
      <osgi:reference interface="org.osgi.service.jdbc.DataSourceFactory"
        filter="(osgi.jdbc.driver.class=com.mysql.*)"/>
    </constructor-arg>
    <constructor-arg>
      <osgix:cm-properties persistent-id="hc.datasource">
        <prop key="url">jdbc:mysql://localhost/database</prop>
        <prop key="user">me</prop>
        <prop key="password">mypassword</prop>
        <prop key="maxWait">30000</prop>
        <prop key="validationQuery">SELECT 1 FROM DUAL</prop>
      </osgix:cm-properties>
    </constructor-arg>
  </bean>

1 comment:

  1. I'm a newbie to using OSGi's JDBC service. Could you give an example of looking up that DataSourceFactory?

    ReplyDelete