5 Understanding Descriptors
EclipseLink uses descriptors to store the information that describes how an instance of a particular class can be represented by a data source.
Descriptors own mappings that associate class instance variables with a data source and transformation routines that are used to store and retrieve values. As such, the descriptor acts as the connection between a Java object and its data source representation.
Common Descriptor Concepts
Many concepts are common to Object-Relational and MOXy descriptors.
Descriptor Architecture
A descriptor stores all the information describing how an instance of a particular object class can be represented in a data source.
The Descriptor API can be used to define, or amend EclipseLink descriptors through Java code. The Descriptor API classes are mainly in the org.eclipse.persistence.descriptors package.
EclipseLink descriptors may contain the following information:
-
The persistent Java class it describes and the corresponding data source (database tables or XML complex type interaction)
-
A collection of mappings, which describe how the attributes and relationships for that class are represented in the data source
-
The primary key information (or equivalent) of the data source
-
A list of query keys (or aliases) for field names
-
Information for sequence numbers
-
A set of optional properties for tailoring the behavior of the descriptor, including support for caching refresh options, identity maps, optimistic locking, the event manager, and the query manager
There is a descriptor type for each data source type that EclipseLink supports. In some cases, multiple descriptor types are valid for the same data source type. The type of descriptor you use determines the type of mappings that you can define.
Descriptors and Inheritance
Inheritance describes how a derived (child) class inherits the characteristics of its superclass (parent).
You can use descriptors to describe the inheritance relationships between classes in relational and XML projects.
In the descriptor for a child class, you can override mappings that have been specified in the descriptor for a parent class, or map attributes that have not been mapped at all in the parent class descriptor.
This figure illustrates the Vehicle object model–a typical Java inheritance hierarchy. The root class Vehicle contains two branch classes: FueledVehicle and NonFueledVehicle. Each branch class contains a leaf class: Car and Bicycle, respectively.
EclipseLink recognizes the following three types of classes in an inheritance hierarchy:
-
The root class stores information about all instantiable classes in its subclass hierarchy. By default, queries performed on the root class return instances of the root class and its instantiable subclasses. However, the root class can be configured so queries on it return only instances of itself, without instances of its subclasses.
For example, the Vehicle class in this figure is a root class.
-
Branch classes have a persistent superclass and also have subclasses. By default, queries performed on the branch class return instances of the branch class and any of its subclasses. However, as with the root class, the branch class can be configured so queries on it return only instances of itself without instances of its subclasses.
For example, the FueledVehicle class in this figure is a branch class.
-
Leaf classes have a persistent superclass in the hierarchy but do not have subclasses. Queries performed on the leaf class can only return instances of the leaf class.
For example, the Car class in this figure is a leaf class.
In the descriptor for a child class, you can override mappings that have been specified in the descriptor for a parent class, or map attributes that have not been mapped at all in the parent class descriptor.
Specifying a Class Indicator
When configuring inheritance, you configure the root class descriptor with the means of determining which subclasses it should instantiate.
You can do this in one of the following ways:
-
Using Class Indicator Fields
-
Using Class Extraction Methods
Note:
All leaf classes in the hierarchy must have a class indicator and they must have the same type of class indicator (field or class extraction method).
Note:
If the indicator field is part of the primary key, define a write-only transformation mapping for the indicator field.
You can use strings or numbers as values in the class indicator field.
The root class descriptor must specify how the value in the class indicator field translates into the class to be instantiated.
Using Class Extraction Methods
You can define a Java method to compute the class indicator based on any available information in the object's data source record. Such a method is called a class extraction method.
Using a class extraction method, you do not need to include an explicit class indicator field in your data model and you can handle relationships that are too complex to describe using class indicator fields.
A class extraction method must have the following characteristics:
-
it must be defined on the root descriptor's class;
-
it must be static;
-
it must take a Record as an argument;
-
it must return the java.lang.Class object to use for the Record passed in.
You may also need to define only-instances and with-all-subclasses expressions. If you use a class extraction method, then you must provide EclipseLink with expressions to correctly filter sibling instances for all classes that share a common table.
When configuring inheritance using a class extraction method, EclipseLink does not generate SQL for queries on the root class.
Inheritance and Primary Keys
For relational projects, EclipseLink assumes that all of the classes in an inheritance hierarchy have the same primary key, as set in the root descriptor.
Single and Multi-Table Inheritance
In a relational project, you can map your inheritance hierarchy to a single table or to multiple tables.
Aggregate and Composite Descriptors and Inheritance
You can designate relational descriptors as aggregates. XML descriptors are always composites (see Descriptors and Aggregation).
When configuring inheritance for a relational aggregate descriptor, all the descriptors in the inheritance tree must be aggregates. The descriptors for aggregate and non-aggregate classes cannot exist in the same inheritance tree.
When configuring inheritance for an XML descriptor, because all XML descriptors are composites, descriptor type does not restrict inheritance.
Descriptors and Aggregation
Two objects—a source (parent or owning) object and a target (child or owned) object—are related by aggregation if there is a strict one-to-one relationship between them, and all the attributes of the target object can be retrieved from the same data source representation as the source object. This means that if the source object exists, then the target object must also exist, and if the source object is destroyed, then the target object is also destroyed.
In this case, the descriptors for the source and target objects must be designated to reflect this relationship.
The EJB 3.0 specification does not support nested aggregates.
Descriptor Customization
You can customize a descriptor at run time by specifying a descriptor customizer.
A descriptor customizer is a a Java class that implements the org.eclipse.persistence.config.DescriptorCustomizer interface and provides a default (zero-argument) constructor.
You use a descriptor customizer to customize a descriptor at run time through code API similar to how you use an amendment method to customize a descriptor. See Amendment Methods.
Amendment Methods
You can associate a static Java method that is called when a descriptor is loaded at run time.
This method can amend the run-time descriptor instance through the descriptor Java code API. The method must be public static and take a single parameter of type org.persistence.descriptors.structures.ClassDescriptor. In the implementation of this method, you can configure advanced features of the descriptor using any of the public descriptor and mapping API.
You can only modify descriptors before the session has been connected; you should not modify descriptors after the session has been connected.
Amendment methods can be used with rational descriptors, object-relational data type descriptors, and XML descriptors.
Descriptor Event Manager
In relational projects, EclipseLink raises various instances of DescriptorEvent during the persistence life cycle.
Each descriptor owns an instance of DescriptorEventManager that is responsible for receiving these events and dispatching them to the descriptor event handlers registered with it.
Using a descriptor event handler, you can execute your own application specific logic whenever descriptor events occur, allowing you to take customized action at various points in the persistence life-cycle. For example, using a descriptor event handler, you can do the following:
-
Synchronize persistent objects with other systems, services, and frameworks
-
Maintain nonpersistent attributes of which EclipseLink is not aware
-
Notify other objects in the application when the persistent state of an object changes
-
Implement complex mappings or optimizations not directly supported by EclipseLink mappings
Object-Relational Descriptor Concepts
This section describes the concepts specific to Object-Relational descriptors.
Fetch Groups
Using a fetch group, you can define a subset of an object's attributes and associate the fetch group with either a ReadObjectQuery or ReadAllQuery query.
By default, when you execute an object-level read query for a particular object class, EclipseLink returns all the persistent attributes mapped in the object's descriptor. With this single query, all the object's persistent attributes are defined, and calling their get methods returns the value directly from the object.
When you are interested in only some of the attributes of an object, it may be more efficient to return only a subset of the object's attributes using a fetch group.
Using a fetch group, you can define a subset of an object's attributes and associate the fetch group with either a ReadObjectQuery or ReadAllQuery query. When you execute the query, EclipseLink retrieves only the attributes in the fetch group. EclipseLink automatically executes a query to fetch all the attributes excluded from this subset when and if you call a get method on any one of the excluded attributes.
You can define more than one fetch group for a class. You can optionally designate at most one such fetch group as the default fetch group. If you execute either a ReadObjectQuery or ReadAllQuery query without specifying a fetch group, EclipseLink will use the default fetch group, unless you configure the query otherwise.
Before using fetch groups, Oracle recommends that you perform a careful analysis of system use. In many cases, the extra queries required to load attributes not in the fetch group could well offset the gain from the partial attribute loading.
Fetch groups can be used only with basic mappings configured with FetchType.LAZY (partial object queries).
EclipseLink uses the AttributeGroup that can be used to configure the use of partial entities in fetch, load, copy, and merge operations.
-
Fetch: Control which attributes and their associated columns are retrieved from the database
-
Load: Control which relationships in the entities returned from a query are populated
-
Copy: Control which attributes are copied into a new entity instance
-
Merge: Merge only those attributes fetched, loaded, or copied into an entity
The following sections describe the possible AttributeGroup types and operations.
FetchGroup
The FetchGroup defines which attributes should be fetched (selected from the database) when the entity is retrieved as the result of a query execution. The inclusion of relationship attributes in a FetchGroup only determines if the attribute's required columns should be fetched and populated. In the case of a lazy fetch type the inclusion of the attribute simply means that its proxy will be created to enable lazy loading when accessed. To force a relationship mapping to be populated when using a FetchGroup on a query the attribute must be included in the group and must either be FetchType.EAGER or it must be included in an associated LoadGroup on the query.
Default FetchGroup
FetchGroup also has the notion of named and default FetchGroup which are managed by the FetchGroupManager. A default FetchGroup is defined during metadata processing if one or more basic mappings are configured to be lazy and the entity class implements FetchGroupTracker (typically introduced through weaving). The default FetchGroup is used on all queries for this entity type where no explicit FetchGroup or named FetchGroup is configured.
Named FetchGroup
A named FetchGroup can be defined for an entity using @FetchGroup annotation or within the eclipselink-orm.xml
file.
Full FetchGroup
A FetchGroup when first created is assumed to be empty. The user must add the attributes to the FetchGroup. If a FetchGroup is required with all of the attributes then the FetchGroupManager.createFullFetchGroup() must be used.
Load/LoadAll with FetchGroup
A FetchGroup can also be configured to perform a load operation of relationship mappings and nested relationship mappings.
LoadGroup
A LoadGroup is used to force a specified set of relationship attributes to be populated in a query result.
CopyGroup
The CopyGroup replaces the deprecated ObjectCopyPolicy being used to define how a entity is copied. In addition to specifying the attributes defining what should be copied from the source entity graph into the target copy the CopyGroup also allows definition of:
-
shouldResetPrimaryKey: Reset the identifier attributes to their default value. This is used when the copy operation is intended to clone the entity in order to make a new entity with similar state to the source. Default is false.
-
shouldResetVersion: Reset the optimistic version locking attribute to its default value in the copies. Default is false.
-
depth: defines cascade mode for handling relationships. By default CASCADE_PRIVATE_PARTS is used but it can also be configured to NO_CASCADE and CASCADE_ALL_PARTS.
Merging
When a partial entity is merged into a persistence context that has an AttributeGroup associated with it defining which attributes are available only those attributes are merged. The relationship mappings within the entity are still merged according to their cascade merge settings.
Descriptor Query Manager
In addition to storing named queries applicable to a particular class, you can also use the DescriptorQueryManager to override the default action that EclipseLink defines for common data source operations.
Descriptors and Sequencing
Sequencing options you configure at the project (or session) level determine the type of sequencing that EclipseLink uses.
An essential part of maintaining object identity is managing the assignment of unique values (that is, a specific sequence) to distinguish one object instance from another.
In a POJO project, you can use session-level sequence configuration to override project-level sequence configuration, on a session-by-session basis, if required.
After configuring the sequence type, for each descriptor's reference class, you must associate one attribute, typically the attribute used as the primary key, with its own sequence.
Descriptors and Locking
EclipseLink supports multiple locking policies.
With object-relational mapping, you can configure a descriptor with any of the following locking policies to control concurrent access to a domain object:
-
Optimistic—All users have read access to the data. When a user attempts to make a change, the application checks to ensure the data has not changed since the user read the data.
-
Pessimistic—The first user who accesses the data with the purpose of updating it locks the data until completing the update.
-
No locking—The application does not prevent users overwriting each other's changes.
Oracle recommends using optimistic locking for most types of applications to ensure that users do not overwrite each other's changes.
This section describes the various types of locking policies that EclipseLink supports, including the following:
Optimistic Version Locking Policies
With optimistic locking, all users have read access to the data. When a user attempts to make a change, the application checks to ensure the data has not changed since the user read the data.
Optimistic version locking policies enforce optimistic locking by using a version field (also known as a write-lock field) that you provide in the reference class that EclipseLink updates each time an object change is committed.
EclipseLink caches the value of this version field as it reads an object from the data source. When the client attempts to write the object, EclipseLink compares the cached version value with the current version value in the data source in the following way:
-
If the values are the same, EclipseLink updates the version field in the object and commits the changes to the data source.
-
If the values are different, the write operation is disallowed because another client must have updated the object since this client initially read it.
EclipseLink provides the following version-based optimistic locking policies:
-
VersionLockingPolicy
-
TimestampLockingPolicy
Note:
-
accessing the timestamp from the data source can have a negative impact on performance;
-
time stamp locking is limited to the precision that the database stores for timestamps.
Whenever any update fails because optimistic locking has been violated, EclipseLink throws an OptimisticLockException. This should be handled by the application when performing any database modification. The application must notify the client of the locking contention, refresh the object, and have the client reapply its changes.
You can choose to store the version value in the object as a mapped attribute, or in the cache. In three-tier applications, you typically store the version value in the object to ensure it is passed to the client when updated (see “Applying Locking in an Application” below).
If you store the version value in the cache, you do not need to map it. If you do map the version field, you must configure the mapping as read-only.
To ensure that the parent object's version field is updated whenever a privately owned child object is modified, consider “Optimistic Version Locking Policies and Cascading.”
If you are using a stored procedure to update or delete an object, your database may not return the row-count required to detect an optimistic lock failure, so your stored procedure is responsible for checking the optimistic lock version and throwing an error if they do not match. Only version locking is directly supported with a StoredProcedureCall. Because timestamp and field locking require two versions of the same field to be passed to the call, an SQL call that uses an ## parameter to access the translation row could be used for other locking policies.
If your database schema is such that both a parent object and its privately owned child object are stored in the same table, then if you update the child object, the parent object's version field will be updated.
However, if the parent and its privately owned child are stored in separate tables, then changing the child will not, by default, update the parent's version field.
To ensure that the parent object's version field is updated in this case, you can either manually update the parent object's version field or, if you are using a VersionLockingPolicy, you can configure EclipseLink to automatically cascade the child object's version field update to the parent.
After you enable optimistic version locking cascading, when a privately owned child object is modified, EclipseLink will traverse the privately owned foreign reference mappings, updating all the parent objects back to the root.
EclipseLink supports optimistic version locking cascading for:
-
object changes in privately owned one-to-one and one-to-many mappings
-
relationship changes (adding or removing) in the following collection mappings (privately owned or not):
-
direct collection
-
one-to-many
-
many-to-many
-
aggregate collection
-
Consider the example object graph shown in this figure:
Figure 5-2 Optimistic Version Locking Policies and Cascading Example
![Description of Figure 5-2 follows Description of Figure 5-2 follows](img/oplckcas.png)
Description of "Figure 5-2 Optimistic Version Locking Policies and Cascading Example"
In this example, ObjectA privately owns ObjectB, and ObjectB privately owns ObjectC, and ObjectC privately owns ObjectD.
Suppose you register ObjectB in a unit of work, modify an ObjectB field, and commit the unit of work. In this case, ObjectB checks the cache for ObjectA and, if not present, queries the database for ObjectA. ObjectB then notifies ObjectA of its change. ObjectA forces an update on its version optimistic locking field even though it has no changes to its corresponding table.
Suppose you register ObjectA in a unit of work, access its ObjectB to access its ObjectC to access its ObjectD, modify an ObjectD field, and commit the unit of work. In this case, ObjectD notifies ObjectC of its changes. ObjectC forces an update on its version optimistic locking field even though it has no changes to its corresponding table. ObjectC then notifies ObjectB of the ObjectD change. ObjectB then notifies ObjectA of the ObjectD change. ObjectA forces an update on its version optimistic locking field even though it has no changes to its corresponding table.
With optimistic locking, use the UnitOfWork method commitAndResumeOnFailure to rollback a locked object's value, if you store the optimistic lock versions in the cache.
If you store the locked versions in an object, you must refresh the objects (or their versions) on a failure. Alternatively, you can acquire a new unit of work on the failure and reapply any changes into the new unit of work.
Optimistic field locking policies enforce optimistic locking by using one or more of the fields that currently exist in the table to determine if the object has changed since the client read the object.
The unit of work caches the original state of the object when you first read the object or register it with the unit of work. At commit time, the unit of work compares the original values of the lock fields with their current values on the data source during the update. If any of the lock fields' values have changed, an optimistic lock exception is thrown.
EclipseLink provides the following optimistic field locking policies:
-
AllFieldsLockingPolicy
-
ChangedFieldsLockingPolicy
-
SelectedFieldsLockingPolicy
-
VersionLockingPolicy
-
TimestampLockingPolicy
For descriptions of these locking policies, see "Setting Optimistic Locking" in Solutions Guide for EclipseLink.
Pessimistic Locking Policies
With pessimistic locking, the first user who accesses the data with the purpose of updating it locks the data until completing the update.
When using a pessimistic locking policy, you can configure the policy to either fail immediately or to wait until the read lock is acquired.
You can use a pessimistic locking policy only in a project with a container-managed persistence type and with descriptors that have EJB information.
You can also use pessimistic locking (but not a pessimistic locking policy) at the query level.
EclipseLink provides an optimization for pessimistic locking when this locking is used with entities with container-managed persistence: if you set your query to pessimistic locking and run the query in its own new transaction (which will end after the execution of the finder), then EclipseLink overrides the locking setting and does not append FOR UPDATE to the SQL. However, the use of this optimization may produce an undesirable result if the pessimistic lock query has been customized by the user with a SQL string that includes FOR UPDATE. In this case, if the conditions for the optimization are present, the query will be reset to nonpessimistic locking, but the SQL will remain the same resulting in the locking setting of the query conflicting with the query's SQL string. To avoid this problem, you can take one of the following two approaches:
-
Use an expression (see Understanding EclipseLink Expressions) for the selection criteria. This will give EclipseLink control over the SQL generation.
-
Place the finder in a transaction to eliminate conditions for the optimization.
Applying Locking in an Application
To correctly lock an object in an application, you must obtain the lock before the object is sent to the client for editing.
If you are using optimistic locking, you have the following two choices for locking objects correctly:
-
Map the optimistic lock field in your object as not read-only and pass the version to the client on the read and back to the server on the update.
Ensure that the original version value is sent to the client when it reads the object for the update. The client must then pass the original version value back with the update information, and this version must be set into the object to be updated after it is registered/read in the new unit of work on the server.
-
Hold the unit of work for the duration of the interaction with the client.
Either through a stateful session bean, or in an HTTP session, store the unit of work used to read the object for the update for the duration of the client interaction.
You must read the object through this unit of work before passing it to the client for the update. This ensures that the version value stored in the unit of work cache or in the unit of work clone will be the original value.
This same unit of work must be used for the update.
The first option is more commonly used, and is required if developing a stateless application.
If you are using pessimistic locking, you must use the unit of work to start a database transaction before the object is read. You must hold this unit of work and database transaction while the client is editing the object and until the client updates the object. You must use this same unit of work to update the object.
Descriptor Files
EclipseLink includes descriptor files that can be used for object-relational and MOXy mapping.
Using orm.xml for Object-Relational Mappings
Use the orm.xml file to apply the metadata to the persistence unit.
This metadata is a union of all the mapping files and the annotations (if there is no xml-mapping-metadata-complete element). If you use one mapping orm.xml file for your metadata and place this file in a META-INF directory on the classpath, then you do not need to explicitly list it. The persistence provider will automatically search for this file (orm.xml) and use it.
The schema for the JPA 2.0 orm.xml is orm_2_0.xsd. (http://java.sun.com/xml/ns/persistence/orm_2_0.xsd)
If you use a different name for your mapping files or place them in a different location, you must list them in the mapping-file element of the persistence.xml
file.
Using eclipselink-orm.xml for EclipseLink Object-Relational Mappings
EclipseLink supports an extended JPA orm.xml mapping configuration file called eclipselink-orm.xml
.
This mapping file can be used in place of JPA's standard mapping file or can be used to override a JPA mapping file. In additional to allowing all of the standard JPA mapping capabilities it also includes advanced mapping types and options.
eclipselink-orm.xml
file, see "eclipselink-orm.xml Schema Reference" in Java Persistence API (JPA) Extensions Reference for EclipseLink.
Note:
Using this mapping file enables many EclipseLink advanced features, but it may prevent the persistence unit from being portable to other JPA implementations.
For more information, on overriding values, see:
-
"XML Overriding Rules" in the JPA Specification.
-
The schema for eclipselink-orm.xml is eclipselink_orm_2_2.xsd:
http://www.eclipse.org/eclipselink/xsds/eclipselink_orm_2_2.xsd
Overriding and Merging Mapping Information
To override the orm.xml
file's mapping, you must define the META-INF/eclipselink-orm.xml
file in the project. The contents of eclipselink-orm.xml
override orm.xml
and any other JPA mapping file specified in the persistence unit. If there are overlapping specifications in multiple ORM files, the files are merged if there are no conflicting entities.
For more information, see "Overriding and Merging" in Java Persistence API (JPA) Extensions Reference for EclipseLink.
Validating the XML Schema
By default the content of your .orm
XML file is not validated against the JPA .orm
XML schema.
During development it is a good idea to validate your .orm
XML file against the schema to ensure it is valid. In EclipseLink, validating the .orm
XML schema can be enabled using the persistence unit property eclipselink.orm.validate.schema in your persistence.xml
file.
Advantages and Disadvantages of Using XML
Some advantages of using XML instead of annotations include:
-
No coupling between the metadata and the source code
-
Compliance with the existing, pre-EJB 3.0 development process
-
Support in IDEs and source control systems
The main disadvantages of mapping with XML include:
-
It is inherently complex (when compared to annotations)
-
The need for replication of the code context (that is, defining the structure in both the XML and the source code)
For more information, see Chapter 10 "Metadata Annotations" in the JPA Specification:
Using eclipselink-oxm.xml for EclipseLink MOXy Mappings
You can use Java annotations to specify JAXB features in your projects.
eclipselink-oxm.xml
. This mapping file contains the standard JAXB mappings and configuration options for advanced mapping types. You can use the eclipselink-oxm.xml
file in place of or to override JAXB annotations in source code.
Note:
Using this mapping file will enable many advanced features but it can prevent the model from being portable to other JAXB implementations.