Novell is now a part of Micro Focus

How to Build J2EE Applications Using Novell Technologies: Part 6

Articles and Tips: article

J. Jeffrey Hanson
Chief Architect
Zareus Inc.
jeff@jeffhanson.com

01 Nov 2002


This AppNote is the sixth in a series that outlines a platform and methodology for Java 2 Platform Enterprise (J2EE) application development on NetWare using a service-oriented architecture. In this AppNote, we will learn about how the platform interfaces with the data tier and how we can build our data-access components to be highly flexible and robust.


Topics

Java application development, J2EE, Java Management Extensions (JMX)

Products

Java 2 Enterprise Edition

Audience

developers

Level

intermediate

Prerequisite Skills

familiarity with Java programming

Operating System

n/a

Tools

none

Sample Code

yes

Introduction

This is the sixth in a series of AppNotes outlining a platform and methodology for Java 2 Platform Enterprise Edition (J2EE) application development on NetWare using a service-oriented architecture.

In the previous AppNote, we thoroughly examined the platform kernel relating to services that JMX offers to empower our platform. In this AppNote, we will discuss how our platform interfaces with the data tier and how we can build our data-access components to be highly flexible and robust.

The Need for Data Access Abstractions

The data tier of enterprise application development offers integration and access to all services and data that are prevalent in enterprise applications and systems, including relational databases, directory services, and legacy systems.

Direct access to a data source links business logic and implementation details of an external resource. If the API to that resource changes, it will be necessary to also change your application source code throughout the application, resulting in a significant testing and maintenance burden.

However, a data access framework that is able to abstract data resources insulates callers from those resource changes. A data access framework presents a vendor-neutral interface to the caller; in addition, it manages the details of passing service requests from the caller to the business tier. This design is particularly beneficial because ultimately a single service request may access multiple data resources. The replacement of an existing data resource will also cause the existing data access class to be replaced, which then presents the new service to the caller. It is effortless for a system design to evolve with time when the business tier (with an abstract data-access layer) is isolated.

Overview of JDBC

JDBC technology allows you to access nearly any record-oriented data source from the Java programming language. It offers cross-DBMS connectivity to an extensive collection of SQL databases, and also provides access to non-relational data sources, such as spreadsheets or flat files.

The JDBC API is the industry standard database-connectivity mechanism between the Java programming language and relational databases. JDBC allows developers the full advantage of Java's "Write Once, Run Anywhere" promise for cross-platform applications that require access to enterprise data. A JDBC-enabled application allows a developer to effortlessly connect all available relational data, even in a heterogeneous environment.

The JDBC API is offered as a core part of the Java 2 platform and makes it possible to establish a connection with a relational database, send SQL statements to the database and process the results.

The Data Access Object Pattern

Code dependent on specific features of data resources connects business logic and data access logic. Therefore, replacing or modifying an application's data resources is very complicated.

The Data Access Object (DAO) pattern reduces the complications arising from tightly-coupled business logic and data access code by abstracting data sources to enable transparent access to the data. The DAO pattern has the following advantages:

  • Separates a data resource's interface from its data access implementation.

  • Adapts a specific data resource's access API to a generic interface.

  • Allows data access implementation to adjust independently from the code that uses the DAO.

  • Offers access to a particular data resource without coupling the resource's API to the business logic.

  • Permits application developers or deployment engineers the choice of a data access mechanism with no changes to the program code.

This approach allows a developer access to a data source only in terms of its DAO interface. Based on configuration data, an application uses a factory object to select the DAO implementation at runtime.

A DAO interface has at least one concrete class that implements the interface for a specific type of data source. To add a new type of data source, an application developer simply follows the steps listed below:

  1. Create a class that implements the DAO interface for the new data source type.

  2. Specify the implementing class's name in a meta-data configuration source.

  3. Facilitate a re-initialization by the client service, which causes the creation of an instance of the new DAO class by the factory, which the application will now use.

Figure 1 shows the implementation of the DAO design pattern for a DAO that accesses data for a business-contacts resource. Notice how the factory is used to select the DAO implementation.

Figure 1: Implementation of the DAO design pattern for a DAO that accesses data for a business-contacts resource.

The DAO Interface

Using the DAO pattern, a developer accesses a data source only in terms of its DAO interface. This approach shields the application or client service from modifications to the underlying data source. The following is an example of a DAO interface for a DAO that abstracts data and functionality for a data source that stores information for business contacts:

public interface ContactsDAO
{
   public Contact getContact(String personID,
                                   String addressID,
                                    String companyID) 
       throws DAOException;
 
 public String addContact(String addStr)
      throws DAOException;
 
 public void modifyContact(String contactID,
                                  String modifyStr)
        throws DAOException;

  public void deleteContact(Contact contact)
       throws DAOException;
}

The DAO Implementation

A DAO interface has at least one concrete class that implements the interface for a specific type of data source. The following is an implementation of the Contacts DAO interface for an hsqldb data source:

public class HSQLDBContactsDAO implements ContactsDAO
{
    public static String GET_PERSON_STATEMENT =
      "select * from person where personID = ?";
   
 public static String GET_ADDRESS_STATEMENT =
     "select * from address where addressID = ?";
 
 public static String GET_COMPANY_STATEMENT =
     "select * from company where companyID = ?";
 
 public HSQLDBContactsDAO()
   {
    }
    
 protected PersonDAO getPerson(String personID) 
      throws DAOException
  {
        Connection conn = null;
      PreparedStatement ps = null;
     ResultSet rs = null;
     PersonDAO ret = null;
    
     try
      {
            conn = getDataSource().getConnection();
          ps = conn.prepareStatement(GET_PERSON_STATEMENT,
                                         ResultSet.TYPE_SCROLL_INSENSITIVE,
                                           ResultSet.CONCUR_READ_ONLY);
         ps.setString(1, personID);
           rs = ps.executeQuery();
          if (rs.first())
          {
                ret = new HSQLDBPersonDAO(personID, rs);
         }
    
         rs.close();
          ps.close();
          conn.close();
            return ret;
      }
        catch (SQLException se)
      {
            throw new DAOException("SQLException: " + se.getMessage());
      }
    }
    
 protected AddressDAO getAddress(String addressID) 
       throws DAOException
  {
        Connection conn = null;
      PreparedStatement ps = null;
     ResultSet rs = null;
     AddressDAO ret = null;
   
     try
      {
            conn = getDataSource().getConnection();
          ps = conn.prepareStatement(GET_ADDRESS_STATEMENT,
                                            ResultSet.TYPE_SCROLL_INSENSITIVE,
                                           ResultSet.CONCUR_READ_ONLY);
         ps.setString(1, addressID);
          rs = ps.executeQuery();
          if (rs.first())
          {
                ret = new HSQLDBAddressDAO(addressID, rs);
           }
    
         rs.close();
          ps.close();
          conn.close();
            return ret;
      }
        catch (SQLException se)
      {
            throw new DAOException("SQLException: " + se.getMessage());
      }
    }
    
 protected CompanyDAO getCompany(String companyID) 
       throws DAOException
      {
    Connection conn = null;
  PreparedStatement ps = null;
 ResultSet rs = null;
 CompanyDAO ret = null;
   
 try
  {
        conn = getDataSource().getConnection();
      ps = conn.prepareStatement(GET_COMPANY_STATEMENT,
                                        ResultSet.TYPE_SCROLL_INSENSITIVE,
                                       ResultSet.CONCUR_READ_ONLY);
     ps.setString(1, companyID);
      rs = ps.executeQuery();
      if (rs.first())
      {
            ret = new HSQLDBCompanyDAO(companyID, rs);
       }
    
     rs.close();
      ps.close();
      conn.close();
        return ret;
  }
    catch (SQLException e)
   {
        throw new DAOException("SQLException: " + e.getMessage());
   }
    }
    
 public Contact getContact(String personID,
                                   String addressID,
                                    String companyID) 
       throws DAOException
  {
        PersonDAO person = getPerson(personID);
      AddressDAO address = getPerson(addressID);
       CompanyDAO company = getPerson(companyID);
       Contact contact = new Contact(person,
                                            address,
                                         company);
    }
    
 public String addContact(String addStr)
      throws DAOException
  {
        ...
  }
    
 public void modifyContact(String contactID,
                                  String modifyStr)
        throws DAOException;
 {
        ...
  }
    
 public void deleteContact(Contact contact)
       throws DAOException;
 {
        ...
  }
}

The DAO Factory

An application or service uses a factory object to select the DAO implementation class at runtime based on configuration data. Configuration data can be stored in a Java constants class, an XML file, a directory service, etc. The following is an example of an abstract factory used as the base class for multiple factories:

public abstract class DAOFactory
{
 // List of DAO types supported by this factory
   public static final int HSQLDB = 1;
  public static final int ORACLE = 2;
  public static final int DB2 = 3;
 
 // An abstract method is provided for each DAO that can be 
  // created. Each concrete factory provides an implementation 
    // for these methods.
    public abstract ContactsDAO getContactsDAO();
    public abstract PersonDAO getPersonDAO();
    public abstract AddressDAO getAddressDAO();
  public abstract CompanyDAO getCompanyDAO();
  
 public static DAOFactory getDAOFactory(int factory)
  {
    switch (factory) {
       case HSQLDB: 
            return new HSQLDBDAOFactory();
       case ORACLE: 
            return new OracleDAOFactory();      
     case DB2: 
           return new DB2DAOFactory();
      default: 
            return null;
     }
    }
}

A Concrete DAO Factory Implementation

A specific DAO factory class is provided for each DAO data source. The following is an example of an hsqldb DAO factory:

public class HSQLDBDAOFactory extends DAOFactory
{
 public static final String DRIVER =
      "org.hsqldb.jdbcDriver";
 public static final String DBURL =
       "jdbc:hsqldb:hsql://localhost:1476";
 
 // provide the database-specific connection method
   public static Connection createConnection(String username,
                                                       String passwd)
   {
        Connection conn = null;
  
     try {
            Class.forName(DRIVER).newInstance();
         if ((username != null) && (passwd != null))
              conn = DriverManager.getConnection(DBURL, username, passwd);
         else
             conn = DriverManager.getConnection(DBURL, null);
 
         return conn;
     }
        catch (Exception e)
      {
            // log error
     }
    }
    
 // protected JNDI-lookup method to be used
   // for all member DAOs
   protected Object lookupDAO(String daoClass)
  {
        Object dao = null;
       try {
            InitialContext ctx = new InitialContext();
           String className = (String)ctx.lookup(daoClass);
         dao = Class.forName(className).newInstance();
        } catch (NamingException e) {
            throw new DAOException(HSQLDBDAOFactory.lookupDAO: "
                                     + e.getMessage());
       } catch (Exception e) {
          throw new DAOException("HSQLDBDAOFactory.lookupDAO: "
                                        + e.getMessage());
       }
    
     return dao;
  }
    
 public ContactsDAO getContactsDAO()
  {
        return (ContactsDAO)lookupDAO(ConfigFile.CONTACTS_DAO_CLASS);
    }
    
 public PersonDAO getPersonDAO()
  {
        return (PersonDAO)lookupDAO(ConfigFile.PERSON_DAO_CLASS);
    }
    
 public AddressDAO getAddressDAO()
    {
        return (AddressDAO)lookupDAO(ConfigFile.ADDRESS_DAO_CLASS);
  }
    
 public CompanyDAO getCompanyDAO()
    {
        return (CompanyDAO)lookupDAO(ConfigFile.COMPANY_DAO_CLASS);
  }
}

The DAO Client

A client application or service retrieves the desired DAO factory, which is used to retrieve the desired DAO instance. Once the DAO instance is retrieved, the client can call data access methods on the instance without worrying about the underlying data access code:

// retrieve the desired DAO Factory
DAOFactory hsqldbFactory =   
    DAOFactory.getDAOFactory(DAOFactory.HSQLDB);
 
// retrieve the DAO
ContactsDAO ContactsDAO = hsqldbFactory.getContactsDAO();
   
// create a new Contact
String contactID = contactsDAO.addContact(addStr);
  
// get the new Contact object.
Contact contact = contactsDAO.getContact(contactID);
 
// modify the Contact object using SQL statements
contactsDAO.modifyContact(contactID, modifyStr);
  
// delete the Contact object
contactsDAO.deleteContact(aContact);

Reducing Redundancy Abundancy

A great deal of redundant code is produced when you write a separate class for data source types that have similar characteristics. For instance, JDBC data sources are different from one another mainly in how the SQL is used to access them. In fact, the only distinctions between our hsqldb DAO and a DAO for a different SQL database are the connection strings and the SQL utilized to access the data.

Therefore, an application can decrease redundant code by simply using a generic DAO that abstracts the SQL for different JDBC data sources. Figure 2 shows how an application can employ an XML file to identify the SQL for numerous JDBC data sources.

Figure 2: How an application can employ an XML file to identify the SQL for numerous JDBC data sources.

A Sample XML Configuration

<DAOConfiguration>
   <DAOStatements database="hsqldb">
      <SQLStatement method="GET_PERSON">
         <SQLFragment parameterNb="1">
              select * from person where personID = ?
          </SQLFragment>
     </SQLStatement>
        ...
  <DAOStatements database="oracle">
      <SQLStatement method="GET_ PERSON ">
           <SQLFragment parameterNb="1">
              select * from person where personID = ?
          </SQLFragment>
     </SQLStatement>
        ...
</DAOConfiguration>

hsqldb on NetWare

hsqldb is an open-source relational database engine written in Java, with a JDBC driver, which supports a subset of ANSI-92 SQL. It offers a small, fast database engine that provides in-memory, embedded and server modes. Furthermore, it contains tools such as a minimal Web server and other management tools.

hsqldb is shipped with JBoss, so, since JBoss was already installed on our NetWare machine via a previous article, we already have the core Java classes we need to create and access our sample database. The only additional item necessary is to add a startup .ncf script.

Starting the Database Server

The following script defines the runDBServer.ncf file that will start the hsqldb database server:

java -classpath $CLASSPATH;../lib/ext/hsqldb.jar org.hsqldb.Server

Creating and Populating the Database

The following script creates and populates our Person database:

create table person(name varchar,ssnum varchar,age varchar)
insert into person (name,ssnum,age) values('John Doe','123-45-6789','25')
insert into person (name,ssnum,age) values('Jane Smith','456-78-9012','32')
insert into person (name,ssnum,age) values('Jim Taylor','111-22-3333','54')
set autocommit true

The following script creates and populates our Address database:

create table address(street varchar,city varchar,state varchar,zip varchar)
insert into address (street,city,state,zip) values('123 Anywhere St.','Hippsville','Utah','84321')
insert into address (street,city,state,zip) values('456 Nowhere Ave.','Mytown','Utah','84789')
insert into address (street,city,state,zip) values('111 S. 222. E.','Yourtown','Utah','84654')
set autocommit true

The following script creates and populates our Company database:

create table company(name varchar,industry varchar)
insert into company (name,industry) values('Acme','Textiles')
insert into company (name,industry) values('Company B','Manufacturing')
set autocommit true

Conclusion

This AppNote introduced how the DAO strategy supports multiple JDBC databases with a single DAO class. It reduces redundant code and makes new database types simpler to include. To support a new database type, you merely add SQL statements for that database type to an XML configuration file, update the environment entry to use the new type, and then re-initialize the DAO.

In the next AppNote in this series, we will discuss how our DAO framework can also be used to simplify and unify access to Web service provider sources.

For Additional Information

For more information about the technologies discussed in this AppNote, refer to the following resources:

* Originally published in Novell AppNotes


Disclaimer

The origin of this information may be internal or external to Novell. While Novell makes all reasonable efforts to verify this information, Novell does not make explicit or implied claims to its validity.

© Copyright Micro Focus or one of its affiliates