Fetch groups are sets of fields that load together. They can be used to to pool together associated fields in order to provide performance improvements over standard data fetching. Specifying fetch groups allows for tuning of lazy loading and eager fetching behavior.
The JPA Overview's Section 5.2.6.1, “Fetch Type” describes how to use JPA metadata annotations to control whether a field is fetched eagerly or lazily. Fetch groups add a dynamic aspect to this standard ability. As you will see, Kodo's JPA extensions allow you can add and remove fetch groups at runtime to vary the sets of fields that are eagerly loaded.
The JDO Overview discusses standard JDO fetch group declarations in Section 5.5, “Fetch Group Element”. Chapter 12, FetchPlan covers runtime fetch group behavior.
Fetch groups are a standard feature of JDO, but are not part of the JPA specification. This section describes Kodo's JPA fetch group implementation.
Kodo places any field that is eagerly loaded according to the JPA metadata rules into the built-in default fetch group. As its name implies, the default fetch group is active by default. You may also define your own named fetch groups and activate or deactivate them at runtime, as described later in this chapter. Kodo will eagerly-load the fields in all active fetch groups when loading objects from the datastore.
You create fetch groups with the
org.apache.openjpa.persistence.FetchGroup
annotation.
If your class only has one custom fetch group, you can place this
annotation directly on the class declaration. Otherwise, use the
org.apache.openjpa.persistence.FetchGroups
annotation
to declare an array of individual FetchGroup
values. The FetchGroup
annotation has the
following properties:
String name
: The name of the fetch group.
Fetch group names are global, and are expected to be shared
among classes. For example, a shopping website may use a
detail fetch group in each product class
to efficiently load all the data needed to display a product's
"detail" page. The website might also define a
sparse list fetch group containing only
the fields needed to display a table of products, as in a search
result.
The following names are reserved for use by Kodo:
default
, values
,
all
, none
, and any name
beginning with jdo
, ejb
,
or kodo
.
String[] fetchGroups
: Other fetch groups
whose fields to include in this group.
FetchAttribute[] attributes
: The set of
persistent fields or properties in the fetch group.
As you might expect, listing a
org.apache.openjpa.persistence.FetchAttribute
within a FetchGroup
includes the corresponding
persistent field or property in the fetch group. Each
FetchAttribute
has the following properties:
String name
: The name of the persistent
field or property to include in the fetch group.
recursionDepth
: If the attribute represents a relation, the
maximum number of same-typed relations to eager-fetch from this field. Defaults
to 1. For example, consider an Employee
class with a
manager
field, also of type Employee
.
When we load an Employee
and the
manager
field is in an active fetch group, the recursion depth (along
with the max fetch depth setting, described below) determines whether we only
retrieve the target Employee
and his manager (depth 1),
or whether we also retrieve the manager's manager (depth 2), or the manager's
manager's manager (depth 3), etc. Use -1 for unlimited depth.
Example 5.16. Custom Fetch Group Metadata
Creates a detail fetch group consisting of the
publisher
and articles
relations.
import org.apache.openjpa.persistence.*; @Entity @FetchGroups({ @FetchGroup(name="detail", attributes={ @FetchAttribute(name="publisher"), @FetchAttribute(name="articles") }), ... }) public class Magazine { ... }
A field can be a member of any number of fetch groups. A field can also declare a load fetch group. When you access a lazy-loaded field for the first time, Kodo makes a datastore trip to fetch that field's data. Sometimes, however, you know that whenever you access a lazy field A, you're likely to access lazy fields B and C as well. Therefore, it would be more efficient to fetch the data for A, B, and C in the same datastore trip. By setting A's load fetch group to the name of a fetch group containing B and C, you can tell Kodo to load all of these fields together when A is first accessed.
Use Kodo's
org.apache.openjpa.persistence.LoadFetchGroup
annotation to specify the load fetch group of any persistent field. The value of
the annotation is the name of a declared fetch group whose members should be
loaded along with the annotated field.
Example 5.17. Load Fetch Group Metadata
import org.apache.openjpa.persistence.*; @Entity @FetchGroups({ @FetchGroup(name="detail", attributes={ @FetchAttribute(name="publisher"), @FetchAttribute(name="articles") }), ... }) public class Magazine { @ManyToOne(fetch=FetchType.LAZY) @LoadFetchGroup("detail") private Publisher publisher; ... }
You can control the default set of fetch groups with the
kodo.FetchGroups
configuration property. Set this property to a comma-separated
list of fetch group names.
You can also set the system's default maximum fetch depth with the
kodo.MaxFetchDepth
configuration property. The maximum fetch depth determines how "deep"
into the object graph to traverse when loading an instance. For example, with
a MaxFetchDepth
of 1, Kodo will load at most the target
instance and its immediate relations. With a MaxFetchDepth
of 2, Kodo may load the target instance, its immediate relations, and
the relations of those relations. This works to arbitrary depth. In fact,
the default MaxFetchDepth
value is -1, which symbolizes
infinite depth. Under this setting, Kodo will fetch configured relations
until it reaches the edges of the object graph. Of course, which relation
fields are loaded depends on whether the fields are eager or lazy, and on the
active fetch groups. A fetch group member's recursion depth may also limit
the fetch depth to something less than the configured maximum.
Kodo's OpenJPAEntityManager
and
OpenJPAQuery
extensions to the standard EntityManager
and Query
interfaces provide access to a
org.apache.openjpa.persistence.FetchPlan
object.
The FetchPlan
maintains the set of active fetch groups
and the maximum fetch depth. It begins with the groups and depth defined in the
kodo.FetchGroups
and kodo.MaxFetchDepth
properties, but allows you to add or remove groups and change the
maximum fetch depth for an individual EntityManager
or
Query
through the methods below.
public FetchPlan addFetchGroup (String group); public FetchPlan addFetchGroups (String... groups); public FetchPlan addFetchGroups (Collection groups); public FetchPlan removeFetchGrop (String group); public FetchPlan removeFetchGroups (String... groups); public FetchPlan removeFetchGroups (Collection groups); public FetchPlan resetFetchGroups (); public Collection<String> getFetchGroups (); public void clearFetchGroups (); public FetchPlan setMaxFetchDepth (int depth); public int getMaxFetchDepth ();
Chapter 9, Runtime Extensions details the
OpenJPAEntityManager
, OpenJPAQuery
, and
FetchPlan
interfaces.
Example 5.18. Using the FetchPlan
import org.apache.openjpa.persistence.*; ... OpenJPAQuery oq = OpenJPAPersistence.cast(em.createQuery(...)); oq.getFetchPlan().setMaxFetchDepth(3).addFetchGroup("detail"); List results = oq.getResultList();
In JDO, you can override the global FetchGroups
property at runtime using the FetchPlan
of
individual PersistenceManager
s,
Queries
, and
Extent
s. See
Chapter 12, FetchPlan for details. The extended
KodoFetchPlan
interface adds an additional useful
methods for manipulating fetch groups:
public void resetGroups ();
resetGroups
resets the active fetch group
set to the groups defined in the kodo.FetchGroups
configuration property. Chapter 9, Runtime Extensions provides
more information on Kodo extensions to standard JDO interfaces.
In addition to controlling fetch configuration on a per-fetch-group basis, you can configure Kodo to include particular fields in the current fetch plan. This allows you to add individual fields that are not in the default fetch group or in any other currently-active fetch groups to the set of fields that will be eagerly loaded from the database.
JPA FetchPlan
methods:
public FetchPlan addField (String field); public FetchPlan addFields (String... fields); public FetchPlan addFields (Class cls, String... fields); public FetchPlan addFields (Collection fields); public FetchPlan addFields (Class cls, Collection fields); public FetchPlan removeField (String field); public FetchPlan removeFields (String... fields); public FetchPlan removeFields (Class cls, String... fields); public FetchPlan removeFields (Collection fields); public FetchPlan removeFields (Class cls, Collection fields); public Collection<String> getFields (); public void clearFields ();
JDO KodoFetchPlan
methods:
public KodoFetchPlan setFields (String[] fields); public KodoFetchPlan setFields (Class cls, String[] fields); public KodoFetchPlan setFields (Collection fields); public KodoFetchPlan setFields (Class cls, Collection fields); public KodoFetchPlan addField (String field); public KodoFetchPlan addField (Class cls, String field); public KodoFetchPlan removeField (String field); public KodoFetchPlan removeField (Class cls, String field); public boolean hasField (Class cls, String field); public Collection getFields (); public KodoFetchPlan clearFields ();
The methods that take only string arguments use the fully-qualified
field name, such as org.mag.Magazine.publisher
.
Similarly, getFields
returns the set of
fully-qualified field names. In all methods, the named field
must be defined in the class specified in the invocation, not a
superclass. So, if the field publisher
is
defined in base class Publication
rather than
subclass Magazine
, you must
invoke addField (Publication.class, "publisher")
and not addField (Magazine.class, "publisher")
.
This is stricter than Java's default field-masking algorithms, which
would allow the latter method behavior if Magazine
did not also define a field called publisher
.
In order to avoid the cost of reflection, Kodo does not perform any validation of the field name / class name pairs that you put into the fetch configuration. If you specify non-existent class / field pairs, nothing adverse will happen, but you will receive no notification of the fact that the specified configuration is not being used.
Example 5.19. Adding an Eager Field
JPA:
import org.apache.openjpa.persistence.*; ... OpenJPAEntityManager oem = OpenJPAPersistence.cast (em); oem.getFetchPlan ().addField (Magazine.class, "publisher"); Magazine mag = em.find (Magazine.class, magId);
JDO:
import kodo.jdo.*; ... PersistenceManager pm = ...; KodoFetchPlan fetch = (KodoFetchPlan) pm.getFetchPlan (); fetch.addField (Magazine.class, "publisher"); Magazine mag = (Magazine) pm.getObjectById (magId);
JPA defaults to eagerly loading direct relations
to other persistence-capable objects. JDO defaults to lazily
loading direct these relations. Kodo respects the defaults of
the metadata format you choose to use. If you use JPA
annotations for metadata, Kodo will include
@OneToOne
and @ManyToOne
relations in the default fetch group unless you explicitly set
fetch=FetchType.LAZY
. If you use JDO
metadata, Kodo will exclude direct relations from the default
fetch group unless you explicitly set
default-fetch-group="true"
.
Even when a direct relation is not eagerly fetched, Kodo selects the foreign key columns and caches the values. This way when you do traverse the relation, Kodo can often find the related object in its cache, or at least avoid joins when loading the related object from the database.
In JDO, you can force Kodo not to select the foreign key values
by explicitly setting default-fetch-group="false"
for the field in your JDO metadata.
The above implicit foreign key-selecting behavior does not always apply when the relation is in a subclass table. If the subclass table would not otherwise be joined into the select, Kodo avoids the extra join just to select the foreign key values.