Dependent Value Classes

The EJB specification states that the type of a cmp-field must either be a primitive or a Serializable class. Dependent Value Classes provide a mechanism for mapping more complex Java types onto individual database columns avoiding the need to serialize values into the database.

As example, consider the case of an Order EJB that has two address fields: a billing address and a shipping address. The public interface for this EJB could be defined as:

public interface Order extends EJBLocalObject {
    ...
    public void setShipTo(Address shipAddress);
    public Address getShipTo();
    public void setBillTo(Address billingAddress);
    public Address getBillTo();
    ...
}

For simplicity, we shall use a simple JavaBean for the address object:

public class Address implements Serializable {
    private String street;
    private String city;
    private String postalCode;
    // with get and set methods omitted for clarity
}

Three different options for storing this information in a relational database are discussed below.

Serialization Approach

The simplest approach, at least from the EJB implmentation viewpoint, is to simply serialize the Address objects into the database. The database table would be declared something like:

CREATE TABLE ORDER (
    ...
    SHIP_TO_ADDRESS BLOB,
    BILL_TO_ADDRESS BLOB,
    ...
)

The EJB would access these fields using simple accessors:

    public abstract void setShipTo(Address shipAddress);
    public abstract Address getShipTo();
    public abstract void setBillTo(Address billingAddress);
    public abstract Address getBillTo();

However, this raises several issues from the database management perspective:

  • The stored object will be treated as an opaque array of bytes by the database and can not be used for queries either via JDBC or via other database access tools. This also stops any finders or select methods from operating on the object.
  • The serialized form of the object may be considerably larger than the in-memory representation.
  • The use of BLOB type columns may result in different physical storage of the object; for example, the data may be stored on different physical disks.
  • The use of BLOB type columns may affect transactional or recovery behaviour; some databases do not maintain multiple versions of large objects to support isolation or rollback. Also large objects may not be logged preventing recovery of committed transactions.
  • The values stored in the database may be from multiple versions of the application; the class being serialized should override readObject and allow unmarshalling of older versions.

The inability to write queries against the data is often a major concern. For example, it may be necessary to identify all orders that are being billed to a specific address to detect potential fraud. This cannot be written as a database query, or even as a EJB-QL finder and the only option would be to load every Order and iterate over them.

Standard CMP Field Approach

In order to access individual members of the address objects in the database, the object must be broken out into multiple columns. This would result in a table definition something like:

CREATE TABLE ORDER (
    ...
    SHIPTO_STREET   VARCHAR,
    SHIPTO_CITY     VARCHAR,
    SHIPTO_POSTCODE VARCHAR,
    BILLTO_STREET   VARCHAR,
    BILLTO_CITY     VARCHAR,
    BILLTO_POSTCODE VARCHAR,
    ...
)

The EJB implementation must then map the objects used in its public interface into the individual fields with code like:

    public void setShipTo(Address shipAddress) {
        setShipToStreet(shipAddress.getStreet());
        setShipToCity(shipAddress.getPostalCode());
        setShipToPostalCode(shipAddress.getPostalCode());
    }
    public abstract void setShipToStreet(String street);
    public abstract void setShipToCity(String city);
    public abstract void setShipToPostalCode(String postalCode);


    public Address getShipTo() {
        Address shipTo = new Address();
        shipTo.setStreet(getShipToStreet());
        shipTo.setCity(getShipToCity());
        shipTo.setPostalCode(getShipToPostalCode());
        return shipTo;
    }
    public abstract String getShipToStreet();
    public abstract String getShipToCity();
    public abstract String getShipToPostalCode();

And just as much again for the billing address.

This requires a substantial amount of code to be written which does nothing except pass values between external objects and the CMP accessors. Futher, this must be done for every usage of the class, everywhere. This requires time, is inefficient and is error prone.

DVC Approach

By defining a DVC, the transfer of fields between the object and the individual columns can automatically be performed by CMP. This merges the simplicity of the EJB code from the serialized case with the flexibility of database access from the CMP field case.

A DVC is defined by creating a mapping in jbosscmp-jdbc.xml between a Java class, its properties, and column names in the database. The following XML snippet illustrates a mapping for the Address object:

    <dependent-value-class>
      <description>Default mapping for an Address object</description>
      <class>Address</class> <!-- fully qualified class name -->
      <property>
        <property-name>street</property-name>
        <column-name>STREET</column-name>
      </property>
      <property>
        <property-name>city</property-name>
        <column-name>CITY</column-name>
      </property>
      <property>
        <property-name>postalCode</property-name>
        <column-name>POSTCODE</column-name>
      </property>
    </dependent-value-class>

In order to be used as a DVC, the class must be a regular JavaBean. Specifically, it must:

  • Implement the Serializable interface
  • Be concrete and have a no-argument constructor
  • Provide both set and get accessors for all named properties