Setting up a JNDI data source in Tomcat

The first question is, why would anyone even want to do this? The short answer is, convenience. Doing this, you can take your application.war file to another host, create a small xml file in $TOMCAT_HOME/conf/[enginename]/[hostname]/, maybe add your database JDBC jar file to Tomcat's lib directory (if it's not in your application's lib directory) and deploy your application.war from the first host to the second host without making any changes to your application.war file. If you add a new JDBC jar file to Tomcat's lib directory, you can even change the type of database you use, say from MySQL to PostgreSQL or just update to a newer version of the database. That's a good reason not to put a JDBC jar file in the application's lib directory.

Now isn't that neat?

Two Scenarios

You can either deploy your application.war file to Tomcat's webapps directory or you can deploy it to any directory from which Tomcat has the right permissions to read it. Of course, in the first case Tomcat will become aware of it as soon as it's written to the webapps directory and deploy it. In this case you don't have to create a small xml file in $TOMCAT_HOME/conf/[enginename]/[hostname]/, but can enter the same text into ${your_app_home}/**/META-INF/context.xml. Obviously, if you go this way and anything about your database - database name, user name, password - changes, you'll have to change this file in your application. This detracts from the portability of your application.war file and is less convenient.

In the second scenario Tomcat won't have a clue when you write your application.war file to the directory outside of Tomcat. It will leave it in total peace, not deploying it and taking no notice of it. You'll have to tell Tomcat where your application.war file is. This you do by creating a small xml file in $TOMCAT_HOME/conf/[enginename]/[hostname]/.

Common to Both Scenarios

This is the only reference in your application to your data source common to both scenarios. Choose a descriptive name, like "staff_database" This is specified in your application's /WEB-INF/web.xml in the resource-ref element. Below is an extract from web-app_2_3.dtd to show where resource-ref fits in.

<!ELEMENT web-app (icon?, display-name?, description?, distributable?,
context-param*, filter*, filter-mapping*, listener*, servlet*,
servlet-mapping*, session-config?, mime-mapping*, welcome-file-list?,
error-page*, taglib*, resource-env-ref*, resource-ref*, security-constraint*,
login-config?, security-role*, env-entry*, ejb-ref*,  ejb-local-ref*)>
                    

Below is an example of a resource-ref element. It was the last element in the web.xml file, below the welcome-file-list element, in this case.

<resource-ref>
<description>The staff database</description>
<res-ref-name>jdbc/staff_database</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
                    

Obviously, the value in the res-ref-name element nested inside the resource-ref element is an exact match to the value in the name attribute of the Resource element nested inside the Context element of the small xml file in $TOMCAT_HOME/conf/[enginename]/[hostname]/, as is discussed further down. The jdbc/ prefix is required in both cases.

The Data source Specification in Tomcat

Let's start by setting up the data source. This is done by creating a small xml file in $TOMCAT_HOME/conf/[enginename]/[hostname]/. This file should have a name the same as the context your web application is going to run in. If your application is going to run in the root context, the name should be ROOT.xml, if in the my_site context, the name should be my_site.xml. The engine name is often, nearly always, Catalina and the host name often localhost. Look in the server.xml file to find these two names. This file should contain the following:

<?xml version="1.0" encoding="UTF-8"?>
<Context antiJARLocking="true" docBase="/home/you/projects/java/your_app/taget/application.war" path="/your_war_name">
<Resource name="jdbc/staff_database"
      auth="Container"
      type="javax.sql.DataSource"
      username="your_database_username"
      password="your_database_password"
      driverClassName="org.postgresql.Driver"
      url="jdbc:postgresql://localhost:5432/your_database_name"
      validationQuery="select 1"
      maxActive="5"
      maxIdle="2"/>
</Context>
                    

In the example above your application is not sourced from $/webapps, but from the war file indicated in the docBase attribute of the Context element. If you are going to deploy your application to $/webapps you simply replace the full path descriptor with your_war_name.war. In case the war name is going to be ROOT.war you can do away with the docBase attribute completely.

The path attribute of the Context element indicates the context root of your application you will type into your browser address field, like http://localhost:8080/your_war_name. In case the context root is going to be the root element, the value of path can be empty or / To run your application in the root context you must remove Tomcat's default ROOT.war file and delete the ROOT directory created from this file - both in Tomcat's webapps directory.

The name attribute of the Resource element nested inside the Context element is the JNDI name you are going to use to get hold of your data source, in this example, staff_database. The jdbc/ prefix is required.

Your JNDI specification can be done elsewhere, from where it will be written to web.xml, if you are using anything but a plain JDBC set-up. For example, if you are using Grails, use grails-app/conf/DataSource.groovy and, for the production environment, do it as follows:

production {
    dataSource {
        pooled = true
        dbCreate = "update"
        jndiName = "jdbc/jndi_name"
    }
}
                    

When you use Grails and you do it as indicated above you will have to add some code to scripts/_Event.groovy so that the resource-ref element is written correctly to web.xml. But that's another story...

Next time you move your application you can leave your war file intact, just insert the JNDI name used in your application's web.xml in the small xml file you created in $TOMCAT_HOME/conf/[enginename]/[hostname]/, as explained above. Now isn't this neat? BTW, this is how this application is done. The main database content is the links, the users (for logging in) and the clients on the respective pages.

Using This in Java

Below is an example of the Java code needed to use the JNDI name. You will notice that I have a connection pool.

import java.sql.Connection;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

/**
*
* @author chris
*/
public class ConnectionProvider {

    ArrayList connections = null;

    private static Connection createConnection() {
        try {
            Context context = new InitialContext();
            DataSource ds = (DataSource) context.lookup("java:comp/env/jdbc/staff_database");
            Connection connection = ds.getConnection();
            PooledConnection pc = new PooledConnection(connection, true);
            if (connections == null) {
                connections = new ArrayList();
            }
            connections.add(pc);
            return connection;
        }
        catch (javax.naming.NamingException ne) {
            System.err.println("NamingException - trying to create a "
                    + "database connection: " + ne.getMessage());
            return null;
        }
        catch (java.sql.SQLException sqle) {
            System.err.println("SQLException - trying to create a "
                    + "database connection: " + sqle.getMessage());
            return null;
        }
    }
}
                                

And finally...

This works in Tomcat 6 and 7. Let us know if you have anything to add or any remarks. The form for sending mail is below.


Submit a comment

Use and empty line to separate paragraphs in the "Comment" text area.

Links and html markup are not allowed.

Submit a Comment




SQL Exception thrown: An I/O error occured while sending to the backend.