Kodo enhances the specification's support for persistent fields in many ways. This section documents aspects of Kodo's persistent field handling that may affect the way you design your persistent classes.
While the JPA specification says that you should not use rolled
back objects, such objects are perfectly valid in Kodo. You can
control whether the objects' managed state is rolled back to its
pre-transaction values with the
kodo.RestoreState
configuration property.
none
does not roll back state (the object
becomes hollow, and will re-load its state the next time it
is accessed), immutable
restores immutable values
(primitives, primitive wrappers, strings) and clears mutable values
so that they are reloaded on next access, and all
restores all managed values to their pre-transaction state.
JDO requires that all immutable fields be restored to their
pre-transaction state when a transaction rollback occurs. Kodo
also has the ability to restore the state of mutable fields
including collections, maps, and arrays. To have the state of
mutable fields restored on rollback, set the
kodo.RestoreState
or javax.jdo.option.RestoreValues
configuration property to all
.
When loading data into a field, Kodo examines the value you assign the field in your declaration code or in your no-args constructor. If the field value's type is more specific than the field's declared type, Kodo uses the value type to hold the loaded data. Kodo also uses the comparator you've initialized the field with, if any. Therefore, you can use custom comparators on your persistent field simply by setting up the comparator and using it in your field's initial value.
Kodo also attempts to preserve the Java type and order of all
collections declared in your JDO metadata. By default, Kodo uses
an ordering column to maintain sequencing for all fields declared
to be a List
or array type. See
Section 7.1.3, “
JDO Forward Mapping Hints
” for how to prevent the
creation of ordering columns during forward mapping.
Kodo does not automatically add an ordering column to
List
and array fields under JPA metadata. When you use
JPA metadata, Kodo respects JPA's default of leaving collections
and arrays without database sequencing.
Example 5.9. Using Initial Field Values
public class Company { // Kodo will detect the custom comparator in the initial field value // and use it whenever loading data from the database into this field private Collection employeesBySal = new TreeSet (new SalaryComparator ()); private Map departments; public Company { // or we can initialize fields in our no-args constructor; even though // this field is declared type Map, Kodo will detect that it's actually // a TreeMap and use natural ordering for loaded data departments = new TreeMap (); } // rest of class definition... }
Kodo's support for the java.util.Calendar
type will store only the Date
part of the
field, not the TimeZone
associated with the
field. When loading the date into the Calendar
field, Kodo will use the TimeZone
that was used to initialize the field.
![]() | Note |
---|---|
Kodo will automatically track changes made via modification
methods in fields of type
|
At runtime, the values of all mutable second class object fields in persistent and transactional objects are replaced with implementation-specific proxies. On modification, these proxies notify their owning instance that they have been changed, so that the appropriate updates can be made on the datastore.
Most proxies only track whether or not they have been modified. Smart proxies for collection and map fields, however, keep a record of which elements have been added, removed, and changed. This record enables the Kodo runtime to make more efficient database updates on these fields.
When designing your persistent classes, keep in mind that
you can optimize for Kodo smart proxies by using fields of type
java.util.Set
,
java.util.TreeSet
, and
java.util.HashSet
for your collections
whenever possible. Smart proxies for these types are more
efficient than proxies for List
s. You
can also design your own smart proxies to further optimize Kodo
for your usage patterns. See the section on
custom proxies
for details.
Under standard ORM behavior, traversing a persistent collection or map field brings the entire contents of that field into memory. Some persistent fields, however, might represent huge amounts of data, to the point that attempting to fully instantiate them can overwhelm the JVM or seriously degrade performance.
Kodo uses special proxy types to represent these "large result
set" fields. Kodo's large result set proxies do not cache
any data in memory. Instead, each operation on the proxy
offloads the work to the database and returns the proper result.
For example, the contains
method
of a large result set collection will perform a
SELECT COUNT(*)
query with the proper WHERE
conditions to find out if the given element exists
in the database's
record of the collection. Similarly, each time you obtain
an iterator Kodo performs the proper query using the current
large result set settings, as discussed in the
JDBC chapter. As you
invoke Iterator.next
, Kodo
instantiates the result objects on-demand.
You can free the resources used by a large result set iterator
by passing it to the static
KodoPersistence.close
or
KodoJDOHelper.close
method.
Example 5.10. Using a Large Result Set Iterator
JPA:
import kodo.persistence.*; ... Collection employees = company.getEmployees (); // employees is a lrs collection Iterator itr = employees.iterator (); while (itr.hasNext ()) process ((Employee) itr.next ()); KodoPersistence.close (itr);
JDO:
import kodo.jdo.*; ... Collection employees = company.getEmployees (); // employees is a lrs collection Iterator itr = employees.iterator (); while (itr.hasNext ()) process ((Employee) itr.next ()); KodoJDOHelper.close (itr);
You can also add and remove from large result set proxies, just as with standard fields. Kodo keeps a record of all changes to the elements of the proxy, which it uses to make sure the proper results are always returned from collection and map methods, and to update the field's database record on commit.
In order to use large result set proxies in JPA, add the
kodo.persistence.LRS
annotation to the
persistent field.
In order to use large result set proxies in JDO, set the
each large result set field's
lrs
metadata extension to
true
.
The following restrictions apply to large result set fields:
The field must be declared as either a
java.util.Collection
or
java.util.Map
. It cannot be
declared as any other type, including any sub-interface
of collection or map, or any concrete collection or map
class.
The field cannot have an externalizer (see Section 5.5.5, “Externalization”).
Because they rely on their owning object for context, large result set proxies cannot be transferred from one persistent field to another. The following code would result in an error on commit:
Collection employees = company.getEmployees () // employees is a lrs collection company.setEmployees (null); anotherCompany.setEmployees (employees);
Example 5.11. Marking a Large Result Set Field
JPA:
import kodo.persistence.*; @Entity public class Company { @ManyToMany @LRS private Collection<Employee> employees; ... }
JDO:
<class name="Company"> <field name="employees"> <collection element-type="Employee"/> <extension vendor-name="kodo" key="lrs" value="true"/> </field> ... </class>
Kodo manages proxies through the
kodo.util.ProxyManager
interface.
Kodo includes a default proxy manager, the
kodo.util.ProxyManagerImpl
(with a plugin
alias name of default
),
that will meet the needs of most users. The default proxy
manager understands the following configuration properties:
TrackChanges
: Whether to use
smart
proxies. Defaults to true
.
AssertAllowedType
: Whether to check
elements of maps and collections as they are added to
make sure they are of the type defined in your metadata.
Defaults to false
.
For custom behavior, Kodo allows you to define your own
proxy classes, and your own proxy manager. See the
kodo.util
package
Javadoc for details on the
interfaces involved, and the utility classes Kodo provides to
assist you.
You can plug your custom proxy manager into the Kodo runtime
through the
kodo.ProxyManager
configuration property.
Section 1.3.6, “Custom Proxies” describes proxy
samples that ship with Kodo.
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. Often, however, a custom mapping is overkill. There is often a simple transformation from a Java field value to its database representation. Thus, Kodo provides the externalization service. Externalization allows you to specify methods that will externalize a field value to its database equivalent on store and then rebuild the value from its externalized form on load.
![]() | Note |
---|---|
Fields of embeddable classes used for |
The
JPA
kodo.persistence.Externalizer
annotation
or
JDO externalizer
metadata extension
sets the name of a method that will be invoked to convert the field
into its external form for database storage. You can specify
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. Each method can also take an
optional
StoreContext
parameter for
access to a persistence context. The return value of the method is
the field's external
form. By default, Kodo assumes that all named methods belong to the
field value's class (or its superclasses). You can, however,
specify static methods of other classes using the format
<class-name>.<method-name>
.
Given a field of type CustomType
that
externalizes to a string, the table below demonstrates several
possible externalizer methods and their corresponding
metadata extensions.
Table 5.1. Externalizer Options
Method | Extension |
---|---|
public String CustomType.toString()
|
JPA:
@Externalizer("toString")
JDO:
<extension vendor-name="kodo" key="externalizer" value="toString"/>
|
public String CustomType.toString(StoreContext ctx)
|
JPA:
@Externalizer("toString")
JDO:
<extension vendor-name="kodo" key="externalizer" value="toString"/>
|
public static String AnyClass.toString(CustomType ct)
|
JPA:
@Externalizer("AnyClass.toString")
JDO:
<extension vendor-name="kodo" key="externalizer" value="AnyClass.toString"/>
|
public static String AnyClass.toString(CustomType ct, StoreContext ctx)
|
JPA:
@Externalizer("AnyClass.toString")
JDO:
<extension vendor-name="kodo" key="externalizer" value="AnyClass.toString"/>
|
The
JPA
kodo.persistence.Factory
annotation
or
JDO factory metadata extension
contains the name of a method that will be invoked to
instantiate the field from the external form stored in
the database. Specify a static method name. The method will
will be invoked with the externalized value and must return
an instance of the field type. The method can also take an optional
StoreContext
parameter for
access to a persistence context.
If a factory is not specified, Kodo will use the constructor
of the field type that takes a single argument of the external
type, or will throw an exception if no constructor with that
signature exists.
Given a field of type CustomType
that
externalizes to a string, the table below demonstrates several
possible factory methods and their corresponding
metadata extensions.
Table 5.2. Factory Options
Method | Extension |
---|---|
public CustomType(String str)
| none |
public static CustomType CustomType.fromString(String str)
|
JPA:
@Factory("fromString")
JDO:
<extension vendor-name="kodo" key="factory" value="fromString"/>
|
public static CustomType CustomType.fromString(String str, StoreContext ctx)
|
JPA:
@Factory("fromString")
JDO:
<extension vendor-name="kodo" key="factory" value="fromString"/>
|
public static CustomType AnyClass.fromString(String str)
|
JPA:
@Factory("AnyClass.fromString")
JDO:
<extension vendor-name="kodo" key="factory" value="AnyClass.fromString"/>
|
public static CustomType AnyClass.fromString(String str, StoreContext ctx)
|
JPA:
@Factory("AnyClass.fromString")
JDO:
<extension vendor-name="kodo" key="factory" value="AnyClass.fromString"/>
|
If your externalized field is not a standard persistent type, you
must explicitly mark it persistent.
In JPA, you can force a persistent field by annotating it with
kodo.persistence.Persistent
annotation.
In JDO, set the field's persistence-modifier
to
true
. If you want the field to be in the default
fetch group, also set its default-fetch-group
metadata attribute to true
. See
Chapter 5, Metadata for complete coverage of JDO
metadata.
![]() | Note |
---|---|
If your custom field type is mutable and is not a standard collection, map, or date class, Kodo will not be able to detect changes to the field. You must mark the field dirty manully, or create a custom field proxy.
See
See Section 5.5.4, “Proxies” for a discussion of proxies. Section 1.3.6, “Custom Proxies” covers Kodo's custom proxy samples. |
You can externalize a field to virtually any value that is supported by Kodo's field mappings (embedded relations are the exception; you must declare your field to be a persistence-capable type in order to embed it). This means that a field can externalize to something as simple as a primitive, something as complex as a collection or map of persistence-capable objects, or anything in between. If you do choose to externalize to a collection or map, Kodo recognizes a family of metadata extensions for specying type information for the externalized form of your fields - see Section 6.4.2.7, “Type”. If the external form of your field is a persistence-capable object, or contains persistence-capable objects, Kodo will correctly include the objects in its persistence-by-reachability algorithms and its delete-dependent algorithms.
The example below demonstrates a few forms of externalization. See Section 1.3.4, “Using Externalization to Persist Second Class Objects” for a working example of externalizing many different field types.
Example 5.13. Using Externalization
JPA:
import kodo.persistence.*; @Entity public class Magazine { // use Class.getName and Class.forName to go to/from strings @Persistent @Externalizer("getName") @Factory("forName") private Class cls; // use URL.getExternalForm for externalization. no factory; // we can rely on the URL string constructor @Persistent @Externalizer("toExternalForm") private URL url; // use our custom methods; notice how we use the KeyType and ElementType // annotations to specify the metadata for our externalized map @Persistent @Externalizer("Magazine.mapFromCustomType") @Factory("Magazine.mapToCustomType") @KeyType(String.class) @ElementType(String.class) private CustomType customType; public static Map mapFromCustomType (CustomType customType) { ... logic to pack custom type into a map ... } public static CustomType mapToCustomType (Map map) { ... logic to create custom type from a map ... } ... }
JDO:
public class Magazine { private Class cls; private URL url; private CustomType customType; public static Map mapFromCustomType (CustomType customType) { ... logic to pack custom type into a map ... } public static CustomType mapToCustomType (Map map) { ... logic to create custom type from a map ... } ... } <class name="Magazine"> <field name="cls" persistence-modifier="persistent" default-fetch-group="true"> <!-- use Class.getName and Class.forName to go to/from strings --> <extension vendor-name="kodo" key="externalizer" value="getName"/> <extension vendor-name="kodo" key="factory" value="forName"/> </field> <field name="url" persistence-modifier="persistent" default-fetch-group="true"> <!-- use URL.getExternalForm for externalization. no --> <!-- factory; we can rely on the URL string constructor --> <extension vendor-name="kodo" key="externalizer" value="toExternalForm"/> </field> <field name="customType" persistence-modifier="persistent"> <!-- use our custom methods; notice how we use the --> <!-- key-type and value-type extensions to specify --> <!-- the metadata for our externalized map --> <extension vendor-name="kodo" key="externalizer" value="Magazine.mapFromCustomType"/> <extension vendor-name="kodo" key="factory" value="Magazine.mapToCustomType"/> <extension vendor-name="kodo" key="key-type" value="String"/> <extension vendor-name="kodo" key="value-type" value="String"/> </field> ... </class>
You can query externalized fields using parameters. Pass in a value of the field type when executing the query. Kodo will externalize the parameter using the externalizer method named in your metadata, and compare the externalized parameter with the value stored in the database. As a shortcut, Kodo also allows you to use parameters or literals of the field's externalized type in queries, as demonstrated in the example below.
![]() | Note |
---|---|
Currently, queries are limited to fields that externalize to a primitive, primitive wrapper, string, or date types, due to constraints on query syntax. |
Example 5.14. Querying Externalization Fields
Assume the Magazine
class has the
same fields as in the previous example.
JPA:
// you can query using parameters Query q = em.createQuery ("select m from Magazine m where m.url = :u"); q.setParameter ("u", new URL ("http://www.solarmetric.com")); List results = q.getResultList (); // or as a shortcut, you can use the externalized form directly q = em.createQuery ("select m from Magazine m where m.url = 'http://www.solarmetric.com'"); results = q.getResultList ();
JDO:
// you can query using parameters Query q = pm.newQuery (Magazine.class, "url == :u"); List results = (List) q.execute (new URL ("http://www.solarmetric.com")); // or as a shortcut, you can use the externalized form directly q = pm.newQuery (Magazine.class, "url == 'http://www.solarmetric.com'"); results = (List) q.execute ();
Externalization often takes simple constant values and
transforms them to constant values of a different type.
An example would be storing a boolean
field as a char
, where true
and false
would be stored
in the database as 'T'
and
'F'
respectively.
Kodo allows you to define these simple translations in metadata, so that the field behaves as in full-fledged externalization without requiring externalizer and factory methods. External values supports translation of pre-defined simple types (primitives, primitive wrappers, and Strings), to other pre-defined simple values.
Use the
JPA
kodo.persistence.ExternalValues
annotation
or
JDO external-values
metadata extension
to define external value translations. The
values are defined in a format similar to that of
configuration
plugins, except that the value pairs represent Java and
datastore values. To convert the Java boolean
values of true
and false
to the character values T
and F
, for example, you would use the extension value:
true=T,false=F
.
If the type of the datastore value is different from
the field's type, use the
JPA
kodo.persistence.Type
annotation
or
JDO type
metadata extension
to define the datastore type.
Example 5.15. Using External Values
This example uses external value translation to transform a string field to an integer in the database.
JPA:
public class Magazine { @ExternalValues({"SMALL=5", "MEDIUM=8", "LARGE=10"}) @Type(int.class) private String sizeWidth; ... }
JDO:
public class Magazine { private String sizeWidth; ... } <class name="Magazine"> <field name="sizeWidth"> <extension vendor-name="kodo" key="external-values" value="SMALL=5,MEDIUM=8,LARGE=10"/> <extension vendor-name="kodo" key="type" value="int"/> </field> ... </class>
![]() ![]() |