Home  Development Resources  EMail 

A Sequence Generator for O/R Mapping

Abstract

A generator for integer sequences with persistent storage in a database specified by EJB resource reference. The generator framework provides a proxy that caches blocks of sequence values and reads from a sequences table only if all cached values are used up. If the proxy needs to access a database, it does so through a client-provided session bean with an EJB resource reference to a sequences data source. As a result, the databases for EJB CMP bean and sequence storage can be configured in the same deployment descriptor.

Integer Sequences in O/R Mapping

In object-relational mapping, data from a relational database is converted to in-memory objects, and vice versa. A prerequisite for mapping is the ability to uniquely identify objects, as well as their relational data representation. For basic O/R mapping, where all rows in one table are mapped to objects of the same class, the primary key or unique identifier of those rows can serve as an object identifier. EJB 2.0 CMP beans are one example of such a simplistic persistence approach, suitable for the use of ascending integer sequences.

A purpose of unique identifier (UID) generators in O/R mapping is to provide key values, before objects enter persistent storage. One solution is to create a table containing one or more rows for named sequences, with counter fields marking the highest value in use. The generation of a UID then requires a read from that table to obtain the next unused value, and an update with an incremented version of the new highest value in use.

    CREATE TABLE SEQUENCES ( SEQ_VAL int(11) NOT NULL default '0',
      SEQ_NAME varchar(50) NOT NULL default '',
      PRIMARY KEY  (SEQ_NAME)
    );
    
    DataSource ds = getDataSource();
    Connection connect = ds.getConnection();
    String sequenceName = "CLIENT_BEAN_TABLE";
    PreparedStatement stmt = connect.prepareStatement(
        "select SEQ_VAL from SEQUENCES where SEQ_NAME = ? for update");
    stmt.setString(1, sequenceName);
    ResultSet rs = stmt.executeQuery();
    if (rs.next()) {
        long uniqueId = rs.getLong(1);
        rs.close();
        stmt.close();
        stmt = connect.prepareStatement(
            "update SEQUENCES set SEQ_VAL = ? where SEQ_NAME = ?");
        stmt.setLong(1, uniqueId + 100);
        stmt.setString(2, sequenceName);
        stmt.execute();
        stmt.close();
    }

For improved performance, blocks of UIDs should be reserved, by incrementing the marker field with the number of values per block, 100 in the example above. To maintain information on reserved values and control access to the sequence, clients must obtain UIDs through a proxy. Calls to the proxy will return one UID at a time, while the proxy compares an internal counter to the highest reserved value and obtains new values from the sequence, when appropriate.

Choosing a Data Source

There is a catch in using an extra table for sequences with EJB CMP beans. The actual database, where beans will be stored, is determined by the deployment descriptor, and an EJB CMP bean cannot execute JDBC commands on its data source. Opening a sequences data source in the context of a CMP bean is not advisable, as this data source would not be specified by deployment descriptor, potentially resulting in separate databases for sequences and bean data. The usual solution for this is to obtain sequence values through a session bean, with an EJB resource reference to the sequences table's target data source. The data sources for both bean data and sequences can then be specified in the same deployment descriptor. However, calling sequences session beans directly from CMP client beans is also not an ideal solution, as direct access to a sequences proxy object would still be faster as invocations on local interfaces, and sequence proxy objects would have to be implemented within the context of the session beans, possibly leading to code duplication.

ComponentsThe aim of this concept is to enable the client to call a sequences generator or proxy from the create method of EJB CMP beans, and still determine the database the sequences table resides in. To achieve this, the generator framework calls a client-provided session bean with EJB resource reference to a data source, when new blocks of values need to be fetched from the sequences table. The session bean will then call back into the sequences module, where a utility for updating sequences is implemented. This utility can now access the data source referenced by the session bean.

Implementation

The sequence proxy objects are created by a sequence factory that implements a mechanism to create and look up sequences, from a reference to the home class of the client-provided session bean, its JNDI/EJB component name and a sequence name. The block size is an optional parameter for how many values are reserved with each update of the sequence table.

    /** Get named sequence.
     * @param homeClass Class of ejb home of generator plugin bean
     * @param jndiName Jndi name of generator plugin bean
     * @param name Sequence name in db sequences table
     * @param blockSize Number of sequence values to get with one database call
     */
    Sequence SequenceFactory.getSequence(
		java.lang.Class homeClass, java.lang.String jndiName,
		java.lang.String name, long blockSize);

In the sequence generator class diagram, the client-provided session bean is denoted by javax.ejb.EJBLocalObject. This is because the session bean does not need to implement any specific interface; the required method "Long getIds(Long numIds, String name)" is called via Java reflection. Moreover, the session bean should be of view-type "local" and reside in the same EJB.jar file as the client CMP beans, since the purpose of the callback mechanism is to store beans and sequences in the same database. The sequence proxy objects are of class Sequence, the method "Long Sequence.getId()" returns a single UID.

Class diagram

Problems

To work with every EJB container the client-provided plug-in must be a stateless session bean, as the EJB specification does not allow to remove a stateful session bean while it is participating in a transaction. This is unfortunate, as only a stateful session bean may implement SessionSynchronization and obtain a transaction committed state from the container. Therefore, the SequenceBeanAdapter either cannot dispose of the plug-in beans it creates, or it cannot detect if the container rolled back updates on the sequences table. Bea Weblogic sticks to the EJB specification and throws RemoveExceptions for stateful session beans, JBoss server does not mind.

Conclusion

The sequence generator classes illustrated in this article allow for high performant retrieval of unique integer identifiers from a data source specified by means of a client-provided session bean plug-in with an EJB resource reference to a data source.

Source Code, Reference Client and Installation

The deployment files and sources below have been tested on JBoss Server 4.0.2. For installation, just place the sequences library crusius-persist.jar into the server's class path and copy usermodelEJB.jar into the deployment directory.

UserModel is a client application for Sequences, with a set of EJB 2.0 CMP beans. UserModel comprises a JUnit init test case that will create a Sequences table with nine named sequences. The client-provided session bean is info.crusius.usermodel.util.ejb.UsermodelSequenceBean.