12.6 First- and Second-Class Objects

JDO provides a natural mapping of your object model to an underlying datastore using different architectures. Most of the differences between datastores are handled for you automatically. In JDO, you identify the classes of your object model that should be stored in the datastore. Instances of these classes are stored with unique identifiers and can be queried efficiently using the values of their fields. Relationships between instances are modeled as references or collections.

In Java, your application classes, such as Movie and Role, and system-defined classes, such as java.util.Date and java.lang.Integer, are not treated differently. They are all referenceable objects in memory. However, there is a fundamental difference between these objects from the standpoint of JDO and most datastores.

The instances of your persistent classes that you would like to be referenced by two or more instances in the datastore are called first-class objects (FCOs). They each have a unique identity in the datastore, they can be queried, and they can be deleted under application control. In addition, the JDO runtime environment guarantees that only a single instance of an FCO with a durable identity is instantiated in memory for a given PersistenceManager cache.

JDO also supports second-class objects (SCOs), which represent values. They do not represent entities that you would want to reference in the datastore. A second-class object is associated and stored as part of a single first-class object. The second-class object is embedded in the first-class object that references and owns it. The class of a first-class object has a field that references the second-class object. This field is declared in the metadata as embedded to indicate that it refers to a second-class object.

An SCO instance represents a value. It may have an object representation in Java, but in the datastore it is not a distinct, referenceable piece of data. In a relational datastore, an SCO usually is mapped to one or more columns of a table. These columns are placed in the table in which the owning FCO is stored. Java types such as int, Integer, String, Date, and BigInteger represent values. Except for int, these types are all considered objects in Java. They are used as the types of fields in your persistent classes. In the datastore, they are stored as values with their associated persistent class instance.

An SCO instance tracks all changes that are made to itself and notifies its owning FCO that it has been changed. A change to an SCO is reflected as a change to its owning FCO. If an FCO instance is in the persistent-clean state, when one of its associated SCO instances changes, it transitions to the persistent-dirty state. When an FCO instance is instantiated in the JVM, fields declared as embedded are assigned SCO instances that track changes made to themselves and notify their owning FCO that they have been changed.

If a persistent class has a field of type int and you change the value of this field in an instance, the JDO implementation automatically marks the instance as dirty. Similarly, if the persistent class has a Date field that references a Date object, and you change the Date object's value via setTime( ), the Date object notifies the persistent class instance that its value has been changed. In the datastore, the Date field is stored as a value in the instance (e.g., in a TIMESTAMP column in a relational datastore). In JDO, an SCO allows specific instances of classes to behave more like primitive values that are contained in an object, rather than as separate referenceable objects. While they are still separate referenceable objects in Java, they are not separate and referenceable in the datastore.

Some of the system-defined classes that are used as field types in your object model are most naturally modeled as second-class objects when stored in the datastore. Table 12-1 identifies the system-defined classes that all JDO implementations support as second-class objects. Fields of these types are embedded by default and many implementations support them only as second-class objects.

Table 12-1. System-defined types that default to second-class objects

Primitives

java.lang

java.util

java.math

boolean
Boolean
Date
BigInteger
byte
Byte
Locale
BigDecimal
short
Short
ArrayList
 
char
Character
Collection
 
int
Integer
HashMap
 
long
Long
HashSet
 
float
Float
Hashtable
 
double
Double
LinkedList
 
 
String
List
 
 
Number
Map
 
  
TreeMap
 
  
TreeSet
 
  
Set
 
  
Vector
 

When discussing second-class objects, there are two kinds of classes to consider: mutable and immutable. A mutable class provides methods to change the value of an instance; an immutable class maintains a value that cannot be changed. JDO supports the following immutable classes:

java.lang package

Boolean, Character, Byte, Short, Integer, Long, Float, Double, and String

java.util package

Locale

java.math package

BigDecimal and BigInteger

JDO and Java support and encourage sharing instances for fields of these immutable classes. However, you should compare the equality of the fields with the equals( ) method; you should not compare them by applying the == operator to their references.

Setting or defaulting the embedded attribute to "true" for fields of the system-defined types listed in Table 12-1 implies containment. You should not delete instances of these classes from the datastore; the JDO implementation deletes them automatically when the owning instance is deleted. In fact, passing an instance of one of these types to deletePersistent( ) causes a JDOUserException to be thrown. You should only pass instances of your persistent classes to deletePersistent( ).

Implementations support mutable system-defined classes by defining a new class that extends the system-defined class. The new class provides its own implementation of each method that alters the state of the object in the base class. These redefined methods notify the owning FCO instance that the SCO instance has changed and call the corresponding method in the base class to perform the state change (e.g., Date.setTime( )). Therefore, you should not depend on knowing the exact class of a system-defined class instance. The JDO implementation may substitute an SCO instance with an instance of a subclass that has the same value when they are compared by calling equals( ). But you are guaranteed that the actual class of the instance is assignment-compatible with the field's declared type.

In order to make your application code and persistent classes portable across multiple JDO implementations, there are a few simple rules to follow:

  • Do not assign the same instance of a system-defined mutable class to multiple persistent fields. Instead, make a copy of a mutable instance before assigning it to another persistent field.

  • Initialize collection fields in a class's constructor and do not assign a new value to the collection field. To clear the contents of the collection, call the clear( ) method to remove the elements instead of assigning an empty collection, or null, to the field.

  • Do not expose second-class objects as public fields or have a method that returns a reference to a field, because you cannot control when they may be used, in or out of a transaction.

12.6.1 Specifying a Second-Class Object

An instance becomes a second-class object if it is referenced by a field that you have declared in the metadata as embedded. You specify whether a field is embedded by using the field element's embedded attribute. When a reference field has an embedded attribute value of "true", the referenced object is a second-class object and its state is embedded within the owning object that refers to it. The embedded attribute defaults to "true" for a field of a type listed in Table 12-1.

Let's consider the following revisions to the metadata for some of the classes in the com.mediamania.store package, which we illustrated in Figure 4-4:

    <package name="com.mediamania.store" >
        <class name="Customer" >
            <field name="currentRentals">
                <collection element-type="Rental"/>
            </field>
            <field name="transactionHistory">
                <collection element-type="Transaction"/>
            </field>
            <field name="address" embedded="true" />     [1]
        </class>
        <class name="Address" />
        <class name="Rental"
               persistence-capable-superclass="Transaction">
            <field name="rentalCode" embedded="true" />     [2]
        </class>
        <class name="MediaItem" >
            <field name="rentalItems">
                <collection element-type="RentalItem"/>
            </field>
        </class>
        <class name="RentalCode" />
    </package>

Line [1] declares that the address field should be embedded. Both the Rental and MediaItem classes have a reference to a RentalCode instance. On line [2], we declare that the rentalCode field in the Rental instance is embedded. However, we do not declare that the rentalCode field is embedded in MediaItem. The RentalCode instances referenced by MediaItem instances will be found in the extent maintained for the RentalCode class. A Rental instance will have its own copy of a RentalCode instance referenced by its rentalCode field; this RentalCode instance does not have an identity and may have the same value as a RentalCode instance in the extent. Such an approach may be valuable to this application, because it can preserve for historical record-keeping purposes the specific RentalCode value used for a Rental, yet have all the MediaItem instances reference the latest values of a RentalCode instance that is shared by all MediaItem instances in the datastore.

In a relational JDO implementation, an embedded object may be represented by columns for its fields in the table of the referencing class. For example, the Rental class declares that the rentalCode field, referring to an instance of RentalCode, should be embedded. The RentalCode class contains several fields: code, numberOfDays, cost, and lateFeePerDay. The table that contains the fields of the Rental class would have a column for each of these RentalCode fields.

12.6.2 Embedding Collection Elements

You specify a collection field as embedded by using the embedded attribute in the collection's field element. You can also specify that the collection's elements should be embedded within the collection.

The collection and array metadata elements have an embedded-element attribute to specify whether the collection elements' values should be embedded with the collection instance in the datastore, instead of as separate FCO instances. This attribute defaults to "false" for persistent classes and interface types and "true" for other types.

You use the embedded-key and embedded-value attributes in the map metadata element to specify whether the map's key and value should be embedded. These attributes default to "false" for persistent classes and interface types and "true" for other types.

12.6.3 Persistent Classes as Second-Class Objects

Many JDO implementations can support your persistent classes as second-class objects, but this support is not a required feature in JDO 1.0.1. For implementations that support SCO instances of your persistent classes, both FCO and SCO instances of a specific persistent class may be possible, but this depends on the implementation. The persistent classes that you define can be either mutable or immutable.

The behavior of SCOs for your persistent classes may not be consistent relative to extents and queries. If the persistent class has a maintained extent, the FCO instances will be in the extent, but an implementation may or may not place the SCO instances in the extent. Furthermore, if a field of one of your persistent classes is an SCO instance, an implementation may or may not be able to access it in a query.

You cannot rely on the automatic deletion of SCO instances for embedded fields of your persistent classes; some implementations will delete them, while others will not. You can always delete instances of your persistent classes explicitly, whether or not they are embedded. We recommend that you delete them explicitly; this will be portable across all JDO implementations.

Using one of your persistent classes as an SCO may offer you some performance and modeling advantages, but there is a tradeoff: they will lack portability and consistency, relative to extents and queries. If you intend to use them, you should verify that your JDO implementation supports them. Here, we describe the behavior of second-class objects with the assumption that the JDO implementation supports them for your persistent classes. If you do not have a specific need to define a persistent class and use it as a second-class object and you want to have a portable application, then you should avoid using instances of your persistent classes as second-class objects, in which case you can skip the remainder of this chapter.

12.6.4 Sharing of Instances

The most visible difference to your application between a field that is an FCO or an SCO is in sharing. Multiple FCO instances can have a reference to the same FCO instance and share it. If the referenced FCO instance changes, its changes are visible to all the FCO instances that refer to it.

For example, consider Figure 12-2. If FCO1 is assigned to a persistent field in FCO2 and FCO3, then any changes to instance FCO1 will be visible to FCO2 and FCO3. FCO2 and FCO3 will continue to reference FCO1 in the datastore after the transaction commits and will refer to it when they are accessed by subsequent transactions (until the reference to FCO1 is changed).

Figure 12-2. Sharing of an FCO instance
figs/jdo_1202.gif

The same instance of a mutable class can be assigned to the embedded field of multiple FCO instances, but this is nonportable and strongly discouraged. If you assign an instance to an embedded field of multiple persistent-new, persistent-clean, or persistent-dirty FCO instances, the Java identity of the referenced SCO instances might change when the transaction commits. If an assignment is made to an embedded field of a transient instance and the instance subsequently becomes persistent by being passed to makePersistent( ) or through persistence-by-reachability, the embedded field is replaced immediately with a copy of the SCO instance and the instance is no longer shared. Figure 12-3 illustrates the copying that is performed with SCO instances.

Figure 12-3. SCOs can be shared from assignment only until commit or makePersistent( )
figs/jdo_1203.gif