Data Source Binding

 < Free Open Study > 



We've already mentioned the inherent support for data source binding that is required by the J2EE 1.3 specification. The intent of this is to simplify the process of configuring and binding DataSource objects, which are standard, general-use factories that are used to obtain connections to databases for other resources to use.

The standard method of obtaining a database connection with JDBC 1.0 was to register a driver with Class.forName(). To use this method we have to specify the details of the database user and location (the username, password, database URL, and driver class) to request a connection. This means that our code becomes less portable. For example, this is how we would obtain a Connection with JDBC 1.0:

    Class.forName("org.gjt.mm.msql.Driver");    String url = "jdbc:mysql://localhost/myDB";    String user = "scott";    String password = "tiger";    Connection conn = DriverManager.getConnection(url, user, password); 

With JDBC 2.0, we can register a DataSource object for the application as a whole, bind it to a JNDI entity, and look it up anywhere in the application code. The benefit of this approach is that by hiding the details of the database schema, the code becomes more portable. Changes made at the database level (such as changing the database password) do not have any impact at the business level. Also, by encapsulating the nature of the data source, the application can switch easily between different connection models (for example, using cached connections or not).

However, the built-in binding method does have its drawbacks. Although we specify the driver class that should be used by the data source, we do not have control over the actual DataSource implementation that will be used. This is because DataSource is an interface, not a concrete class. For example, connection pooling can a dramatic effect on the performance of an application. By using a connection pool for reserving and returning pooled connection objects, the overhead of separately creating and destroying connections can be (largely) avoided. If we were experiencing a lot of traffic on our web site, we might want to increase the number of available connections in the pool. However, we have no way of configuring the properties of any connection pool supported by the data source factory.

We can see an example of the problems that can arise with Tomcat. Tomcat uses the open-source Tyrex data source factory as its default DataSource binding. A close look at the Tyrex implementation (available at http://tyrex.exolab.org) indicates that it neither uses a vendor-specific DataSource implementation, or a form of connection pooling. As a result, I would strongly discourage you from using the standard data source binding methods of Tomcat in an enterprise application. Of course, this is not true of all servlet containers. For example, the WebLogic application server from BEA provides an excellent pooling implementation.

For an enterprise application, a more robust approach is required. We will implement a generic extension of our abstract Config class, and use it to dynamically configure a custom DataSource implementation whose properties are specified in an XML file.

We have not entirely abandoned the built-in data source binding method though. We'll briefly go through the steps required for using the standard resource binding approach. Then you'll have knowledge of two alternatives methods for configuring and binding JDBC DataSource objects to a JNDI directory:

  • Built-In Data Source Binding

    Using the built-in data source binding it is very simple to set up and use DataSource objects. We only have to configure the data source in the server's configuration file and specify a data source to use in the application's deployment descriptor.

  • Custom Data Source Binding

    Setting up a custom data source binding is more complex than using a built-in data source binding, but in return we have full control over the method used to obtain connections. Using vendor-specific extensions, we can even use connection pooling and caching behind the scenes to reduce the overhead associated with the connections.

Using the Built-In Data Source Binding

To use the built-in method of data source binding in an application, we add a <resource-ref> element to the application's deployment descriptor. The logical data source named used by this element should refer to a resource entry configured in the server's configuration descriptor.

Before we can use a specified data source, we must make sure that the driver class we specify (for example, org.gjt.mm.myql.Driver for MySQL), is in the classpath of both the server and the application. We can ensure this by adding the JAR containing the driver to the server's shared library directory. If you are using Tomcat, the shared library is located at %CATALINA_HOME%/common/lib.

When the driver classes have been added to the classpath, and all resource entries have been properly configured, we can look up the data source. For example:

    InitialContext init = new InitialContext();    Context ctx = (Context) init.lookup("java:/comp/env");    DataSource source = (DataSource) ctx.lookup("jdbc/ProductionDB");    Connection conn = source.getConnection(); 

We have to include the Tomcat <Resource> tag in the <DefaultContext> element (and not a <Context> element) of the server's server.xml file if we are building your application from a WAR file.

Using a Custom Data Source Binding

To provide greater flexibility in our data source configuration, we will implement a simple application that configures and binds a custom DataSource object when the server is initialized. This application will consist of:

  • A DataSourceListener context listener that binds the actual data source when the server is started

  • The abstract DataSourceConfig configuration class that extends our abstract Config class

  • Optional RDBMS-specific extensions

As an example, we will illustrate how to extend the DataSourceConfig class for the Oracle and MySQL databases, although our methods will apply to any relational database.

Implementing the DataSourceConfig Class

So that we can dynamically configure our DataSource implementation, we define an extension of the abstract Config class (DataSourceConfig) that is used to read XML data source properties.

The DataSource interface lacks some common methods that would make it easier to construct a new DataSource instance dynamically (for example, by using the Class.forName() method) so we won't use the DataSourceConfig class directly. Also, although it's supposed to serve as a factory for database Connection objects, the DataSource interface defines no methods that can be used to set standard connection properties such as the user name and password of a database. This means that in order to construct a new instance we must resort to vendor-specific extensions. Accordingly, we declare the DataSourceConfig class as abstract, and declare extensions of that for each database vendor. We'll use the DataSourceConfig class to facilitate code-reuse.

The XML descriptor we use for our DataSourceConfig class has a few standard elements:

  • The root element must be a <DataSource> element

  • The root element should contain a <DatabaseUser> element, which specifies a user name of the database schema in question

  • The root element should contain a <DatabasePassword> element that specifies the password for the user specified in the <DatabaseUser> element

  • The root element should contain a <DatabaseName> element the specifies the name of the database

  • The root element should contain a <ServerName> element the specifies the name of the database server

  • The root element should contain a <ServerPort> element the specifies the port number of the database server

For example:

    <?xml version="1.0" encoding="ISO-8859-1"?>    <DataSource>      <DatabaseUser>danr</DatabaseUser>      <DatabasePassword>wrox</DatabasePassword>      <DatabaseName>persistence</DatabaseName>      <ServerName>localhost</ServerName>      <ServerPort>3306</ServerPort>    </DataSource> 

The DataSourceConfig class extracts these common properties, that can then be used to connect to a database via JDBC. Extensions of DataSourceConfig can add their own extensions to the XML document:

    package persistence.database;    import persistence.Config;    import javax.servlet.ServletContext;    import javax.sql.DataSource;    public abstract class DataSourceConfig extends Config {      private static final String DATABASE_USER = "DatabaseUser";      private static final String DATABASE_PASSWORD = "DatabasePassword";      private static final String SERVER_NAME = "ServerName";      private static final String DATABASE_NAME = "DatabaseName";      private static final String SERVER_PORT = "ServerPort";      protected DataSource ds;      protected String databaseUser;      protected String databasePassword;      protected String serverName;      protected String portNumber;      protected String databaseName; 

Through the init() method, the DataSourceConfig reads the common database properties:

      public void init(ServletContext sctx, String xmlFile) throws Exception {        super.init(sctx, xmlFile);        databaseUser = getElementText(root, DATABASE_USER);        databasePassword = getElementText(root, DATABASE_PASSWORD);        databaseName = getElementText(root, DATABASE_NAME);        serverName = getElementText(root, SERVER_NAME);        portNumber = getElementText(root, SERVER_PORT);      }      public DataSource getDataSource() {        return ds;      }    } 

Implementing the Example for Oracle

To illustrate how we can use our abstract DataSourceConfig class, we'll implement an extension for the Oracle RDBMS. In order to use this extension for your own application, you must download the Oracle JDBC drivers and add them to %CATALINA_HOME%/common/lib. You can download the Oracle JDBC drivers free of charge from http://otn.oracle.com.

Using our data source application allows us to specify at deployment time what DataSourceConfig implementation we want to use. To use our application with a new type of relational database (for example SQL Server), we only have to write a simple extension of our DataSourceConfig class, which involves implementing a single method. The overhead of doing that for each type of RDBMS that we want to support is compensated by the increased control over the connection fabrication process that we gain.

We're don't have to use Oracle for this application to work. We can easily extend the DataSourceConfig class to work with any RDBMS (we'll see a MySQL implementation in a moment):

    package persistence.database.oracle;    import persistence.database.DataSourceConfig;    import javax.servlet.ServletContext;    import javax.sql.DataSource;    import oracle.jdbc.pool.OracleConnectionCacheImpl;    public class OracleConfig extends DataSourceConfig {      private static final String MAX_CONNECTIONS = "MaxConnections"; 

For increased efficiency, our OracleConfig class uses the OracleConnectionCache implementation provided by the Oracle JDBC extensions. This DataSource implementation maintains caches of physical database connections, similar to a traditional connection pool. To illustrate the use of vendor-specific XML properties, we let OracleConfig retrieve the maximum number of cached connections from the XML file (which we would set using a custom <MaxConnections> element).and set the respective property of the OracleConnectionCacheImpl instance accordingly:

      public void init(ServletContext ctx, String xmlFile) throws Exception {        super.init(ctx, xmlFile);        //Construct the database URL        String databaseURL = "jdbc:oracle:thin:@" + serverName + ":" +                             portNumber + ":" + databaseName;        ds = new OracleConnectionCacheImpl();        ((OracleConnectionCacheImpl) ds).setURL(databaseURL);        ((OracleConnectionCacheImpl) ds).setUser(databaseUser);        ((OracleConnectionCacheImpl) ds).setPassword(databasePassword);        try {          int maxConnections = Integer.parseInt(                                         getElementText(root, MAX_CONNECTIONS));          ((OracleConnectionCacheImpl) ds).setMaxLimit(maxConnections);        } catch (NumberFormatException n) {}        cleanup();      }    } 

Implementing the Example for MySQL

We'll also implement an extension of DataSourceConfig for MySQL:

    package persistence.database.mysql;    import persistence.database.DataSourceConfig;    import javax.servlet.ServletContext;    import javax.sql.DataSource;    import org.gjt.mm.mysql.MysqlDataSource;    public class MySQLConfig extends DataSourceConfig { 

Just as our Oracle implementation used OracleConnectionCache, we will use the MysqlDataSource class, which is provided with the MM.MySQL JDBC driver (available from http://www.mysql.com/downloads/api-jdbc.html). This JDBC implementation doesn't support connection pooling so we don't bother with the custom <MaxConnections> element:

      public void init(ServletContext sctx, String xmlFile) throws Exception {        System.out.println("MySQL.init()");        super.init(sctx, xmlFile);        ds = new MysqlDataSource();         ((MysqlDataSource) ds).setServerName(serverName);         ((MysqlDataSource) ds).setDatabaseName(databaseName);         ((MysqlDataSource) ds).setUser(databaseUser);         ((MysqlDataSource) ds).setPassword(databasePassword);        try {          int port = Integer.parseInt(portNumber);          ((MysqlDataSource) ds).setPort(port);        } catch (NumberFormatException n) {}        cleanup();      }    } 

Implementing a Generic Solution

Sometimes it might be useful to define a generic, implementation of the DataSourceConfig class, that can be used when a specific DataSourceConfig implementation for the database in question is not available, or if the JDBC driver being used doesn't offer any specific performance enhancements. A simple way of writing such a default implementation is to use the open-source PoolMan application from Code Studio (http://www.codestudio.com). This package allows you to configure a generic database connection pool, no matter what database you are working with.

Implementing the DataSourceListener

Now that we have implemented an appropriate data source configuration class, we can create the context listener that will instantiate and bind the specified DataSource object when the server starts. This class, DataSourceListener, implements the ServletContextListener interface. When the server is initialized, its contextInitialized() method is called, in which it looks up two initialization parameters:

  • The name of the data source XML configuration file from a supplied initialization parameter (which means that we don't have to hard-code the file name in our source code)

  • The class name of the DataSourceConfig implementation it should use from a supplied initialization parameter (which the class uses to attempt to construct a new instance)

If everything goes as expected, the contextInitialized() method obtains the DataSource object and binds it to the local directory context (using a JNDI name supplied as a context-wide application parameter):

    package persistence.database;    import javax.naming.InitialContext;    import javax.servlet.*;    import javax.servlet.http.*;    public class DataSourceListener implements ServletContextListener {      public static String JNDI_NAME;      private ServletContext sctx;      public void contextInitialized(ServletContextEvent event) {        sctx = event.getServletContext();        try {          Class cls = Class.forName(sctx.getInitParameter("DataSourceConfig"));          DataSourceConfig cfig = (DataSourceConfig) cls.newInstance();          cfig.init(sctx, sctx.getInitParameter("DataSourceConfigXML"));          InitialContext ctx = new InitialContext();          JNDI_NAME = sctx.getInitParameter("DataSourceConfig");          ctx.bind(JNDI_NAME, cfig.getDataSource());        } catch (Throwable t) {          sctx.log("DataSourceListener", t);        }      }      public void contextDestroyed(ServletContextEvent event) {        sctx = null;      }    } 

Deploying the Application

To deploy the DataSourceListener we need to make the following additions to the application's deployment descriptor:

    <context-param>      <param-name>DataSourceConfigXML</param-name>      <param-value>/xml/datasource-config.xml</param-value>    </context-param>    <context-param>      <param-name>DataSourceConfig</param-name>      <param-value>persistence.database.mysql.MySQLConfig</param-value>    </context-param>    <listener>      <listener-class>persistence.database.DataSourceListener</listener-class>    </listener> 

Once we have deployed the listener, we can look up and use the DataSource in the same way that we did for the built-in binding method:

    Context ctx = new InitialContext();    DataSource ds = (DataSource) ctx.lookup(DataSourceListener.JNDI_NAME);    Connection conn = ds.getConnection(); 

start sidebar

As we illustrate the use of persistent resource in your servlet applications, we'll assume that you have a valid DataSource is configured and bound at server initialization time (although we won't worry about the actual method used for the binding).

end sidebar



 < Free Open Study > 



Professional Java Servlets 2.3
Professional Java Servlets 2.3
ISBN: 186100561X
EAN: 2147483647
Year: 2006
Pages: 130

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net