6.9. Storing Second Class Objects via Stringification

Object fields that are neither instances of PersistenceCapable nor one of the default persistent types mandated by the JDO specification (primitives and wrappers, String, Locale, etc.) are, by default, persisted to blob fields by serializing the object. This has a number of drawbacks:

Kodo offers the ability to write custom field mappings in order to have complete control over the mechanism with which fields are stored, queried, and loaded from the datastore. Furthermore, since simple types can often be represented as a string (such as java.net.URL), Kodo provides metadata extensions to allow the simple storage of classes via stringification/destringification without having to write any custom code.

The field-level externalizer metadata extension will contain the name of a method that will be invoked to convert the field into a String for storage in the database. This extension can take either the name of a non-static method, which will be invoked on the field value, or a static method, which will be invoked with the field value as a parameter. Static methods can be specified using the format "ClassName.method".

The field-level factory metadata extension will contain the name of a method that will be invoked to instantiate the field from the String stored in the database. This extension will take a static method name, which will be invoked with the String value of the datastore value, and must return an instance of the field type. If this extension is not specified, Kodo will use the constructor of the field type that takes a single String argument, or will throw a JDOFatalException if no constructor with that signature exists.

Example 6.3. Metadata for persisting field of type java.lang.Class

<!-- A field of type Class, stored by the class name. It
     will be saved with the getName method, and
     re-instantiated with the static forName factory method. -->
<field name="clas" default-fetch-group="true">
    <extension vendor-name="kodo" key="externalizer" value="getName" />
    <extension vendor-name="kodo" key="factory" value="forName" />
</field>

Example 6.4. Metadata for persisting field of type java.net.URL

<!-- The field of type jave.net.URL can be externalized to
     a String with the URL.toExternalForm method. Since we
     do not specify the "factory" extension, the String
     constructor for URL will be used to recreate it.
     Since the constructor does not deal with nulls, we
     specify that the JDO layer should throw an exception    
     when we attempt to store a null value for the field. -->
<field name="url" null-value="exception" default-fetch-group="true">
    <extension vendor-name="kodo" key="externalizer" value="toExternalForm" />
</field>

Example 6.5. Metadata for persisting field of type java.net.InetAddress

<!-- The field of type java.net.InetAddress can
     be externalized with the InetAddress.getHostAddress()
     method, and restored with InetAddress.getByName(String).
     Note that we could also store the host name by having
     the externalizer instead by InetAddress.getHostName(), but
     that would result in very slow DNS queries every time the
     object is resored. Nulls are not handled by the
     factory, so we set them to throw an exception at
     the JDO level. Since the factory method might be slow,
     we set the default-fetch-group to be false, so the
     address will not be instantiated until it is needed. -->
<field name="address" null-value="exception"
    default-fetch-group="false">
    <extension vendor-name="kodo" key="externalizer" value="getHostAddress" />
    <extension vendor-name="kodo" key="factory" value="InetAddress.getByName" />
</field>

Example 6.6. Metadata for persisting field of type java.util.TimeZone

<!-- The field of type java.util.TimeZone can be
     constructed with the static TimeZone.getTimeZone
     factory, and externalized via TimeZone.getID. Note that
     that TimeZone is an abstract class; since we have
     a static factory method, this isn't a problem. -->
<field name="timeZone" null-value="exception"
    default-fetch-group="true">
    <extension vendor-name="kodo" key="externalizer" value="getID" />
    <extension vendor-name="kodo" key="factory" value="TimeZone.getTimeZone" />
</field>

Example 6.7. Metadata for persisting field of type java.io.File

<!-- java.io.File objects are represented by the
     externalizer File.getAbsolutePath. File's String
     constructor will be used to restore the File from
     the String. -->
<field name="file" null-value="exception"
    default-fetch-group="true">
    <extension vendor-name="kodo" key="externalizer" value="getAbsolutePath" />
</field>

Note that by default, stringification fields are not in the default fetch group, so they will be loaded in a secondary statement when first accessed. This can be overridden by setting the default-fetch-group attribute of the field to be true.

[Note]Note

As with fields that are stored through serialization, it is not possible to perform queries against fields that are stored via stringification. Furthermore, since access to the field is not mediated, changes to the internal state of the field will not be detected by the JDO's StateManager, and thus the field will only be marked dirty if the field is explicitely dirtied or reset.