4.5. Data Access Object Pattern

The Music EJB shown in this chapter accesses a database using the JDBC's SQL interface. It works fine with Cloudscape's reference implementation of SQL (provided by Sun's J2EE reference implementation) but what happens if we change databases? How would this affect our code? Unfortunately, porting the current version of the Music EJB to a different database is not as smooth as one would like. Specifically, changes may be necessary to the bean implementation class, MusicBean.java. Although we isolated database access to separate private methods in the Music EJB, the EJB code must still be recompiled and redeployed.

The Data Access Object (DAO) pattern introduces a deeper level of abstraction for any EJB that accesses databases. Figure 4-7 shows you how this pattern might be applied to the Music EJB. First, we create an interface (MusicDAO) that defines the methods necessary for database access. Second, we define a Factory class (MusicDAOFactory) with a static method that instantiates an implementation of our abstract interface. This Factory class performs a lookup to get the actual name of the implementation class. Because the class name appears in the EJB's deployment descriptor, it's possible to change implementation classes without recompiling any code. This is the obvious benefit of the DAO approach. Third, we create at least one implementation class (MusicDAOCloudscape) with the code to access a database (in this case, Cloud scape). In our application, the MusicDAOCloudscape class creates the RecordingVO and TrackVO value objects and sends them back to the EJB client.

Figure 4-7. Class Relationships in DAO Pattern Implementation


DAO Pattern Implementation

Now we're ready to show you how to implement the DAO Pattern for the Music EJB. The Swing client (MusicApp.java) and the web component client (musicGet.jsp, musicPost.jsp, and error.jsp) do not require changes. Likewise, the home and remote interfaces (MusicHome and Music) are also unchanged. The bean implementation class, MusicBean.java, does change, since we now access the database differently. In fact, the bean implementation code shrinks quite a bit with the DAO approach.

Figure 4-8 is a sequence diagram showing the interactions between the DAO client, Music EJB, and the other objects required to implement the DAO pattern. You might find it helpful to consult this diagram while reading through the implementation code.

Figure 4-8. Sequence Diagram Showing Interactions Among Objects in DAO Pattern


MusicBean

Listing 4.11 is the altered source for MusicBean.java, which uses the DAO pattern for database access. The code is similar to what we had before, except for several changes. Inside ejbCreate(), where we previously performed the database name lookup and DataSource instantiation, we now call MusicDAOFactory.getDAO() to obtain a single instance of a MusicDAO object (dao). This change allows our two business methods (getMusicList() and getTrackList()) to invoke methods with a dao reference from the MusicDAO interface (dbLoadMusicList() and dbLoadTrackList(), respectively). Hence, the bean implementation class becomes simpler with this approach, since we have extracted all of the SQL code. This use of the DAO pattern is significant, because it decouples our business methods from the database they access.

Listing 4.11. MusicBean.java Using DAO Pattern
// MusicBean.java
import java.util.*;
import javax.ejb.*;
import javax.naming.*;
import java.rmi.RemoteException;

public class MusicBean implements SessionBean {

  // class-wide MusicDAO object to access the data
  // MusicDAO object is instantiated in ejbCreate()
  // and provides implementation of the MusicDAO interface
  // for the particular database we're using
  private MusicDAO dao;

  // Business methods

  public ArrayList getMusicList()
  {
    try {
      // Encapsulate database calls in MusicDAO
      return(dao.dbLoadMusicList());
    } catch (MusicDAOSysException ex) {
      throw new EJBException("getMusicList: " +
               ex.getMessage());
    }
  }

  public ArrayList getTrackList(RecordingVO rec)
             throws NoTrackListException {
    ArrayList trackList;
    try {
      // Encapsulate database calls in MusicDAO
      trackList = dao.dbLoadTrackList(rec);

    } catch (MusicDAOSysException ex) {
      throw new EJBException("getTrackList: " +
          ex.getMessage());
    }

    if (trackList.size() == 0) {
      throw new NoTrackListException(
          "No Track List found for RecordingID " +
                   rec.getRecordID());
    }
    return trackList;
  }

  // EJB methods

  public void ejbCreate() {
    try {
      // The MusicDAOFactory class returns
      // an implementation of the MusicDAO interface
      dao = MusicDAOFactory.getDAO();
    }

    catch (MusicDAOSysException ex) {
      throw new EJBException(ex.getMessage());
    }
  }
  public MusicBean() {}
  public void ejbRemove() {}
  public void ejbActivate() {}
  public void ejbPassivate() {}
  public void setSessionContext(SessionContext sc) {}

} // MusicBean

Naming Environment Entry

The MusicDAOFactory class is responsible for instantiating the appropriate MusicDAO class. Remember, MusicDAO is an interface and cannot be instantiated. It exists solely to provide the method signatures our Music EJB calls to access the database.

How do we tell the MusicDAOFactory which implementation class to instantiate? By specifying a naming environment entry in the Music EJB deployment descriptor, we can customize a component during deployment without changing the component's source code. The EJB container oversees the naming environment and provides access through the JNDI naming context. Class InitialContext provides a single point of access to naming services.

A deployment tool lets you specify such an environment entry. Here's what the entry in a deployment descriptor might look like.

<env-entry>
  <env-entry-name>MusicDAOClass</env-entry-name>
  <env-entry-type>java.lang.String</env-entry-type>
  <env-entry-value>MusicDAOCloudscape</env-entry-value>
</env-entry>

The <env-entry> tag specifies a naming environment entry consisting of a name (MusicDAOClass), type (java.lang.String), and value (MusicDAOCloudscape). The MusicDAOFactory uses class name MusicDAOCloudscape to create the correct instance.

MusicDAOFactory

Listing 4.12 is the code for class MusicDAOFactory consisting of a single getDAO() static method. First, this method instantiates InitialContext to perform a lookup of java:comp/env/MusicDAOClass in the naming environment entry of the deployment descriptor. To create the correct instance, getDAO() reads MusicDAOCloudscape from the corresponding naming environment entry and uses it as a String argument to an invocation of Class.forName().newIn stance(). The return type from this call is an implementation of a MusicDAO object, which is then returned from the static method.

Listing 4.12. MusicDAOFactory.java
// MusicDAOFactory.java
import javax.naming.NamingException;
import javax.naming.InitialContext;
import MusicDAOSysException;

public class MusicDAOFactory {

  // Instantiate a subclass that implements the
  // MusicDAO interface.
  // Obtain subclass name from the deployment descriptor

  public static MusicDAO getDAO()
             throws MusicDAOSysException {

  MusicDAO musicDAO = null;
  String musicDAOClass = "java:comp/env/MusicDAOClass";
  String className = null;

    try {
      InitialContext ic = new InitialContext();

      // Lookup value of environment entry
      // for DAO classname
      // Value is set in deployment descriptor
      className = (String) ic.lookup(musicDAOClass);

      // Instantiate class, which will provide
      // implementation for the MusicDAO interface
      musicDAO = (MusicDAO) Class.forName(
                 className).newInstance();

    } catch (Exception ex) {
      throw new MusicDAOSysException(
           "MusicDAOFactory.getDAO: " +
           "NamingException for <" + className +
"> DAO class : 
" + ex.getMessage());
    }
    return musicDAO;
  }
}

MusicDAO

Interface MusicDAO in Listing 4.13 provides the methods for accessing the Music Collection database. This interface has the same functionality as the methods we wrote in the first version of the Music EJB implementation. The two methods, dbLoadMusicList() and dbLoadTrackList(), both return ArrayList and have the same signatures as before. Note that the throws clauses for both methods include MusicDAOSysException. This user-defined exception allows client EJB code to discriminate exceptions generated in the MusicDAO object from other runtime exceptions.

Listing 4.13. MusicDAO.java
// MusicDAO.java
import java.util.ArrayList;
import MusicDAOSysException;
import RecordingVO;

// Interface for Music Data Access Object

public interface MusicDAO {
  public ArrayList dbLoadMusicList()
      throws MusicDAOSysException;
  public ArrayList dbLoadTrackList(RecordingVO rec)
      throws MusicDAOSysException;
}

MusicDAOSysException

Listing 4.14 is the class definition for MusicDAOSysException. Note that this exception class extends from RuntimeException, making it a system exception. The EJB container catches system exceptions and typically wraps them in RemoteException to send back to the client. However, the container may instead perform some sort of error handling to address the problem.

Listing 4.14. MusicDAOSysException.java
// MusicDAOSysException.java
// System Exception
import java.lang.RuntimeException;

public class MusicDAOSysException extends RuntimeException {
// MusicDAOSysException extends standard RuntimeException.
// Thrown by MusicDAO subclasses when there is an
// unrecoverable system error (typically SQLException)

  public MusicDAOSysException(String msg) {
    super(msg);
  }
  public MusicDAOSysException() {
    super();
  }
}

MusicDAOCloudscape

The MusicDAOCloudscape class implements the MusicDAO interface specifically for the Cloudscape database. Using a database from another vendor, such as Oracle or Sybase, would require a different implementation. To use an alternative implementation, all you have to do is change the environment entry value for MusicDAOCloudscape to a different value in the EJB's deployment descriptor. No recompilation of any code is required (other than compiling the new implementation class). Listing 4.15 shows the code for MusicDAOCloudscape.java.

The constructor performs a lookup of the DataSource name to instantiate a DataSource object. We have private methods to make a connection, release the connection, and close the prepared statements and result sets. These private methods make it easier to connect for each database operation and close the resources from within a finally block when we're done.

The workhorse methods, dbLoadMusicList() and dbLoadTrackList(), are almost identical to the methods in the first version of the Music EJB. One important difference is the changes to all the catch handlers. Each catch handler now has an SQLException signature and throws a MusicDAOSysException in its catch block. In a large application it is always helpful to know the source of an exception. Only a subclass of MusicDAO will generate the MusicDAOSysException.

Listing 4.15. MusicDAOCloudscape.java
// MusicDAOCloudscape.java
import java.util.ArrayList;
import javax.ejb.*;
import javax.naming.*;
import javax.sql.DataSource;
import java.sql.*;
import MusicDAOSysException;

// Implements MusicDAO for Cloudscape database.
public class MusicDAOCloudscape implements MusicDAO {

  private Connection con = null;
  private DataSource datasource = null;

  public MusicDAOCloudscape()
             throws MusicDAOSysException {
    String dbName = "java:comp/env/jdbc/MusicDB";
    try {
      InitialContext ic = new InitialContext();
      datasource = (DataSource) ic.lookup(dbName);
    } catch (NamingException ex) {
      throw new MusicDAOSysException(
          "Exception during datasource lookup: " +
            dbName + ":
" + ex.getMessage());
    }
  }

  // Obtain a JDBC Database connection
  private void getConnection() throws MusicDAOSysException
  {
    try {
      con = datasource.getConnection();

    } catch (SQLException ex) {
      throw new MusicDAOSysException(
        "SQLException during DB Connection:
" +
        ex.getMessage());
    }
  }
  // Release JDBC Database connection
  private void disConnect() throws MusicDAOSysException
  {
    try {
      if (con != null) con.close();
    } catch (SQLException ex) {
      throw new MusicDAOSysException(
        "SQLException during DB close connection:
" +
        ex.getMessage());
    }
  }

  private void closeStatement(PreparedStatement s)
            throws MusicDAOSysException {
    try {
      if (s != null) s.close();
    } catch (SQLException ex) {
      throw new MusicDAOSysException(
        "SQL Exception during statement close
"
        + ex.getMessage());
    }
  }

  private void closeResultSet(ResultSet rs)
            throws MusicDAOSysException {
    try {
      if (rs != null) rs.close();
    } catch (SQLException ex) {
      throw new MusicDAOSysException(
        "SQL Exception during result set close
"
        + ex.getMessage());
    }
  }

  public ArrayList dbLoadMusicList()
           throws MusicDAOSysException {

    ArrayList mList = new ArrayList();

    // The following query searches 3 tables in
    // the Music Collection database to create a composite
    // RecordingVO object.
    // We select all fields in the Recordings table,
    // the RecordingArtistName field in the
    // Recording Artists table, and the MusicCategory field
    // from the Music Categories table.
    // The Where clause matches the correct records from
    // the Recording Artists and Music Categories tables.

    String selectQuery = "Select Recordings.*, " +
      ""Recording Artists".RecordingArtistName, " +
      ""Music Categories".MusicCategory " +
      "From Recordings, "Recording Artists", " +
      ""Music Categories" " +
      "Where Recordings.RecordingArtistID = " +
      ""Recording Artists".RecordingArtistID and " +
      "Recordings.MusicCategoryID = " +
      ""Music Categories".MusicCategoryID ";

    PreparedStatement musicStmt = null;
    ResultSet rs = null;
    try {
      // Obtain a database connection
      getConnection();
      musicStmt = con.prepareStatement(selectQuery);

      // Execute the query; results are in ResultSet rs
      rs = musicStmt.executeQuery();

      // Loop through ResultSet and create RecordingVO
      // object for each row

      while (rs.next())
      {
        // Create RecordingVO object and add to mList
        // Use getInt() and getString() to pull data from
        // ResultSet rs.

        // The parameter numbers match the order of the
        // field names in the query statement.
        mList.add(new RecordingVO(
          rs.getInt(1),       // RecordingID
          rs.getString(2),    // RecordingTitle
          rs.getString(9),     // RecordingArtistName
          rs.getString(10),    // MusicCategory
          rs.getString(5),     // RecordingLabel
          rs.getInt(7)));       // NumberofTracks
      }

    } catch(SQLException ex) {
      throw new MusicDAOSysException(
        "SQLException reading recording data:
" +
        ex.getMessage());

    } finally {
      closeResultSet(rs);
      closeStatement(musicStmt);

      // Release the database connection
      disConnect();
    }
    return mList;
  }

  public ArrayList dbLoadTrackList(RecordingVO rec)
         throws MusicDAOSysException {

    ArrayList tList = new ArrayList();

    // Select all records from Tracks table
    // where the RecordingID is the same as the
    // In parameter
    // Order the records by TrackNumber field

    String selectQuery = "Select * From Tracks " +
      "Where RecordingID = ? Order By TrackNumber";

    PreparedStatement trackStmt = null;
    ResultSet rs = null;
    try {
      // Obtain a database connection
      getConnection();

      trackStmt = con.prepareStatement(selectQuery);

      // Set In parameter to RecordingID in
      // RecordingVO argument
      trackStmt.setInt(1, rec.getRecordID());

      // Execute query; return in ResultSet rs
      rs = trackStmt.executeQuery();

      while (rs.next())
      {
        // Loop through ResultSet and create TrackVO
        // Add to tList ArrayList
        tList.add(new TrackVO(
        rs.getInt(2),            // trackNumber
        rs.getString(3),         // title
        rs.getString(4)));       // trackLength
      }

    } catch (SQLException ex) {
      throw new MusicDAOSysException(
        "SQLException while reading track data:
"
        + ex.getMessage());

    } finally {
      closeResultSet(rs);
      closeStatement(trackStmt);
      // Release the database connection
      disConnect();
    }
    return tList;
  }
} // MusicDAOCloudscape

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.172.82