Oracle Application Server TopLink Application Developer's Guide 10g (9.0.4) Part Number B10313-01 |
|
Mapping enables you to relate objects in your application to data in a database. This chapter describes how you can build mappings for Oracle Application Server TopLink-based applications. It includes descriptions of:
For more information about mappings, see also the Oracle Application Server TopLink Mapping Workbench User's Guide.
In an OracleAS TopLink application, you persist objects by storing, or mapping, information about them in a relational database. A mapping has three components:
Although OracleAS TopLink supports more complex mappings, most OracleAS TopLink classes map to a single database table that defines the type of information available in the class. Each object instantiated from a given class maps to a single row comprising the object's attributes, plus an identifier (the primary key) that uniquely identifies the object.
Figure 3-1 illustrates the simplest case in which:
OracleAS TopLink provides you with the tools to build these mappings, from the simple mappings illustrated in Figure 3-1, to complex mappings. OracleAS TopLink addresses the most difficult challenge for mapping--transforming a class or object into database table or row.
The following section describes the basic concepts that you must understand before moving on to the more in-depth information in this chapter, and introduces some of the more complex issues that are part of mapping.
Persistent entities are entities that survive, or persist, beyond the scope of a given transaction. A key feature of OracleAS TopLink is its ability to persist objects and entities in an application by mapping them to a database.
OracleAS TopLink implements a metadata model, in which OracleAS TopLink uses metadata to define how objects and classes map to tables or rows, as well how tables and rows map to objects and classes. OracleAS TopLink uses the metadata, contained in the descriptor, to generate SQL statements that create, read, modify, and delete objects.
The OracleAS TopLink metadata model has three levels of information:
For more information, see Primitive Versus Complex Data.
The metadata model describes the simplest case. There are more complex cases in which objects map to partial or multiple rows, and classes map to multiple tables, which are described later in this chapter. For the purposes of introducing mapping, this simple case forms the basis for understanding how mapping works.
OracleAS TopLink interaction with both object models and databases is unintrusive: OracleAS TopLink adapts to the object model and database schema, rather than requiring developers to design their object model or database schema to suit OracleAS TopLink.
The OracleAS TopLink Mapping Workbench is a graphical tool that gives you access to most OracleAS TopLink features. Although the OracleAS TopLink Mapping Workbench does not support the complete OracleAS TopLink feature set, it does support the basic functions required for mapping your application, as well as most of the advanced features.
The graphical nature of the OracleAS TopLink Mapping Workbench makes it easy to create models and mappings. As such, Oracle recommends that you build as much of your project in the OracleAS TopLink Mapping Workbench as possible.
An important feature of the OracleAS TopLink Mapping Workbench is its ability to generate deployment files from your project, either as deployment XML files or Java source code.
For more information about generating deployment files, see "Exporting Project Information" in the Oracle Application Server TopLink Mapping Workbench User's Guide.
The OracleAS TopLink Mapping Workbench can generate XML files from your project. OracleAS TopLink reads these files at runtime to configure your application. Deployment XML files reduce development time by eliminating the need to regenerate and recompile Java code each time the project changes.
The OracleAS TopLink Mapping Workbench can generate Java source files for your project that you compile and run for your application. Often, this generated code deploys faster than XML files, but is less flexible and more difficult to troubleshoot.
OracleAS TopLink offers several types of mapping, each optimized for different types of information.
Direct mappings define how a persistent object refers to objects and attributes that do not have OracleAS TopLink descriptors, such as the JDK classes, primitive types, and other nonpersistent classes. Direct mappings map primitive data types to database data types on a one-to-one basis.
For more information about direct mappings, see "Direct Mappings".
Relationship mappings describe how you manage relationships on the database. OracleAS TopLink uses several different mechanisms to represents relationships in the database, the most common of which is foreign keys. The OracleAS TopLink descriptors include details on the storage and retrieval mechanisms used for the relationship.
For more information about relationship mappings, see "Relationship Mappings".
In object modeling, when one class (the superclass) shares its attributes with another class (the subclass), the subclass is said to inherit those attributes from the superclass or table. Similarly, in the database world, when one table shares information with a subordinate table in the database, the subordinate table inherits information from the main table. Although these two types of inheritance are similar, mapping them properly can be difficult.
OracleAS TopLink supports both object and database inheritance, and enables you to easily map object inheritance to database tables. OracleAS TopLink treats both types of inheritance interchangeably, provided that you map the inheritance in the class descriptors for the superclass and subclass.
For more information about inheritance, see "Inheritance".
OracleAS TopLink stores objects in database tables. In most cases, a single row in a database table represents a single object in your OracleAS TopLink application. Several OracleAS TopLink concepts follow from this arrangement, including:
A primary key is a column or a combination of columns, in a database table that contains a unique identifier for every record in the table. Persistent objects require a primary key. If a table uses a combination of columns to create a unique identifier, this combination of fields is collectively called a composite primary key. In either case, a primary key uniquely identifies each row.
Sequencing is a mechanism to populate the primary key attribute of new objects and entity beans before inserting them into the database.
For more information, see "Sequencing".
Objects stored in one database table (the source objects) can share a relationship with objects in other tables (the target objects). To define these relationships, your tables must include data that identifies which target objects are related to the source object in the relationship.
The target table primary key in the relationship becomes a foreign key in the source table and identifies which objects in the target table are related to the objects in the source table.
For more information, see "Foreign Keys".
The standard object reading behavior in Java is that when you read an object, you also read all its related objects, which can be unnecessarily time consuming. The OracleAS TopLink indirection feature enables you to defer reading related objects until they are required. This is also known as lazy reading, lazy loading, and just-in-time reading.
For more information, see "Indirection".
In OracleAS TopLink, serialization is the act of writing out (marshalling) an object from its home OracleAS TopLink Java virtual machine (JVM) to another JVM.
For more information, see "Serialization".
This section outlines some of the more common general concepts you will encounter when dealing with mappings.
OracleAS TopLink treats certain classes as primitive data types for mapping purposes. These include Strings and Integers. Primitive data types correspond directly to representations in the database fields in which they are stored.
Because of this direct correspondence, there is no need to describe how to map the primitive data. As a result, OracleAS TopLink does not require mapping descriptors for primitive data types.
Object attributes represent complex data. OracleAS TopLink requires class descriptors to define how the attributes and relationships of instances of a particular class are stored and retrieved. Descriptors specify where and how attributes are stored in database tables.
Java objects represent the components or business logic of your application. As the basic building blocks in an OracleAS TopLink application, objects can include data, methods, relationships, and inheritance hierarchies.
The OracleAS TopLink Mapping Workbench enables you to set properties and configure the mappings and OracleAS TopLink descriptors for any given project in a graphical environment. To create mappings, use either the OracleAS TopLink Mapping Workbench or the Java code-based API. However, Oracle recommends the OracleAS TopLink Mapping Workbench whenever possible.
Mappings for each class are stored in the class descriptor. OracleAS TopLink uses the descriptor to instantiate objects from the database and to store new or modified objects on the database. The descriptor describes how to store to or retrieve from the database a given class. Object instantiation uses this information to build and store the instantiated objects.
The relationship among the database, the objects and classes, and the descriptor makes up the OracleAS TopLink metadata model.
For more information about the OracleAS TopLink Mapping Workbench, see the Oracle Application Server TopLink Mapping Workbench User's Guide.
This section presents several topics and techniques to optimize your mapping strategy, including:
Use direct mapping to map primitive object attributes, or nonpersistent regular objects, such as the JDK classes. For example, use a direct-to-field mapping to store a String
attribute in a VARCHAR field.
You can map entity bean attributes using direct mappings without any special considerations.
All direct mappings include optional setGetMethodName()
and setSetMethodName()
messages. These messages allow OracleAS TopLink to access the attribute through user-defined methods, rather than directly through the attribute.
The direct-to-field mappings are instances of the DirectToFieldMapping
class and require the following elements:
setAttributeName()
message
setFieldName()
message
The Descriptor
class provides the addDirectMapping()
method that creates a new DirectToFieldMapping
, sets the attribute and field name parameters, and registers the mapping with the descriptor.
You create a direct-to-field mapping in one of two ways:
Example 3-1 and Example 3-2 illustrate common ways of mapping one attribute to one field.
// Create a new mapping and register it with the descriptor. DirectToFieldMapping mapping = new DirectToFieldMapping(); mapping.setAttributeName("city"); mapping.setFieldName("CITY"); descriptor.addMapping(mapping);
This mapping example assumes that the persistent class has getCity()
and setCity()
methods defined.
// Create a new mapping and register it with the descriptor. DirectToFieldMapping mapping = new DirectToFieldMapping(); mapping.setAttributeName("city"); mapping.setFieldName("CITY"); mapping.setGetMethodName("getCity"); mapping.setSetMethodName("setCity"); descriptor.addMapping(mapping);
// Alternate method which does the same thing. descriptor1.addDirectMapping("city", "CITY"); descriptor2.addDirectMapping("city", "getCity", "setCity", "CITY");
You must pay special attention when you map more than one attribute to the same field where some mappings are read-only and some are not. By default, with DatabaseLogin.setShouldOptimizeDataConversion(true)
-- OracleAS TopLink uses the data type of the attribute of the last writable mapping for all subsequent read-only mappings. In this context, "last" is relative to the order in which the attributes are declared in the mapped class.
This behavior can lead to a loss of precision.
Consider the following hypothetical example: you want to map the class that appears in Example 3-4 to create two different views of the same, underlying database field. You want attribute view1
to represent the database field as an integer and attribute view2
to represent the same database field as a double. Finally, you want attribute view1
to be writable and attribute view2
to be read-only.
public class ClassToMap { private String name; private long id; private int view1; private double view2; // READONLY ... }
Furthermore, your database administrator decides that both attributes will be mapped to a single database column, NUM_VIEW
of table CLASSTOMAP
, declared NUMBER(20,7)
-- that is, with a non-zero sub field size which allows storage of both integer values and floating point values with up to 7 digits of precision.
The corresponding project.xml
database mapping elements appear in Example 3-5: the first maps attribute view1
to table CLASSTOMAP
field NUM_VIEW
as writable and the second maps attribute view2
to the same field as read-only.
<database-mapping> <attribute-name>view1</attribute-name> <read-only>false</read-only> <field-name>CLASSTOMAP.NUM_VIEW</field-name> <type>oracle.toplink.mappings.DirectToFieldMapping</type> </database-mapping> <database-mapping> <attribute-name>view2</attribute-name> <read-only>true</read-only> <field-name>CLASSTOMAP.NUM_VIEW</field-name> <type>oracle.toplink.mappings.DirectToFieldMapping</type> </database-mapping>
If the database is loaded with a record with CLASSTOMAP.NUM_VIEW
value 3.141, you need to use readAllObjects()
to get this instance of ClassToMap
as shown in Example 3-6.
Session sess = SessionManager.getManager().getSession("test"); Vector v = sess.readAllObjects(ClassToMap.class)
In the returned instance, the value of view1
will be 3 and the value of view2
will be 3.0 instead of 3.141. This loss of precision is the result of OracleAS TopLink's method of applying the data type of the last writable mapping which in this example is integer.
In this case, you can choose from either of the following options:
DatabaseLogin.setShouldOptimizeDataConversion(false)
.
To change your design so that the view1
is read-only and view2
is writable, proceed as follows:
<database-mapping> <attribute-name>view1</attribute-name> <read-only>true</read-only> <field-name>CLASSTOMAP.NUM_VIEW</field-name> <type>oracle.toplink.mappings.DirectToFieldMapping</type> </database-mapping> <database-mapping> <attribute-name>view2</attribute-name> <read-only>false</read-only> <field-name>CLASSTOMAP.NUM_VIEW</field-name> <type>oracle.toplink.mappings.DirectToFieldMapping</type> </database-mapping>
For more information about the available methods for DirectToFieldMapping
, see the Oracle Application Server TopLink API Reference.
Type conversion mappings and instances of the TypeConversionMapping
class and require the following elements:
setAttributeName( )
message
setFieldName( )
message
setAttributeClassification( )
message
setFieldClassification( )
message
// Create a new mapping and register it with the descriptor. TypeConversionMapping typeConversion = new TypeConversionMapping(); typeConversion.setFieldName("J_DAY"); typeConversion.setAttributeName("joiningDate"); typeConversion.setFieldClassification(java.sql.Date.class); typeConversion.setAttributeClassification(java.util.Date.class); descriptor.addMapping(typeConversion);
For more information about the available methods for TypeConversionMapping
, see the Oracle Application Server TopLink API Reference.
Object type mappings are instances of the ObjectTypeMapping
class and require the following elements:
setAttributeName( )
message
setFieldName( )
message
addConversionValue( )
message
The following methods are useful in a legacy environment or when you want to change the values of the fields:
addToAttributeOnlyConversionValue(Object fieldValue, Object attributeValue)
: This is a one-way mapping from the field to the attribute. Use this mapping if multiple database values map to the same object value. When written to the database, the value entered by addConversionValue(Object fieldValue, Object attributeValue)
is used, and the original values in the database change.
setDefaultAttributeValue Object defaultAttributeValue)
: Substitutes the default value for any unmapped value retrieved from database. When writing to the database, the value entered by addConversionValue(Object fieldValue, Object attributeValue)
is used, and the original values in the database change.
// Create a new mapping and register it with the descriptor. ObjectTypeMapping typeMapping = new ObjectTypeMapping(); typeMapping.setAttributeName("gender"); typeMapping.setFieldName("GENDER"); typeMapping.addConversionValue("M", "Male"); typeMapping.addConversionValue("F", "Female"); typeMapping.setNullValue("F"); descriptor.addMapping(typeMapping);
For more information about the available methods for ObjectTypeMapping
, see the Oracle Application Server TopLink API Reference.
Relationship mappings define how persistent objects reference other persistent objects. OracleAS TopLink supports several relationship mapping types, as described in this section.
Persistent objects use relationship mappings to store references to instances of other persistent classes. The appropriate mapping type is selected based on the cardinality of the relationship (for example: a one-to-one or one-to-many). Entity beans can have relationships to regular Java objects, other entity beans, or both.
A bean that has a relationship to another bean acts as a client of that bean--it does not access the actual bean directly but acts through the remote (EJB 1.1) or local (EJB 2.0) interface of the bean. For example, if an OrderBean
is related to a CustomerBean
, it has an instance variable of type Customer
(the Local
or Remote
interface of the CustomerBean
) and accesses only those methods defined on the Customer
interface.
Most OracleAS TopLink relationship mapping functionality is available regardless of the EJB specification supported by your J2EE container or application server. However, there are some differences between OracleAS TopLink support for EJB 1.1 and EJB 2.0.
The EJB 1.1 specification does not specify how entity beans store an object reference to another entity bean; as a result, if you are using an EJB 1.1-compliant container, this normally prevents you from mapping relationships between entity beans. However, OracleAS TopLink includes support for relationships that exceeds what is available in the EJB 1.1 specification, and allows the creation of inter-bean relationships.
The EJB 2.0 specification defines methods for relating beans to one another. OracleAS TopLink support for the EJB 2.0 specification includes the following concepts:
The EJB 2.0 specification also imposes many restrictions on CMP relationships, some of which are not enforced by OracleAS TopLink. Therefore, although OracleAS TopLink offers more flexibility in developing applications, if the application must be fully EJB 2.0-compliant, be careful about which features you include in your application.
Some of the EJB 2.0 restrictions that OracleAS TopLink does not enforce include:
Local
interfaces.
The EJB 2.0 specification describes additional restrictions to the mapping and runtime behavior of EJB 2.0 CMP beans.
For more information about the Enterprise JavaBeans and the EJB 2.0 specification, see
http://java.sun.com/products/ejb/ http://java.sun.com/products/ejb/docs.html http://java.sun.com/j2ee/white/index.html
In addition, although EJB 2.0 support for indirection is limited, OracleAS TopLink does enable you to implement OracleAS TopLink valueholder indirection for one-to-one relationships, and transparent indirection for one-to-many and many-to-many relationships.
For more information, see "Indirection".
The OracleAS TopLink Mapping Workbench can obtain relationship metadata from the ejb-jar.xml
file.
For more information on how to update OracleAS TopLink relationships in the OracleAS TopLink Mapping Workbench from the ejb-jar.xml
deployment descriptor, see "Working with project properties" in the Oracle Application Server TopLink Mapping Workbench User's Guide.
Entity beans represent independent business objects. Objects that depend on the entity bean are often implemented as Java classes, and included as part of the entity bean on which they depend. The following relationship mappings may exist between an entity bean and regular Java objects:
One-to-one mappings represent simple pointer references between two objects. One-to-one mappings for relationships between entity beans, or between an entity bean and a regular Java object, where the entity bean is the source and the regular Java object is the target of the relationship.
One-to-one mappings are instances of the OneToOneMapping()
class and require the following elements:
setAttributeName( )
message
setReferenceClass( )
message
setForeignKeyFieldName( )
message and passing the foreign key field from the source table that references the primary key of the target table
If the mapping has a bidirectional relationship in which the two classes in the relationship reference each other with one-to-one mappings, then set up the foreign key information as follows:
setForeignKeyFieldName()
message.
setTargetForeignKeyFieldName()
message.
It is also possible to set up composite foreign key information by sending the addForeignKeyFieldName()
and addTargetForeignKeyFieldName()
messages. Because OracleAS TopLink enables indirection by default, the attribute must be a ValueHolderInterface
.
// Create a new mapping and register it with the descriptor. OneToOneMapping oneToOneMapping = new OneToOneMapping(); oneToOneMapping.setAttributeName("address"); oneToOneMapping.setReferenceClass(Address.class); oneToOneMapping.setForeignKeyFieldName("ADDRESS_ID"); descriptor.addMapping(oneToOneMapping);
The foreign key is stored in the Policy's table referencing the composite primary key of the Carrier.
// In the Policy class, which will hold the foreign key, create the mapping that references the Carrier class. OneToOneMapping carrierMapping = new OneToOneMapping(); carrierMapping.setAttributeName("carrier"); carrierMapping.setReferenceClass(Carrier.class); carrierMapping.addForeignKeyFieldName("INSURED_ID", "CARRIER_ID"); carrierMapping.addForeignKeyFieldName("INSURED_TYPE", "TYPE"); descriptor.addMapping(carrierMapping);. . . // In the Carrier class, create the mapping that references the Policy class. OneToOneMapping policyMapping = new OneToOneMapping(); policyMapping.setAttributeName("masterPolicy"); policyMapping.setReferenceClass(Policy.class); policyMapping.addTargetForeignKeyFieldName("INSURED_ID", "CARRIER_ID"); policyMapping.addTargetForeignKeyFieldName("INSURED_TYPE", "TYPE"); descriptor.addMapping(policyMapping);
For more information about the available methods for OneToOneMapping
, see the Oracle Application Server TopLink API Reference.
For more information about one-to-one mappings, see the Oracle Application Server TopLink Mapping Workbench User's Guide.
To maintain EJB compliance, the object attribute that points to the target of the relationship must be the remote (EJB 1.1) or local (EJB 2.0) interface type--not the bean class.
OracleAS TopLink provides variations on one-to-one mappings that allow you to define complex relationships when the target of the relationship is a dependent Java object. For example, variable one-to-one mappings enable you to specify variable target objects in the relationship. These variations are not available for entity beans, but are valid for dependent Java objects.
For more information, see the "Variable One-to-One Mappings".
Two objects are related by aggregation if there is a strict one-to-one relationship between the objects, and if all the attributes of the second object can be retrieved from the same table(s) as the owning object. So if the target (or child) object exists, then the source (or parent) object must also exist. The child object cannot exist without its parent.
Aggregate object mappings are instances of the AggregateObjectMapping
class. This mapping relates to an attribute in each of the parent classes. Aggregate object mappings require the following information:
setAttributeName( )
message
setReferenceClass( )
message
Aggregate object mappings also require the following modifications to the target class descriptor:
descriptorIsAggregate( )
message to the descriptor to indicate that all information must come from the row(s) of its parent object's r
By default, the mapping allows null references to its target class, so it does not create an instance of the target object. To prevent a parent from having a null reference, send the dontAllowNull( )
message, which results in an instance of the child with its attributes set to null.
// Create a new mapping and register it with the source descriptor. AggregateObjectMapping aggregateMapping = new AggregateObjectMapping(); aggregateMapping.setAttributeName("employPeriod"); aggregateMapping.setReferenceClass(Period.class); descriptor.addMapping(aggregateMapping);
The aggregate target descriptor does not need a mapping to its parent, nor does it need any table or primary key information.
// Create a descriptor for the aggregate class. The table name and primary key are not specified in the aggregate descriptor. Descriptor descriptor = new Descriptor(); descriptor.setJavaClass(Period.class); descriptor.descriptorIsAggregate(); // Define the attribute mappings or relationship mappings. descriptor.addDirectMapping("startDate", "START_DATE"); descriptor.addDirectMapping("endDate", "END_DATE"); return descriptor;
The field names must be translated in the Project descriptor. No changes need to be made to the Period
class descriptor to implement this second parent.
// Create a new mapping and register it with the parent descriptor. AggregateObjectMapping aggregateMapping = new AggregateObjectMapping(); aggregateMapping.setAttributeName("projectPeriod"); aggregateMapping.setReferenceClass(Period.class); aggregateMapping.addFieldNameTranslation("S_DATE", "START_DATE"); aggregateMapping.addFieldNameTranslation("E_DATE", "END_DATE"); descriptor.addMapping(aggregateMapping);
For more information about the available methods for AggregateObjectMapping
, see the Oracle Application Server TopLink API Reference.
You can use aggregate mappings with entity beans when the source of the mapping is an entity bean and the target is a regular Java object. An entity bean cannot be the target of an aggregate object mapping.
For more information about aggregate object mappings, see the Oracle Application Server TopLink Mapping Workbench User's Guide.
One-to-many mappings represent the relationship between a single source object and a collection of target objects.
For more information about one-to-many mappings, see the Oracle Application Server TopLink Mapping Workbench User's Guide.
One-to-many mappings are instances of the OneToManyMapping
class and require the following elements:
setAttributeName()
message
setReferenceClass()
message
setTargetForeignKeyFieldName()
message and passing a field in the target object's associated table that refers to the primary key in the owning object's table
For more information, see "One-to-One Mappings" .
// In the Employee class, create the mapping that references the Phone class. oneToManyMapping = new OneToManyMapping(); oneToManyMapping.setAttributeName("phoneNumbers"); oneToManyMapping.setReferenceClass(PhoneNumber.class); oneToManyMapping.setTargetForeignKeyFieldName("EMPID"); descriptor.addMapping(oneToManyMapping); . . . // In the Phone class, which will hold the foreign key, create the mapping that references the Employee class. OneToOneMapping oneToOneMapping = new OneToOneMapping(); oneToOneMapping.setAttributeName("owner"); oneToOneMapping.setReferenceClass(Employee.class); oneToOneMapping.setForeignKeyFieldName("EMPID"); descriptor.addMapping(oneToOneMapping);
In addition to the API Example 3-14 illustrates, other common API for use to implement indirection in aggregate collection include:
useBasicIndirection()
: implements OracleAS TopLink valueholder indirection
useTransparentCollection()
: if you use transparent indirection, this element places a special collection in the source object's attribute
dontUseIndirection()
: implements no indirection
For more information about the available methods for OneToManyMapping
, see the Oracle Application Server TopLink API Reference.
Use one-to-many mappings for relationships between entity beans or between an entity bean and a collection of privately owned regular Java objects. When you create one-to-many mappings, also create a one-to-one mapping from the target objects back to the source. The object attribute that contains a pointer to the bean must be the remote (EJB 1.1) or local (EJB 2.0) interface type--not the bean class.
OracleAS TopLink automatically maintains back-pointers when you create or update bidirectional relationships between beans.
For more information, see "Maintaining Bidirectional Relationships".
Aggregate collection mappings represent the aggregate relationship between a single-source object and a collection of target objects. Unlike the OracleAS TopLink one-to-many mappings, there is no back reference required for the aggregate collection mappings because the foreign key relationship is resolved by the aggregation.
Aggregate collection mappings require a target table for the target objects.
To implement an aggregate collection mapping:
Although similar in behavior to one-to-many mappings, an aggregate collection is not a replacement for one-to-many mappings. Use aggregate collections when the target collections are reasonable in size and a one-to-one mapping from the target to the source proves difficult.
Because one-to-many relationships offer better performance and are more robust and scalable, consider using a one-to-many relationship rather than an aggregate collection. In addition, aggregate collections are privately owned by the source of the relationship and must not be shared or referenced by other objects.
Aggregate collection descriptors can make use of inheritance, but you must declare the subclasses as aggregate collections as well. The subclasses can have their own mapped tables, or share the table with their parent class.
In a Java vector, the owner references its parts; in a relational database, the parts reference their owners. Relational databases use this implementation to make querying more efficient.
Aggregate collection mappings are instances of the AggregateCollectionMapping
class and require the following elements:
setAttributeName( )
message
setReferenceClass( )
message
addTargetForeignKeyFieldName( )
message and passing the field name of the target foreign key and the source of the primary key in the source table
// In the PolicyHolder class, create the mapping that references the Phone class
AggregateCollectionMapping phonesMapping = new AggregateCollectionMapping();
phonesMapping.setAttributeName("phones");
phonesMapping.setGetMethodName("getPhones");
phonesMapping.setSetMethodName("setPhones");
phonesMapping.setReferenceClass("Phone.class");
phonesMapping.dontUseIndirection();
phonesMapping.privateOwnedRelationship;
phonesMapping.addTargetForeignKeyFieldName("INS_PHONE.HOLDER_SSN","HOLDER.SSN");
descriptor.addMapping(phonesMapping);
In addition to the API Example 3-15 illustrates, other common API for use to implement indirection in aggregate collection mappings include:
useBasicIndirection()
: implements OracleAS TopLink valueholder indirection
useTransparentCollection()
: if you use transparent indirection, this element places a special collection in the source object's attribute
dontUseIndirection()
: implements no indirection
For more information about the available methods for AggregateCollectionMapping
, see the Oracle Application Server TopLink API Reference.
You can use aggregate collection mappings with entity beans if the source of the relationship is an entity bean or Java object, and the mapping targets are regular Java objects. Entity beans cannot be the target of an aggregate object mapping.
Direct collection mappings store collections of Java objects that are not OracleAS TopLink-enabled. Direct collections usually store Java types, such as strings
.
Direct collection mappings are instances of the DirectCollectionMapping
class and require the following elements:
setAttributeName( )
message
setReferenceTableName( )
message
setDirectFieldName( )
message
setReferenceKeyFieldName( )
message and passing the name of the field that is a foreign reference to the primary key of the source object
DirectCollectionMapping directCollectionMapping = new DirectCollectionMapping(); directCollectionMapping.setAttributeName ("responsibilitiesList"); directCollectionMapping.setReferenceTableName ("RESPONS"); directCollectionMapping.setDirectFieldName("DESCRIP"); directCollectionMapping.setReferenceKeyFieldName ("EMP_ID"); directCollectionMapping.useCollectionClass (Vector.class); // the default descriptor.addMapping(directCollectionMapping);
In addition to the API Example 3-16 illustrates, other common API for use with direct collection mappings include:
useBasicIndirection()
: implements OracleAS TopLink valueholder indirection
useTransparentCollection()
: if you use transparent indirection, this element places a special collection in the source object's attribute
dontUseIndirection()
: implements no indirection
For more information about the available methods for DirectCollectionMapping
, see the Oracle Application Server TopLink API Reference.
Many-to-many mappings represent the relationships between a collection of source objects and a collection of target objects. This requires an intermediate table that manages the associations between the source and target records.
Many-to-many mappings are instances of the ManyToManyMapping
class and requires the following elements:
setAttributeName( )
message
setReferenceClass( )
message
setRelationTableName( )
message
setSourceRelationKeyFieldName( )
and setTargetRelationKeyFieldName( )
messages
addSourceRelationKeyFieldName()
or addTargetRelationKeyFieldName()
messages
// In the Employee class, create the mapping that references the Project class. ManyToManyMapping manyToManyMapping = new ManyToManyMapping(); manyToManyMapping.setAttributeName("projects"); manyToManyMapping.setReferenceClass(Project.class); manyToManyMapping.setRelationTableName("PROJ_EMP"); manyToManyMapping.setSourceRelationKeyFieldName ("EMPID"); manyToManyMapping.setTargetRelationKeyFieldName ("PROJID"); descriptor.addMapping(manyToManyMapping);
In addition to the API Example 3-17 illustrates, other common API for use with many-to-many mappings include:
useBasicIndirection()
: implements OracleAS TopLink valueholder indirection
useTransparentCollection()
: if you use transparent indirection, this element places a special collection in the source object's attribute
dontUseIndirection()
: implements no indirection
For more information about the available methods for ManyToManyMapping
, see the Oracle Application Server TopLink API Reference.
When you use CMP, many-to-many mappings are valid only between entity beans, and cannot be privately owned. The only exception is when a many-to-many mapping is used to implement a logical one-to-many mapping with a relation table.
OracleAS TopLink automatically maintains back-pointers when you create or update bidirectional relationships.
For more information, see "Maintaining Bidirectional Relationships".
For more information about ManyToManyMapping
, see the Oracle Application Server TopLink Mapping Workbench User's Guide.
By default, when an OracleAS TopLink application reads an object, it also reads all its related objects. For example, given an object, CAR, with related objects, TIRES and RADIO, reading the CAR object forces reading of the TIRES and RADIO objects at the same time. This method is inefficient if the reason for reading in the CAR object has nothing to do with the related objects (for example: when you read CAR to check one of its attributes, such as COLOR).
OracleAS TopLink indirection gives you the ability to replace the related objects (TIRES and RADIO, in this example) with an indirection object. An indirection object is a placeholder that represents related objects, but prevents them from being read until they are actually required. If you never need the related objects, they are never read from the database.
OracleAS TopLink supports three main types of indirection:
Vector
, Hashtable
, or Collection
interfaces.
Indirection represents an effective way to improve the efficiency of your application and we recommend it be implemented wherever it is supported by your application and its usage patterns.
For more information about implementing indirection in code, see "Implementing Indirection in Java".
Valueholder indirection is a native OracleAS TopLink feature that implements the OracleAS TopLink ValueHolderInterface
on your objects to achieve indirection. A valueholder represents an instance of a related class and stores the information necessary to retrieve the object it represents from the database. If the application does not access the valueholder, the replaced object is never read from the database.
If you use method access, the get
and set
methods specified for the mapping must access an instance of ValueHolderInterface
, rather than the object that the valueholder is referencing. To obtain the object represented by the valueholder, use the getValue()
and setValue()
methods of the ValueHolderInterface
class. You can hide the getValue
and setValue
methods of the ValueHolderInterface
inside get
and set
methods.
You can change the attribute types in the class editor, but if you do, also change the attribute types in your Java code, as well as their accessor methods.
If the instance variable returns a vector instead of an object, define the valueholder in the constructor as follows:
addresses = new ValueHolder(new Vector());
The application uses the getAddress()
and setAddress()
methods to access the Address
object. When you use indirection, OracleAS TopLink uses the getAddressHolder()
and setAddressHolder()
methods to save instances to and retrieve instances from the database.
This example modifies the class definition so that the address
attribute of Employee
is a ValueHolderInterface
, rather than an Address
, and supplies the appropriate get
and set
methods.
// Initialize ValueHolders in Employee Constructor
public Employee() {
address = new ValueHolder();
}
protected ValueHolderInterface address;
// 'Get' and `Set' accessor methods registered with the mapping and used by
OracleAS TopLink.
public ValueHolderInterface getAddressHolder() {
return address;
}
public void setAddressHolder(ValueHolderInterface holder) {
address = holder;
}
// `Get' and `Set' accessor methods used by the application to access the
attribute.
public Address getAddress() {
return (Address) address.getValue();
}
public void setAddress(Address theAddress) {
address.setValue(theAddress);
}
Proxy indirection enables you to use dynamic proxy objects as stand-ins for a defined interface. You can configure all the following mapping types to use proxy indirection, which gives you the benefits of indirection without the need to include OracleAS TopLink classes in your domain model:
Note that all these mapping types map one-to-one relationships.
The useProxyIndirection()
method indicates that OracleAS TopLink must use proxy indirection for the current mapping. When you read the source object from the database, OracleAS TopLink creates a proxy for the target object and uses it in place of the target object. When you call any method other than toString()
on the proxy, OracleAS TopLink reads the target object from the database.
Proxy indirection is not directly supported in the OracleAS TopLink Mapping Workbench. To implement proxy indirection, use the useProxyIndirection
method in an amendment method.
Proxy indirection does not use the OracleAS TopLink ValueholderInterface
, and nor are target objects typed as ValueHolderInterface
. Instead, to implement proxy indirection, make changes to both the object model and the descriptor mapping for the source object.
To use proxy indirection, your domain model must satisfy the following criteria:
get()
and set()
methods must use the interface.
In the descriptor, invoke the useProxyIndirection
method in the source object descriptor that defines mapping between the source and target objects.
The Employee
class has an attribute, ADDRESS
, of type Address. The Address
attribute is mapped using a one-to-one mapping from Employee
(source) to Address
(target) and uses proxy indirection. The code includes the steps for building this relationship.
//Step 1. Define an interface "IAddress" for "Address" public interface IAddress { public String getCity(); public void setCity(String aCity); } // Step 2. Implement this interface on the "Address" class public class Address implements IAddress { String city; public String getCity() { return city;} public void setCity(String aCity){city = aCity;} public Address() { ... } } //Step 3. Declare the attribute "address" as interface "IAddress" on the Employee. public class Employee { public BigInteger id; public String firstName; public String lastName; public IAddress address; //4. Configure the Set and get methods "getAddress()", "setAddress()" to use interface IAddress" // get and set methods for instance variables public IAddress getAddress() {return this.address;} public void setAddress (IAddress newAddress) {this.address=newAddress;} public Employee() { ... } } //5. The mapping between Employee and Address must invoke the useProxyIndirection() api. Also, the target class, which implements the interface must be passed to the setReferenceClass() method as the argument. //Define the 1:1 mapping, and specify that ProxyIndirection should be used OnetoOnemapping addressMapping = new OneToOneMapping(); addressMapping.setAttributeName("address"); addressMapping.setReferenceClass(Address.class); addressMapping.setForeignKeyFieldName("ADDRESS_ID"); addressMapping.setSetMethodName("setAddress"); addressMapping.setGetMethodName("getAddress"); addressMapping.useProxyIndirection(); descriptor.addMapping(addressMapping);
You cannot register the target of a proxy indirection implementation with a Unit of Work. Instead, first register the source object with the Unit of Work. This enables you to retrieve a target object clone with a get...()
call against the source objects clone.
For example:
UnitOfWork uow = session.acquireUnitOfWork(); Employee emp = (Employee)session.readObject(Employee.class); // Register the source object Employee empClone = (Employee)uow.registerObject(emp); // All of source object's relationships are cloned when source object is cloned Address addressClone = empClone.getAddress(); addressClone.setCity("Toronto");
For more information about clones and the Unit of Work, see "Understanding the Unit of Work".
Transparent indirection enables you to declare any relationship attribute of a persistent class that holds a collection of related objects as a java.util.Collection
, java.util.Map
, java.util.Vector
, or java.util.Hashtable
. OracleAS TopLink uses an indirection object that implements the appropriate interface, and performs just-in-time reading of the related objects.
When using transparent indirection, you do not have to declare the attributes as ValueHolderInterface
.
You can specify transparent indirection from the OracleAS TopLink Mapping Workbench. Newly created collection mappings use transparent indirection by default if their attribute is not a ValueHolderInterface
.
Do not include OracleAS TopLink classes in the domain class for transparent indirection.
Although there are no universal rules for the use of indirection, the following guidelines illustrate when indirection is beneficial, and helps you choose the appropriate type of indirection.
Because it delays database reads until they are required, indirection produces an increase in performance. However, if you have a relationship between objects that are always called together, the benefit does not apply. For example, if you have a pair of objects that are always called together to populate a Web page, there is no benefit to delay the reading of the target of the relationship, because it will be called at the same time as the source object every time. If you have objects that you always call together, do not implement indirection.
Use valueholder indirection if at least one of the following conditions exists:
Use proxy indirection if you are not applying indirection with EJB entity beans as targets.
When you create one-to-many or many-to-many relationships in the OracleAS TopLink Mapping Workbench, OracleAS TopLink automatically implements transparent indirection. This provides the best possible performance for large relationship graphs and must not be disabled.
OracleAS TopLink offers mechanisms to implement indirection for relationships between EJBs. As with regular Java objects, these mechanisms include:
The Oracle Application Server TopLink Mapping Workbench User's Guide describes these indirection mechanisms.
Consider the following guidelines when you use indirection with EJBs, particularly when you migrate objects between client and server:
When both the source and target are entity beans, the indirection policies for container-managed relationship fields under the EJB 2.0 specification must be one of the following:
Because subclasses are code-generated, all indirection is hidden from the user.
OracleAS TopLink supports Java serialization, which enables you to write objects out to one JVM and read objects back from the other JVM. Preparing the objects for transport is known as marshalling; receiving objects back is known as unmarshalling. In an OracleAS TopLink application, serialization occurs between a JVM with OracleAS TopLink and a non-OracleAS TopLink JVM. If you serialize to another JVM with OracleAS TopLink, consider using a remote session instead.
For more information, see "Remote Session".
A common cause of problems with serialization is the use of indirection in serialized objects. Indirection valueholders rely on the OracleAS TopLink session for context (mapping information, JDBC connectivity, and so on); however, because the OracleAS TopLink session is stored in the object in a transient variable, it does not survive serialization, leaving the serialized valueholders with no context and no way to resolve the links to the data they represent. As a result, when you marshall the object for serialization, the values held by valueholders are replaced with a null value. If the application on the receiving JVM invokes the valueholder, the result is a null pointer exception.
Note that no null pointer exception is thrown during the serialization process, nor does OracleAS TopLink prevent you from serializing an untriggered valueholder. This enables you to serialize objects and retain the efficiency advantages of indirection if you know that the receiving JVM does not use the valueholders.
A common way to avoid null pointer exceptions in the receiving JVM is to selectively trigger valueholders before serializing them.
Java serialization supports a callback mechanism that enables you to execute a special type of method on an object before serializing it. Specifically, if a writeObject
method exists on the object Java serialization executes the method.
For example:
protected void writeObject(ObjectOutputStream out) throws IOException
This mechanism presents an opportunity to selectively trigger valueholders. You can:
Helper classes are the most flexible way to trigger valueholders, because you can use a single helper class for several objects. When deciding how to trigger valueholders, we recommend the following methods:
Unmarshalling a serialized object always occurs in the context of a Unit of Work when you integrate changes made outside of the JVM with the affected objects in the OracleAS TopLink application. Several options are available for the merge:
shallowMergeClone(java.lang.Object rmiClone)
method to capture changes in the deserialized object only. Use this option when you know that changes to the object do not extend to related objects.
mergeClone(java.lang.Object rmiClone)
method to capture changes in the deserialized object and any of its privately owned objects.
mergeCloneWithReferences(java.lang.Object rmiClone)
method to capture changes to the deserialized object, its privately owned objects, and all its referenced objects. Note that the referenced objects include only those objects with a direct relationship to the deserialized object.
deepMergeClone(java.lang.Object rmiClone)
method to capture all changes to the deserialized object's relationship graph. This method causes OracleAS TopLink to traverse all relationships from the deserialized object to its leaf objects and merge any changes it finds.
To maintain data integrity, OracleAS TopLink imposes a restriction on merging back serialized objects. If the outside JVM adds objects to the structure passed to it, and then passes back the new objects, OracleAS TopLink merges those objects into the model if one of the following conditions is met:
A primary key is a column (or combination of columns) that contains a unique identifier for every record in the table. OracleAS TopLink requires that every table that stores persistent objects has a primary key. The following concepts and techniques apply to primary keys:
Under most circumstances, you set primary key information in the OracleAS TopLink Mapping Workbench for persistent Java objects and EJB entity beans. Alternatively, set the primary key manually in Java code.
For more information, see "Implementing Primary Keys in Java".
A primary key is a mechanism by which OracleAS TopLink and other applications identify persistent objects and entity beans. EJB entity beans use primary keys in much the same way as regular Java objects, and as with Java objects, you usually set primary keys for entity beans in the OracleAS TopLink Mapping Workbench.
EJB entity beans support both simple primary keys, which are composed of information from a single field in the bean, and composite primary keys, which are composed of information from one or more fields and are stored in a custom class.
When you create tables that do not include a unique key suitable for use as a primary key, use sequencing to assign an identifier to each record. In most cases, you configure sequencing through the OracleAS TopLink Mapping Workbench.
For more information, see "Working with Sequencing" in the Oracle Application Server TopLink Mapping Workbench User's Guide. For more information about implementing sequencing in Java code, see "Implementing Sequence Numbers in Java".
This section describes how to assign primary keys to objects that use sequencing, and includes discussions on:
OracleAS TopLink offers three ways to implement sequencing. Although each method is unique, the three techniques have some commonality.
You store persistent objects for your application in database tables that represent the class of instantiated object. Each row of the table represents an instantiated object from that class, and one column in that table holds the primary key for each object. Sequencing populates the primary key row in the table.
When you configure sequencing, specify two settings for these tables, regardless of the type of sequencing you plan to use:
In addition to these two elements, table sequencing requires you to specify a SEQ_NAME
(a name to identify the class in a special sequencing table name) for each sequenced object. You configure these elements in the OracleAS TopLink Mapping Workbench.
To improve sequencing efficiency, OracleAS TopLink allows you to preallocate sequence numbers. Preallocation enables OracleAS TopLink to build a pool of available sequence numbers that are assigned to new objects as they are created and inserted into the database. OracleAS TopLink assigns numbers from the pool until the pool is exhausted.
The preallocation size specifies the size of the pool of available numbers. Preallocation improves sequencing efficiency by substantially reducing the number of database accesses required by sequencing. By default, OracleAS TopLink sets preallocation size to 50. You can specify preallocation size either in the OracleAS TopLink Mapping Workbench or as part of the session login.
For more information about setting sequencing parameters at session login, see "Setting Sequencing at Login".
Preallocation is available in table sequencing and is required for Oracle native sequencing.
Table sequencing involves creating and maintaining an extra database table that includes sequencing information for sequenced objects in the project. OracleAS TopLink maintains this table to track sequence numbers.
Sequencing information appears in this table for any class that uses sequencing. The default table is called SEQUENCE
and contains two columns:
SEQ_NAME
, which specifies the class type to which the selected row refers
SEQ_COUNT
, which specifies the highest sequence number currently allocated for the object represented in the selected row
The rows of the SEQUENCE
table represent every class that participates in sequencing. When you configure sequencing in the OracleAS TopLink Mapping Workbench, you specify the SEQ_NAME
for the class. OracleAS TopLink adds a row with that name to the SEQUENCE
table and initializes the SEQ_COUNT
column to the value 1
.
You can create the SEQUENCE
table on the database in one of two ways:
For more information about specifying tables in the OracleAS TopLink Mapping Workbench, see "Working with Database Tables in the Navigator Pane" in the Oracle Application Server TopLink Mapping Workbench User's Guide.
For more information, see "Creating the Sequence Table".
OracleAS TopLink includes an internal mechanism that manages table sequencing. This mechanism maintains a pool (a vector or array) of preallocated values for each sequenced class. When OracleAS TopLink exhausts this pool of values, it acquires a new pool of values, as follows:
SEQ_COUNT
for the given class (identified by the SEQ_NAME
) be incremented by the preallocation size and the result returned.
For example, consider the SEQUENCE
table in Figure 3-4. If you create a new purchase order and OracleAS TopLink has exhausted its pool of sequence numbers, then OracleAS TopLink executes SQL to increment SEQ_COUNT
for SEQ_PURCH_ORDER
by the preallocation size (in this case, the OracleAS TopLink default of 50). The database increments SEQ_COUNT
for SEQ_PURCH_ORDER
to 1600 and returns this number to OracleAS TopLink.
As you add new objects to the class table, OracleAS TopLink continues to assign values from the pool until it exhausts the pool. When the pool is exhausted, OracleAS TopLink again requests new values from the table.
In most cases, you implement table sequencing using the default table parameters. However, you may want to leverage the Custom Table option if:
OracleAS TopLink support for native sequencing with Oracle databases is similar to table sequencing, except that OracleAS TopLink does not maintain a table in the database. Instead, the Oracle database contains a SEQUENCE
object that stores the current maximum number and preallocation size for sequenced objects.
The Oracle SEQUENCE
object implements a strategy that closely resembles OracleAS TopLink sequencing: it implements an INCREMENT
construct that parallels the OracleAS TopLink preallocation size, and a sequence.nextval
construct that parallels the SEQ_COUNT
field in the OracleAS TopLink SEQUENCE
table in table sequencing. This implementation enables OracleAS TopLink to use the Oracle SEQUENCE
object as if it were an OracleAS TopLink SEQUENCE
table, but eliminates the need for OracleAS TopLink to create and maintain the table.
As with table sequencing, OracleAS TopLink creates a pool of available numbers by requesting that the Oracle SEQUENCE
object increment the sequence.nextval
and return the result. Oracle adds the value, INCREMENT
, to the sequence.nextval
, and OracleAS TopLink uses the result to build the sequencing pool.
The key difference between this process and the process involved in table sequencing is that OracleAS TopLink is unaware of the INCREMENT
construct on the SEQUENCE
object. OracleAS TopLink sequencing and the Oracle SEQUENCE object operate in isolation. To avoid sequencing errors in the application, set the OracleAS TopLink preallocation size and the Oracle SEQUENCE
object INCREMENT
to the same value.
Your Database Administrator (DBA) must create a SEQUENCE
object on the database for every sequencing series your application requires. If every class in your application requires its own sequence, the DBA creates a SEQUENCE
object for every class; if you design several classes to share a sequence, the DBA need only create one SEQUENCE
object for those classes.
For example, consider the case of a sporting goods manufacturer that manufactures three styles of tennis racquet. The data for these styles of racquet are stored in the database as follows:
The manufacturer can:
SEQUENCE
objects, perhaps called ATTACK_SEQ
, VOLLEY_SEQ
, and PROX_SEQ
. Each different racquet line has its own serial number series, and there may be duplication of serial numbers between the lines (for example: all three styles may include a racquet with serial number 1234).
SEQUENCE
object (perhaps called RACQUET_SEQ
). The manufacturer assigns serial numbers to racquets as they are produced, without regard for the style of racquet.
Several databases support a type of native sequencing in which the database management system (DBMS) generates the sequence numbers. When you create a class table for a class that uses sequencing, include a specified primary key column, and set the column type as follows:
IDENTITY
.
SERIAL
.
When you insert a new object into the table, OracleAS TopLink populates the object before insertion into the table, but does not include the sequence number. As the database inserts the object into its table, the database automatically populates the primary key field, with a value equal to the primary key of the previous object, plus 1.
At this point, and before the transaction closes, OracleAS TopLink reads back the primary key for the new object so that the object has an identity in the OracleAS TopLink cache.
To implement sequencing for CMP entity beans, use a sequencing strategy that implements preallocation, such as table sequencing or Oracle native sequencing. Preallocation ensures that the bean primary key is available at ejbPostCreate()
time. If use native sequencing as offered in Sybase, Microsoft SQL Server, or Informix databases, be aware that:
ejbPostCreate()
time.
ejbCreate
method to avoid problems with object identity in the OracleAS TopLink cache and the container.
The OracleAS TopLink CMP integration with IBM WebSphere does not automatically provide the primary key after calling the ejbCreate
method. If you deploy to a WebSphere server, explicitly set the primary key in the ejbCreate
method. Example 3-20 illustrates this call in a WebSphere integration.
public Integer ejbCreate() throws CreateException { oracle.toplink.ejb.cmp.was.SessionLookupHelper.getHelper().getSession(this) .getActiveUnitofWork().assignSequenceNumber(this); return null }
In the OracleAS TopLink CMP integration with BEA WebLogic, OracleAS TopLink automatically sets the primary key field on the bean. You do not pass the key value as a parameter to the create()
method, nor set them in the create()
method.
public Integer ejbCreate() throws CreateException { return null; }
The additional line of code looks up the correct session and uses it to assign a sequence number to the bean.
For more information about how to configure sequencing, see the Oracle Application Server TopLink Mapping Workbench User's Guide.
If you have stored procedures that perform sequencing for your application, use an amendment method to direct sequencing queries to use the stored procedures.
DataModifyQuery seqUpdateQuery = new DataModifyQuery(); StoredProcedureCall call = new StoredProcedureCall(); call.setProcedureName("UPDATE_SEQ"); seqUpdateQuery.addArgument("SEQ_NAME"}; seqUpdateQuery.setCall(call) project.getLogin().setUpdateSequenceQuery(seqUpdateQuery));
Example 3-22 illustrates specifying a stored procedure for sequence updates. The name of the stored procedure must match the name specified in the setProcedureName
call (in this case, UPDATE_SEQ
). The seqUpdateQuery.addArgument
contains one argument, the sequence name.
Example 3-23 illustrates the use of a stored procedure for sequence selects.
ValueReadQuery seqReadQuery = new ValueReadQuery(); StoredProcedureCall call = new StoredProcedureCall(); call.setProcedureName("SELECT_SEQ"); seqReadQuery.addArgument("SEQ_NAME"}; seqReadQuery.setCall(call) project.getLogin().setSelectSequenceNumberQuery (seqReadQuery));
The name of the stored procedure must match the name specified in the setProcedureName
call (in this case, SELECT_SEQ
). The seqUpdateQuery.addArgument
contains one argument, the sequence name.
A foreign key is a combination of columns that reference a unique key, usually the primary key, in another table. As with a primary key, a foreign key can be any number of fields, all of which are treated as a unit. A foreign key and the parent key it references must have the same number and types of fields.
OracleAS TopLink enables you to specify two types of foreign keys:
Relationship mappings use foreign keys to search the database for the information it requires to instantiate the target object or objects. For example, if every Employee has an attribute, address
, that contains an instance of Address
(which has its own descriptor and table), then the one-to-one mapping for the address
attribute specifies foreign key information to find an address for a particular Employee.
OracleAS TopLink enables you to store the information for a single class in multiple tables. This feature offers you the flexibility to create the objects for your application without imposing any new design requirements on your database schema.
For example, you can create a class called EMPLOYEE
that contains not just personal information about the employees, but also business information, such as salary. If your database schema stores salaries in a separate table from basic employee information, OracleAS TopLink multiple table mappings support enables you to create the class you require. Use multiple tables when either of the following is true:
You can associate information for the class using primary keys or foreign keys.
For more information about mapping a class to multiple tables, see "Working with Multiple Tables" in the Oracle Application Server TopLink Mapping Workbench User's Guide.
For more information about implementing multiple table mappings in code, see "Implementing Multiple Tables in Java".
To enable container-managed persistent (CMP) storage of entity beans in an Enterprise JavaBean (EJB) application, map the attributes on the bean implementation class. The implementation class is the class specified in the ejb-class
element for the specified bean in the ejb-jar.xml
deployment descriptor file. Do not map the Home
or Remote
interface classes, or the primary key classes.
If you use the OracleAS TopLink Mapping Workbench to build projects with entity beans, you can load the bean classes themselves into the OracleAS TopLink Mapping Workbench. You do not need to load the Remote
, Local
, Home
, and localHome
interfaces, or the primary key class, nor must you use these classes to define mappings.
To avoid errors when you load the beans, ensure that classes referenced by the entity beans are on the project class path used by the OracleAS TopLink Mapping Workbench project. The Remote
, Local
, Home
, and localHome
interfaces must also be on the class path, because they may be used during EJB validation.
Inheritance enables you to share attributes between objects such that a subclass inherits attributes from its parent class. OracleAS TopLink provides several methods to preserve inheritance relationships, and enables you to override mappings that are specified in a superclass, or to map attributes that are not mapped in the superclass. Subclasses must include the same database field (or fields) as the parent class for their primary key (although the primary key can have different names in these two tables). As a result, when you are mapping relationships to a subclass stored in a separate table, the subclass table must include the parent table primary key, even if the subclass primary key differs from the parent primary key.
This section describes OracleAS TopLink inheritance, and introduces several topics and techniques to leverage inheritance in your own applications, including:
For more information about implementing inheritance in code, see "Implementing Inheritance in Java".
Consider a simple database used by a courier company. It contains registration information for three types of vehicles: trucks, cars, and bicycles. For each vehicle type, your application requires the following information:
If these are all the attributes shared by all vehicles in the application, then these attributes must all appear in the super class, Vehicle. You can then build subclasses for each of the vehicle types that reflects their differences. For example, the Truck class might have an attribute indicating whether the local department of transportation considers it to be a commercial vehicle (NumAxles), the Car class might require a NumPass (number of passengers) attribute, and the Bicycle class, by virtue of its more limited range, may require a Location attribute. Through inheritance, each vehicle automatically inherits the basic vehicle information, but by being separate subclasses, also have unique characteristics.
You can represent inheritance in the database in one of two ways:
If your database already represents the objects in the inheritance hierarchy this way, you can map the objects and relationships without modifying the tables. However, it is most efficient to represent all classes from a given inheritance hierarchy in a single table, because it substantially reduces the number of table reads and eliminates joins when querying on objects in the hierarchy.
To consolidate tables in the database this way, determine the class type of the objects represented by the rows in the table. There are two ways to determine class type:
For more information about class indicators, see "Class Indicators".
For more information about class extraction methods, see "Class Extraction Methods".
The OracleAS TopLink inheritance hierarchy includes three types of classes:
The root class stores information for 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, you can also configure the root class to return only instances of itself, without instances of its subclasses when queried. All class types beneath the root class inherit from the root class.
Branch classes have a persistent superclass and subclasses. By default, queries performed on the branch class return instances of the branch class and any of its subclasses. As with the root class, you can configure the branch class to return only instances of itself without instances of its subclasses when queried. All classes below the branch class inherit attributes from the branch class, including any attributes the branch class inherits from classes above it in the hierarchy.
Leaf classes have a persistent superclass in the hierarchy, but do not have subclasses. Queries performed on the leaf class return only instances of the leaf class.
A class indicator is a mechanism for determining the class or type of an object. For example, a Person table might include an indication of whether the person represented by the table row is an Employee or a Manager. Use the class indicator to select the appropriate subclass to be instantiated from a set of available subclasses.
A class indicator field is a number or string stored in a database table that indicates the class or type of an object. OracleAS TopLink uses this information to determine the correct type of object to instantiate when building an object from that data in the row. For example, an EMPLOYEE
table might contain a field, the value of which indicates whether the employee is permanent or contract, and determines whether OracleAS TopLink instantiates a PermanentEmployee
object or a ContractEmployee
object.
You can use strings or numbers as values in the class indicator field in the database. The root class descriptor must specify how the value in the class indicator field translates into the class to be instantiated.
Class indicator fields do not have an associated direct mapping unless it is set to read-only. Mappings defined for the write-lock or class indicator field must be read-only, unless the write-lock is configured not to be stored in the cache and the class indicator is part of the primary key.
For more information about transformation mappings, see "Transformation Mappings".
Class extraction enables you to determine the correct class type to instantiate from a table that includes several classes. Unlike a class indicator, however, a class extraction method does not rely on a single column in the table to determine class type. Instead, you can apply logic to the information in several fields to determine class type.
This method is useful when you use a legacy database with a new application. Table 3-1 illustrates a sample use of the a class extraction method.
ID | NAME | JOB_TYPE | JOB_TITLE |
---|---|---|---|
732 |
Bob Jones |
1 |
Manager |
733 |
Sarah Smith |
3 |
Technical Writer |
734 |
Ben Ng |
2 |
Director |
735 |
Sally Johnson |
3 |
Programmer |
The inheritance hierarchy is designed such that Employee is the root class, and Director is a branch class that inherits from Employee. All employees, other than directors, are represented as instances of Employee, but directors must be represented by an instance of the Director class. Because values other than 2 can appear in the JOB_TYPE field, you cannot use OracleAS TopLink's class indicator mechanism for mapping this data.
To resolve this, add a class extraction method to the root class, Employee. The method executes custom logic to determine the correct class to instantiate. The method is static, returns a Class object, and takes DatabaseRow
as a single parameter.
// Return the Director class for TYPE values of 2, // Employee class for any other value public static Class getClassFromRow(DatabaseRow row) { if (row.get("JOB_TYPE").equals(new Integer(2)) { return Director.class; } else { return Employee.class; } }
This simple case enables you to determine whether the selected person is of the Director class or the Employee class. You can also implement complex logic that combines information from several columns in the table to infer class type. For example, consider a table that represents vehicles in a municipal vehicle pool. In addition to other information, Table 3-2 illustrates, the database includes data that indicates gross vehicle weight and number of axles.
Gross Vehicle Weight | Number Of Axles |
---|---|
2650 |
3 |
800 |
2 |
2730 |
2 |
2400 |
2 |
3580 |
4 |
Although there is no direct indication of vehicle type in the data, you can build logic into a class extraction method to infer the vehicle type. This is made easier if you are familiar with the available types in the database. In this example, you can use a class extraction method to implement the following logic:
NumberOfAxles
is greater than 2, then return the class HeavyTruck
.
NumberOfAxles
is 2 or less and GrossVehicleWeight
is greater than 1000, then return the class type PassengerVehicle
.
Motorcycle
.
public static Class getClassFromRow(DatabaseRow row) { if (row.get("NumberOfAxles").intValue()>2){ return HeavyTruck.class; } else { if (row.get("GrossVehicleWeight").intValue()>1000) { return PassengerVehicle.class; } else { return Motorcycle.class; } } }
In addition to implementing logic to determine object class, you can also use class extraction methods to execute other methods unrelated to class determination. This is an unusual use for class extraction methods but, provided that the method ultimately returns a class type, it is possible.
To implement the class extraction method in the OracleAS TopLink Mapping Workbench, open the inheritance settings for the root descriptor in the subclass hierarchy (EMPLOYEE
in this case), and select the class extraction method in the Use Class Extraction Method box.
The following restrictions apply to entity beans when using inheritance:
Home
interfaces cannot inherit. The findByPrimaryKey
method must be overloaded to have the correct return type, but this is not allowed. As a result, inheritance is not applicable to the Home
interfaces.
The Application Server EJB 1.1 and 2.0 CMP Advanced Examples illustrate inheritance. For more information, see the OracleAS TopLink Examples at <ORACLE_HOME>
\toplink\doc\examples.htm
.
EJB Entity beans represent a business entity. Entity beans can be shared by many users and are long-lived, able to survive a server failure. Essentially, entity beans are persistent data objects (objects with durable state that exist from one moment in time to the next).
This section describes entity bean development, as well as the following mapping topics and techniques:
An EJB implements a business task or a business entity. EJBs are server-side domain objects that fit into a component-based architecture for building enterprise applications using the Java language. EJBs are Java objects that the developer can install in an EJB server to make them distributed, transactional, and secure. OracleAS TopLink supports three kinds of EJBs under the EJB 2.0 specification: session beans, entity beans, and message-driven beans. Note that EJB 1.1 does not support message-driven beans.
An EJB bean resides in an EJB container that in turn resides in an EJB server. Although the EJB 2.0 specification does not define the container-server relationship, the accepted paradigm is that the server provides the bean with access to various services (transactions, security, and so on), and the container provides the execution context for the bean by managing its life cycle.
Deployment descriptors supply additional information that is required to install an EJB within its server. The deployment descriptors are of a set of XML files that provide the security, transaction, relationship, and persistence information for the bean.
Session beans represent a business operation, task, or process. Although the use of a session bean may involve database access, the beans are not in themselves persistent because they do not directly represent a database entry. Session beans do not always retain conversational state; they can be stateful and retain client information between calls. They can be stateless and retain information only within a single method call.
You can use OracleAS TopLink to make the regular Java objects that are accessed by a session bean persistent, or to access OracleAS TopLink persistent entity beans. Session beans may also act as wrappers to other legacy applications.
Entity beans represent a persistent data object that exists from one access to the next. Accomplish persistence by storing the object in a relational database, object database, or some other storage facility.
Two schemes exist for making entity beans persistent: bean-managed persistence (BMP) and container-managed persistence (CMP). BMP requires the bean developer to hand-code the methods that perform the persistence work. CMP uses information supplied by the developer to handle all aspects of persistence.
Message-driven beans process asynchronous Java Message Service (JMS) messages. A bean method is transactionally-invoked by a JMS message sent to the objects registered against the given topic. From a client perspective, a message-driven bean is simply a JMS consumer with no conversational state and no Home
or Remote
interfaces.
OracleAS TopLink provides a class oracle.toplink.ejb.bmp.BMPEntityBase
. This class provides developers with a starting point when developing beans. The BMPEntityBase
class provides implementation for all EJB specification required methods except ejbPassivate()
which is excluded because of special requirements. By subclassing the BMPEntityBase
, developers have an OracleAS TopLink-enabled entity bean.
To use the BMPEntityBase
, create the sessions.xml
file. For information about the sessions.xml
file, see "Session Manager". In addition, add an oracle.toplink.ejb.bmp.BMPWrapperPolicy
to each descriptor that represents an Entity Bean. This BMPWrapperPolicy
provides OracleAS TopLink with the information to create Remote objects for entity beans and to extract the data out of a Remote object. After this is performed, the user must create the Home
and Remote
interfaces, create deployment descriptors, and deploy the beans.
If a more customized approach is required, OracleAS TopLink provides a hook into its functionality through the oracle.toplink.ejb.bmp.BMPDataStore
class. Use this class to translate EJB-required functionality into simple calls.
The BMPDataStore
provides implementations of LOAD
and STORE
, multiple finders, and REMOVE
functionality. The BMPDataStore
, requires a sessions.xml
file and the session manager. A single instance of BMPDataStore
must exist for each bean type deployed within a session. When creating a BMPDataStore
, pass in the session name of the session that the BMPDataStore
must use to persist the beans and the class of the Bean type being persisted. Store the BMPDataStore
in a global location so that each instance of a Bean type uses the correct Store.
If you use a customized implementation, the full functionality of the server session and the UnitOfWork
is available.
To use BMP support with EJB 2.0, the Home
interface must inherit from the oracle.toplink.ejb.EJB20Home
. To make calls to the oracle.toplink.ejb.bmp.BMPEntityBase
, the FindAll()
method must call the EJB 2.0 version of the methods. These methods are prefixed with ejb20
.
For example, in the EJB 2.0 version, the findAll()
method appears as ejb20FindAll
.
To use local beans, use the oracle.toplink.ejb.EJB20LocalHome
setting instead of the default oracle.toplink.ejb.EJB20Home
.
Instead of the oracle.toplink.ejb.BMPWrapperPolicy
setting, use the oracle.toplink.ejb.bmp.BMPLocalWrapperPolicy
setting.
To accommodate both local and remote configurations, ensure the following:
Local
or Remote
interfaces, not both.
OracleAS TopLink CMP is an extension of the OracleAS TopLink persistence framework. OracleAS TopLink CMP support provides container-managed persistence for EJBs deployed in a J2EE container.
OracleAS TopLink CMP support enables complex mappings from entity beans to relational database tables and enables you to model bean-to-bean and bean-to-regular Java object relationships. OracleAS TopLink provides a rich set of querying options and allows query definition at the bean-level rather than the database level. OracleAS TopLink CMP supports the specification as defined by Sun Microsystems.
This section introduces the concepts required to use CMP facilities. It highlights the features that are specific to OracleAS TopLink CMP, and explains any differences in the use of other core features.
The common mechanism for developers to make beans persistent is to map beans to a relational database. The EJB specification describes the CMP entity bean as a type of bean for which the designer does not have to include calls to any particular persistence mechanism in the bean itself. The EJB Server and its tools use meta-information in the deployment descriptor to describe how the bean is to be persisted to a database. This is commonly referred to as automatic persistence.
OracleAS TopLink provides support for EJB 2.0 entity beans. Here are some specific features of EJB 2.0 that OracleAS TopLink supports:
ejb-jar.xml
file
ejbSelect
Table 3-3 illustrates the components that Java objects contain:
Table 3-4 illustrates the components that entity beans contain:
For more information about the Enterprise JavaBeans and the EJB specification, see
http://java.sun.com/products/ejb/ http://java.sun.com/products/ejb/docs.html http://java.sun.com/j2ee/white/index.html
When one-to-one or many-to-many mappings are bidirectional, maintain the back-pointers as the relationships change. When the relationship is between two entity beans (in EJB 2.0), OracleAS TopLink automatically maintains the relationship. However, when the relationship is between an entity bean and a Java object, or when the application is built to the EJB 1.1 specification, the relationship must be maintained manually. To set the back-pointer under the EJB 1.1 specification, do one of the following:
If you set back-pointers within the entity bean, the client is freed of this responsibility. This has the advantage of encapsulating the mapping maintenance implementation in the bean.
In a one-to-many mapping, a source bean might have several dependent target objects. For example, an EmployeeBean
might have several dependent phoneNumbers
. When a new dependent object (a phoneNumber
, in this example) is added to an employee record, the phoneNumber
's back-pointer to its owner (the employee) must also be set.
Maintaining a one-to-many relationship in the entity bean involves getting the local object reference from the context of the EmployeeBean
, and then updating the back-pointer. The following code illustrates this technique:
// obtain owner and phoneNumber
owner = empHome.findByPrimaryKey(ownerId); phoneNumber = new PhoneNumber("cell", "613", "5551212");// add phoneNumber to the phoneNumbers of the owner
owner.addPhoneNumber(phoneNumber);
The Employee's addPhoneNumber( )
method maintains the relationship, as follows:
public void addPhoneNumber(PhoneNumber newPhoneNumber) { //get, then set the back pointer to the owner Employee owner = (Employee)this.getEntityContext().getEJBLocalObject(); newPhoneNumber.setOwner(owner); //add new phone getPhoneNumbers().add(newPhoneNumber); }
The EJB 1.1 specification recommends that you model entity beans so that all dependent objects are regular Java objects and not other entity beans. If you expose a dependent or privately owned object to the client application, it must be serializable (that is, it implements the java.io.Serializable interface
) so that it can be sent over to the client and back to the server.
Because entity beans are remote objects, they are referenced remotely in a pass-by-reference fashion. When an entity bean is returned to the client, a remote reference to the bean is returned.
Unlike entity beans, regular Java objects are not remote objects. As a result, when regular Java objects are referenced remotely, they are passed by value (rather than by reference) and serialized (copied) from the remote machine on which they originally resided.
One of the effects of serializing regular Java objects between servers and clients is a loss of object identity, due to the copying semantics inherent in serialization. When you serialize a dependent object from the server to the client and then back, two objects with the same primary key but different object identities exist in the server cache. These objects must be merged to avoid exceptions.
If relationships exist between entity beans and Java objects, and these objects are serialized back and forth between the client and server, either:
SessionAccessor
utility class to perform the merge for you.
Use the class oracle.toplink.ejb.WebLogic.SessionAccessor
to perform merges for you within the set methods (on your bean class) that take regular Java objects as their arguments.
Two static methods are defined on the SessionAccessor
that allow you to do the register and merge operation:
This method requires two arguments: the object to merge and the EntityContext
for the bean.
public void setAddress(Address address) { this.address = (Address)SessionAccessor .registerOrMergeObject(address,this.ctx); }
The registerOrMergeAttribute()
method requires three arguments: the Java object to be merged, the name of the attribute, and the EntityContext
for the bean:
public void setAddress(Address address) { this.address = (Address) SessionAccessor.registerOrMergeAttribute (address, "address", this.ctx); }
To use the registerOrMergeAttribute()
call for collection mappings, pass the entire collection as the attribute object.
For example:
public void setPhones(Vector phones) {
this.phones = (Vector)SessionAccessor.registerOrMergeAttribute(phones,
"phones", this.ctx);
//... additional logic to set back-pointers on the phones
}
The registerOrMergeObject()
method is not as simple to use for setters of collection mappings. It requires that you iterate through the collection and invoke the registerOrMergeObject()
for each element in the collection. You must also create a new collection, set in the entity bean, to hold the return values of the call.
Merging code may be required in methods that add elements to a collection.
For example:
/* The old version of this phone number is removed from the collection. It is assumed that equals() returns true for phones with the same primary key value. If this is not true, you must iterated through the phones to see if a phone with the same primary key already exists in the collection. */
public void addPhoneNumber(PhoneNumber phone) { phone.setOwner((Employee)this.ctx.getEJBObject());//add to collection
//merge new phone
PhoneNumber serverSidePhone = (PhoneNumber)SessionAccessor.registerOrMergeObject(phone,this.ctx);//set back pointer
getPhoneNumbers().addElement(serverSidePhone); }}
There are several ways to merge objects manually. For example, you can use a set()
method as follows:
public void setAddress(Address address) { if(this.address == null){ this.address = address; } else{ this.address.merge(address); } }
You must merge objects when they are added to a collection on the entity bean, unless the objects cannot be added more than once to a collection in which case, merging is not necessary.
Merging a collection requires more work. Determine if a copy of each object already exists in the collection, and if so, merge the two copies. If not, you need only add the new object to the collection.
Unlike EJBs, OracleAS TopLink dependent persistent objects can be sent back and forth between a client and the server. When objects are serialized, the risk exists the objects can cause the cache to lose the identity of the objects or attempt to cache duplicate identical objects. To avoid potential problems, use the bean set methods when adding dependent objects to relationship collections. This enables OracleAS TopLink to handle merging of objects in the cache.
addPhoneNumber(PhoneNumber phone) { Collection phones = this.getPhoneNumbers(); Vector newCollection = new Vector(); newCollection.addAll(phones); newCollection.add(phone); this.setPhones(newCollection); }
Collections generally use the equals()
method to compare objects. However, in the case of a Java object that contains a collection of entities, the EJBObjects
do not respond as expected to the equals()
method. If you manage a collection of entities under EJB 1.1, we recommend the use of the isIdentical()
method to avoid problems.
In addition, the standard collection methods, such as remove()
or contains()
, frequently return unexpected results and so must be avoided.
Several options are available when dealing with collections of EJBObjects
. One option is to create a helper class to assist with collection-type operations. Example 3-29 illustrates the use of a helper in the EJBCollectionHelper
distribution.
public void removeOwner(Employee previousOwner){ EJBCollectionHelper.remove(previousOwner, getOwners()); }
Example 3-30 illustrates the implementation of remove()
and indexOf()
in EJBCollectionHelper
.
public static boolean remove(javax.ejb.EJBObject ejbObject, Vector vector) {
int index = -1;
index = indexOf(ejbObject, vector);
// indexOf returns -1 if the element is not found.
if(index == -1){
return false;
}
try{
vector.removeElementAt(index);
} catch(ArrayIndexOutOfBoundsException badIndex){
return false;
}
return true;
}
public static int indexOf(javax.ejb.EJBObject ejbObject, Vector vector) {
Enumeration elements = vector.elements();
boolean found = false;
int index = 0;
javax.ejb.EJBObject current = null;
while(elements.hasMoreElements()){
try{
current = (javax.ejb.EJBObject)
elements.nextElement();
if(ejbObject.isIdentical(current)){
found = true;
break;
}
}catch(ClassCastException wrongTypeOfElement){
. . .
}catch (java.rmi.RemoteException otherError){
. . .
}
index++; //increment index counter
}
if(found){
return index;
} else{
return -1;
}
}
You can create a special Collection class that uses isIdentical()
instead of equals()
for its comparison operations. To use isIdentical()
, properly define the equals()
method for the primary key class.
You can validate descriptors in two ways:
For more information about descriptor exceptions, see "Descriptor Exception".
For more information about the Integrity Checker, see "Using the Integrity Checker".
Several complex mappings are available in OracleAS TopLink. This section discusses the following mapping types:
Transformation mappings enable you to create specialized translations between how a value is represented in Java and in the database. Use transformation mappings only when mapping multiple fields into a single attribute. Transformation mapping is often appropriate when you use values from multiple fields to create an object.
After you create the required transformation method, use the OracleAS TopLink Mapping Workbench to implement transformation mappings.
For more information, see "Working with Transformation Mappings" in Oracle Application Server TopLink Mapping Workbench User's Guide.
Transformation mappings are instances of the TransformationMapping
class and require the following elements:
setAttributeName( )
message; not required for write-only mappings
setAttributeTransformation( )
message that expects one or two parameters: a DatabaseRow
and optionally a Session
addFieldTransformation( )
message, passing along the database field name and the method name
Use the optional setGetMethodName( )
and setSetMethodName( )
messages to access the attribute through user-defined methods, rather than directly.
This example provides custom support for two fields. You can use this approach to map any number of fields.
// Create a new mapping and register it with the descriptor. TransformationMapping transformation1 = new TransformationMapping(); transformation1.setAttributeName ("dateAndTimeOfBirth"); transformation1.setAttributeTransformation ("buildDateAndTime"); transformation1.addFieldTransformation("B_DAY", "getDateOfBirth"); transformation1.addFieldTransformation("B_TIME", "getTimeOfBirth"); descriptor.addMapping(transformation1); // Define attribute transformation method to read from the database row public java.util.Date buildDateAndTime(DatabaseRow row) { java.sql.Date sqlDateOfBirth = (java.sql.Date)row.get("B_DAY"); java.sql.Time timeOfBirth = (java.sql.Time)row.get("B_TIME"); java.util.Date utilDateOfBirth = new java.util.Date( sqlDateOfBirth.getYear(), sqlDateOfBirth.getMonth(), sqlDateOfBirth.getDate(), timeOfBirth.getHours(), timeOfBirth.getMinutes(), timeOfBirth.getSeconds()); return utilDateOfBirth; } // Define a field transformation method to write to the database public java.sql.Time getTimeOfBirth() { return new java.sql.Time this.dateAndTimeOfBirth.getHours(), this. dateAndTimeOfBirth.getMinutes(), this.dateAndTimeOfBirth.getSeconds()); } // Define a field transformation method to write to the database public java.sql.Date getDateOfBirth() { return new java.sql.DateOfBirth this.dateAndTimeOfBirth.getYear(), this.dateAndTimeOfBirth.getMonth(), this.dateAndTimeOfBirth.getDate()); }
// Create a new mapping and register it with the descriptor. TransformationMapping transformation2 = new transformation2.setAttributeName("designation"); transformation2.setGetMethodName ("getDesignationHolder"); transformation2.setSetMethodName ("setDesignationHolder"); transformation2.setAttributeTransformation ("getRankFromRow"); transformation2.addFieldTransformation("RANK", "getRankFromObject"); transformation2.useIndirection(); descriptor.addMapping(transformation2);
//Define an attribute transformation method to read from database row.public String getRankFromRow()
{
Integer value = new Integer(((Number)row.get("RANK)).intValue()); String rank = null; if (value.intValue() == 1) { rank = "Executive"; } if (value.intValue() == 2) { rank = "Non-Executive"; } return rank;}
//Define a field transformation method to write to the database. public Integer getRankFromObject() { Integer rank = null; if (getDesignation().equals("Executive")) rank = new Integer(1); if (getDesignation().equals("Non-Executive")) rank = new Integer(2); return rank; } //Provide accessor methods for the indirection. private ValueHolderInterface designation; public ValueHolderInterface getDesignationHolder() { return designation; } public void setDesignationHolder(ValueHolderInterface value) { designation = value; }
For more information about the available methods for TransformationMapping
, see the Oracle Application Server TopLink API Reference.
Serialized object mappings are used to store large data objects, such as multimedia files and BLOBs, in the database. Serialization transforms these large objects as a stream of bits.
Serialized object mappings are instances of the SerializedObjectMapping
class and require the following elements:
setAttributeName( )
message
setFieldName( )
message
Use the optional setGetMethodName( )
and setSetMethodName( )
messages to access the attribute through user-defined methods, rather than directly. You do not have to define accessors when you use Java 2.
// Create a new mapping and register it with the descriptor.SerializedObjectMapping serializedMapping = new SerializedObjectMapping();
serializedMapping.setAttributeName("jobDescription");
serializedMapping.setFieldName("JOB_DESC");
descriptor.addMapping(serializedMapping);
For more information about the available methods for SerializedObjectMapping
, see the Oracle Application Server TopLink API Reference.
Variable one-to-one mappings are instances of the VariableOneToOneMapping()
class and require the following elements:
setAttributeName( )
message
setReferenceClass( )
message
setForeignQueryKeyName( )
message and passing the source foreign key field name and the target abstract query key name on the interface descriptor
If the mapping uses a class indicator field:
VariableOneToOneMapping variableOneToOneMapping = new VariableOneToOneMapping(); variableOneToOneMapping.setAttributeName("contact"); variableOneToOneMapping.setReferenceClass (Contact.class); variableOneToOneMapping.setForeignQueryKeyName ("C_ID", "id"); variableOneToOneMapping.setTypeFieldName("TYPE"); variableOneToOneMapping.addClassIndicator(Email.class, "Email"); variableOneToOneMapping.addClassIndicator(Phone.class, "Phone"); variableOneToOneMapping.dontUseIndirection(); variableOneToOneMapping.privateOwnedRelationship();
VariableOneToOneMapping variableOneToOneMapping = new VariableOneToOneMapping(); variableOneToOneMapping.setAttributeName("contact"); variableOneToOneMapping.setReferenceClass (Contact.class); variableOneToOneMapping.setForeignQueryKeyName ("C_ID", "id"); variableOneToOneMapping.dontUseIndirection(); variableOneToOneMapping.privateOwnedRelationship();
For more information about the available methods for VariableOneToOneMapping
, see the Oracle Application Server TopLink API Reference.
Relational mappings defines the reference between persistent objects. Object relational mappings enable you to persist an object model into an object-relational data model. The OracleAS TopLink Mapping Workbench does not directly support these mappings--you must define them in code through amendment methods.
OracleAS TopLink supports the following object-relational mappings:
In an object-relational data-model, structures can contain arrays (collections of other data types). These arrays can contain primitive data types or collections of other structures. OracleAS TopLink stores the arrays with their parent structure in the same table.
All elements in the array must be the same data type. The number of elements in an array controls the size of the array. An Oracle database allows arrays of variable sizes (called Varrays).
Oracle8i or higher offers two collection types:
OracleAS TopLink supports arrays of primitive data through the ArrayMapping
class. This is similar to DirectCollectionMapping
--it represents a collection of primitives in Java. However, the ArrayMapping
class does not require an additional table to store the values in the collection.
OracleAS TopLink supports arrays of aggregate structures through the ObjectArrayMapping
class.
OracleAS TopLink supports nested tables through the NestedTableMapping
class.
Array mappings are instances of the ArrayMapping
class and require the following elements:
setAttributeName( )
message
setFieldName( )
message
setStructureName( )
message
// Create a new mapping and register it with the source descriptor. ArrayMapping arrayMapping = new ArrayMapping(); arrayMapping.setAttributeName("responsibilities"); arrayMapping.setStructureName("Responsibilities_t"); arrayMapping.setFieldName("RESPONSIBILITIES"); descriptor.addMapping(arrayMapping);
In addition to the API Example 3-36 illustrates, other common API for use with implement array mapping include:
setReferenceClass(Class referenceClass)
: to set the parent class
setGetMethodName(String name)
and setSetMethodName(String name)
: to provide method access
For more information about the available methods for ArrayMapping
, see the Oracle Application Server TopLink API Reference.
In an object-relational data-model, object arrays allow for an array of object types or structures to be embedded into a single column in a database table or an object table.
OracleAS TopLink supports object array mappings to define a collection-aggregated relationship in which the target objects share the same row as the source object.
Object array mappings are instances of the ObjectArrayMapping
class. You must associate this mapping to an attribute in the parent class. Object array mappings require the following elements:
setAttributeName( )
message
setFieldName( )
message
setStructureName( )
message
Use the optional setGetMethodName( )
and setSetMethodName( )
messages to access the attribute through user defined methods, rather than directly.
// Create a new mapping and register it with the source descriptor. ObjectArrayMapping phonesMapping = new ObjectArrayMapping(); phonesMapping.setAttributeName("phones"); phonesMapping.setGetMethodName("getPhones"); phonesMapping.setSetMethodName("setPhones"); phonesMapping.setStructureName("PHONELIST_TYPE"); phonesMapping.setReferenceClass(Phone.class); phonesMapping.setFieldName("PHONES"); descriptor.addMapping(phonesMapping);
For more information about the available methods for ObjectArrayMapping
, see the Oracle Application Server TopLink API Reference.
In an object-relational data-model, structures are user defined data types or object-types. This is similar to a Java class--it defines attributes or fields in which each attribute is either:
OracleAS TopLink maps each structure to a Java class defined in your object model and defines a descriptor for each class. A StructureMapping
maps nested structures, similar to an AggregateObjectMapping
. However, the structure mapping supports null values and shared aggregates without requiring additional settings (because of the object-relational support of the database).
Structure mappings are instances of the StructureMapping
class. You must associate this mapping to an attribute in each of the parent classes. Structure mappings require the following elements:
setAttributeName( )
message
setFieldName( )
message
setReferenceClass( )
message
Use the optional setGetMethodName( )
and setSetMethodName( )
messageto access the attribute through user-defined methods, rather than directly.
Make the following changes to the target (child) class descriptor:
descriptorIsAggregate( )
message to indicate it is not a root level
// Create a new mapping and register it with the source descriptor. StructureMapping structureMapping = new StructureMapping(); structureMapping.setAttributeName("address"); structureMapping.setReferenceClass(Address.class); structureMapping.setFieldName("address"); descriptor.addMapping(structureMapping);
The aggregate target descriptor does not need a mapping to its parent, or any table or primary key information.
// Create a descriptor for the aggregate class. The table name and primary key are not specified in the aggregate descriptor. ObjectRelationalDescriptor descriptor = new ObjectRelationalDescriptor (); descriptor.setJavaClass(Address.class); descriptor.setStructureName("ADDRESS_T"); descriptor.descriptorIsAggregate(); // Define the field ordering descriptor.addFieldOrdering("STREET"); descriptor.addFieldOrdering("CITY"); ... // Define the attribute mappings or relationship mappings. ...
In addition to the API Example 3-39 illustrates, other common API for use with structure mapping include:
For more information about the available methods for StructureMapping
, see the Oracle Application Server TopLink API Reference.
In an object-relational data-model, structures reference each other through refs--not through foreign keys (as in a traditional data model). Refs are based on the target structure's ObjectID
.
OracleAS TopLink supports refs through the ReferenceMapping
. They represent an object reference in Java, similar to a OneToOneMapping
. However, the reference mapping does not require foreign key information.
Reference mappings are instances of the ReferenceMapping
class. You must associate this mapping to an attribute in the source class. Reference mappings require the following elements:
setAttributeName( )
message
setFieldName( )
message
setReferenceClass ( )
message
Use the optional setGetMethodName( )
and setSetMethodName( )
messages to access the attribute through user-defined methods, rather than directly.
// Create a new mapping and register it with the source descriptor. ReferenceMapping referenceMapping = new ReferenceMapping(); referenceMapping.setAttributeName("manager"); referenceMapping.setReferenceClass(Employee.class); referenceMapping.setFieldName("MANAGER"); descriptor.addMapping(refrenceMapping);
In addition to the API Example 3-40 illustrates, other common API for use with reference mappings include:
useBasicIndirection()
: implements OracleAS TopLink valueholder indirection
dontUseIndirection()
readWrite()
readOnly()
setIsReadOnly(boolean readOnly)
For more information about the available methods for ReferenceMapping
, see the Oracle Application Server TopLink API Reference.
Nested table types model an unordered set of elements. These elements may be built-in or user-defined types. You can view a nested table as a single-column table or, if the nested table is an object type, as a multi-column table (with a column for each attribute of the object type).
Nested tables represent a one-to-many or many-to-many relationship of references to another independent structure. They support querying and joining better than Varrays
that are inlined to the parent table.
OracleAS TopLink supports nested table through the NestedTableMapping
. They represent a collection of object references in Java, similar to a OneToManyMapping
or ManyToManyMapping
. However, the nested table mapping does not require foreign key information (such as a one-to-many mapping) or the relational table (such as a many-to-many mapping).
Nested table mappings are instances of the NestedTableMapping
class. This mapping is associated to an attribute in the parent class. Nested table mapping require the following elements:
setAttributeName( )
message
setFieldName( )
message
setStructureName( )
message
Use the optional setGetMethodName( )
and setSetMethodName( )
messages to allow OracleAS TopLink to access the attribute through user-defined methods, rather than directly.
// Create a new mapping and register it with the source descriptor. NestedTableMapping policiesMapping = new NestedTableMapping(); policiesMapping.setAttributeName("policies"); policiesMapping.setGetMethodName("getPolicies"); policiesMapping.setSetMethodName("setPolicies"); policiesMapping.setReferenceClass(Policy.class); policiesMapping.dontUseIndirection(); policiesMapping.setStructureName("POLICIES_TYPE"); policiesMapping.setFieldName("POLICIES"); policiesMapping.privateOwnedRelationship(); policiesMapping.setSelectionSQLString("select p.* from policyHolders ph, table(ph.policies) t, policies p where ph.ssn=#SSN and ref(p) = value(t)"); descriptor.addMapping(policiesMapping);
In addition to the API Example 3-41 illustrates, other common API for use with nested table mappings include:
useBasicIndirection()
: implements OracleAS TopLink valueholder indirection
dontUseIndirection()
setUsesIndirection(boolean usesIndirection)
independentRelationship()
privateOwnedRelationship()
setIsPrivateOwned(Boolean isPrivateOwned)
For more information about the available methods for NestedTableMapping
, see the Oracle Application Server TopLink API Reference.
Direct map mappings store instances that implement java.util.Map
. Unlike one-to-many or many-to-many mappings, the keys and values of the map in this type of mapping are Java objects that do not have descriptors. The object type stored in the key and the value of direct map are Java primitive wrapper types such as String objects.
Support for primitive data types such as int
is not provided because Java maps only hold objects.
Direct map mappings are instances of the DirectMapMapping
class and require the following elements:
setAttributeName( )
message
setReferenceTableName( )
message
setDirectKeyFieldName( )
message
setReferenceKeyFieldName( )
message and passing the name of the field that is a foreign reference to the primary key of the source object
setDirectFieldName( )
message
setKeyClass( )
message
setValueClass( )
message
DirectMapMapping directMapMapping = new DirectMapMapping(); directMapMapping.setAttributeName("cities"); directMapMapping.setReferenceTableName("CITY_TEMP"); directMapMapping.setReferenceKeyFieldName("RECORD_ID"); directMapMapping.setDirectKeyFieldName("CITY"); directMapMapping.setDirectFieldName("TEMPERATURE"); directMapMapping.setKeyClass(String.class); directMapMapping.setValueClass(Integer.class); descriptor.addMapping(directMapMapping);
In addition to the API Example 3-42 illustrates, other common API for use with direct map mappings include:
useBasicIndirection()
: implements OracleAS TopLink valueholder indirection
useTransparentCollection()
: if you use transparent indirection, this element places a special collection in the source object's attribute
dontUseIndirection()
: implements no indirection
For more information about the available methods for DirectMapMapping
, see the Oracle Application Server TopLink API Reference.
OracleAS TopLink projects, descriptors, and mapping are normally created using the OracleAS TopLink Mapping Workbench. The output of the OracleAS TopLink Mapping Workbench is an XML file that contains the mapping information required to store persistent objects in the database.
The OracleAS TopLink Mapping Workbench does not offer access to all the customization available to the OracleAS TopLink descriptors that make up the project. In these situations, to customize the mapping information, you can specify an amendment method to be run at deployment time. Each OracleAS TopLink descriptor can have an amendment method.
This section describes some of the available customization topics and techniques, including:
Amendment methods are static methods that run at deployment time and enable you to implement descriptor customization code. You can modify the OracleAS TopLink descriptor of any persistent class with an amendment method when you first instantiate the descriptor. For container-managed persistence, this happens when the entity beans are deployed into the EJB server.
For more information about amendment methods, see the Oracle Application Server TopLink Mapping Workbench User's Guide.
Some OracleAS TopLink features cannot be configured from the OracleAS TopLink Mapping Workbench. To use these features, amend the descriptor after it is loaded as part of the project. After load methods are a type of amendment method that enables you to modify descriptors in code after you create the project object (either from an XML project or a project class).
To access descriptors from the project object or the session object (after the session object is created from the project), write a Java method that takes the name of the descriptor as a single parameter. You can then send messages to the descriptor or any of its specific mappings to configure advanced features. Make all descriptor changes before the session logs in. Any descriptor change made after login is ignored.
For more information, see "Amending Descriptors After Loading in Oracle Application Server TopLink Mapping Workbench User's Guide.
Use any of the following APIs to implement after load methods:
For more information about these APIs, see the Oracle Application Server TopLink API Reference.
The descriptor event manager enables you to create events that trigger other events in your application. You use the Event Manager to invoke specific events when OracleAS TopLink reads, updates, deletes, or inserts objects on the database.
Descriptor events enable you to:
You specify descriptor events in the OracleAS TopLink Mapping Workbench.
For more information, see "Specifying Events" in the Oracle Application Server TopLink Mapping Workbench User's Guide.
Applications receive descriptor events in several ways:
Register objects that implement the DescriptorEventListener
interface with the descriptor event manager. The descriptor event manager then notifies the object when any event occurs for that descriptor.
Use the DescriptorEventAdapter
class if your application does not require all the methods defined in the DescriptorEventListener
interface. The DescriptorEventAdapter
implements the DescriptorEventListener
interface and defines an empty method for each method in the interface. To use the adapter, subclass it and then register your new object with the descriptor event manager.
Register a public method as an event method. The descriptor then calls the event method when a database operation occurs. The event method must:
If you want an object other than the domain object to handle these events, register it as a listener with the descriptor event manager. If you want a LockManager
to receive events for all Employees
, then modify your descriptor amendment to register the LockManager
as the listener.
Any object you register as a listener must implement the DescriptorEventListener
interface. The amendment method appears in Example 3-43.
public static void addToDescriptor(Descriptor descriptor) { descriptor.getEventManager().addListener(LockManager.activeManager()); }
Table 3-5 summarizes the most common public methods for DescriptorEventManager
. For more information about the available methods for DescriptorEventManager
, see the Oracle Application Server TopLink API Reference.
The DescriptorEventManager
supports several methods, including those in Table 3-6.
The OracleAS TopLink Unit of Work feature uses copies of object (clones) rather than the original objects to perform its tasks. You can construct clones as follows:
descriptor.createCopyPolicy("clone");
When the Unit of Work requires a clone of this object, it calls the clone()
method to create the copy.
useCloneCopyPolicy(String)
The String
in this method is the name of another method that clones the object.
The most common way to use any policy other than the default (using the object default constructor) is to create an amendment method and specify it in the OracleAS TopLink Mapping Workbench when you configure the class.
For more information about amendment methods, see "Customizing OracleAS TopLink Descriptors with Amendment Methods".
For more information about implementing descriptor copy policy in code, see "Setting the Copy Policy in Java".
You can add queries to a descriptor (named queries) for execution later in the application. For example, you can add the following code to a descriptor:
ReadObjectQuery aQuery = new ReadObjectQuery(Employee.class); descriptor.getQueryManager().addQuery("readAnEmployee", aQuery);
You can accomplish this with an amendment method.
For more information about amendment methods, see "Customizing OracleAS TopLink Descriptors with Amendment Methods".
You can replace all queries in an OracleAS TopLink descriptor with user defined queries. Doing this enables you to change query behavior or to substitute stored procedures for the queries.
This example illustrates how to force the read object descriptor call to use a stored procedure.
ReadObjectQuery query = new ReadObjectQuery(); StoredProcedureCall call = new StoredProcedureCall(); call.setProcedureName("READ_RDM_EMP"); query.setCall(call); descriptor.getQueryManager().setReadObjectQuery (query);
The Descriptor
class provides the following methods to specify how objects get instantiated:
useDefaultConstructorInstantiationPolicy()
: Instructs OracleAS TopLink to use the default constructor to create new instances of objects built from the database. This method can be private, protected, or default/package.
useFactoryInstantiationPolicy(Object, String)
: Instructs OracleAS TopLink to send the message specified by the String
parameter to an object factory specified by the Object
parameter to create objects from the database. The object factory method can be public, private, protected, or default/package and requires no arguments.
useMethodInstantiationPolicy(String)
: Instructs OracleAS TopLink to send the message contained in the string parameter to create objects that are populated with data from the database. This method can be a public, static method on the descriptor class, or it can be private, protected, or default/package. It must return a new instance of the class.
useFactoryInstantiationPolicy(Class factoryClass, String methodName)
: Instructs OracleAS TopLink to send the message contained in the String
parameter to an instance of the specified factoryClass
. This method must be return a new instance of the descriptor class. To instantiate the factory, OracleAS TopLink invokes the default constructor of the specified factoryClass
. Both the factoryClass
default constructor and the method invoked on the factory can be private, protected, or default/package.
useFactoryInstantiationPolicy(Class factoryClass, String methodName, String factoryMethodName)
: Instructs OracleAS TopLink to send the message contained in the first String
parameter, methodName
, to an instance of the specified factoryClass
. This method must return a new instance of the descriptor class. To instantiate the factory, OracleAS TopLink invokes the second String
, methodName
on the specified factoryClass
. This method must be a static method on the factoryClass
and must return an instance of the factoryClass
. The factory class static factory method and the method invoked on the factory can be private, protected, or default/package.
The Descriptor
class provides methods used in conjunction with the wrapper policy:
setWrapperPolicy(oracle.toplink.descriptors.WrapperPolicy)
: can be invoked to provide a wrapper policy for the descriptor
getWrapperPolicy()
: returns the wrapper policy for a descriptor
Create mappings and OracleAS TopLink descriptors to access features that are not available in the OracleAS TopLink Mapping Workbench.
To define a project using Java code:
oracle.toplink.sessions.Project
class.
toplink-ejb-jar.xml
deployment descriptor so that the value for the project-class
element is the fully-qualified Project class name.
For more information about creating project classes, see the Oracle Application Server TopLink Mapping Workbench User's Guide.
Note:
Use the OracleAS TopLink Mapping Workbench to create a Java Project class from an existing project. This provides a starting point for a custom project class. For more information, see the Oracle Application Server TopLink Mapping Workbench User's Guide.
You can also use the OracleAS TopLink Mapping Workbench Export Project to Java Source... menu command to create a starting point for coding the project class manually.
Example 3-45 illustrates how you can specify OracleAS TopLink projects in code.
/**
* The class EmployeeProject is an example of an OracleAS TopLink project defined in Java code. The individual parts of the project - the Login and the descriptors, are built inside of methods that are called by the constructor. Note that EmployeeProject extends the class oracle.toplink.sessions.Project.
*/
public class EmployeeProject extends oracle.toplink.sessions.Project{/**
* Supply a zero argument constructor that initializes all aspects of the project. Make sure that the login and all the descriptors are initialized and added to the project.
*/
public EmployeeProject(){ applyPROJECT(); applyLOGIN(); buildAddressDescriptor(); buildEmployeeDescriptor();// other methods to build all descriptors for the project
/**
* Project-level properties, such as the name of the project, should be specified here.
*/
protected void applyPROJECT(){ setName("Employee"); } protected void applyLOGIN() { oracle.toplink.sessions.DatabaseLogin login = new oracle.toplink.sessions.DatabaseLogin();// use platform appropriate for underlying database
login.setPlatformClassName( "oracle.toplink.internal.databaseaccess. OraclePlatform");// if no sequencing is used, setLogin() will suffice
setLoginAndApplySequenceProperties(login); }/**
* Descriptors are built by defining table info, setting properties (caching, etc.) and by adding mappings to the descriptor.
*/
protected void buildEmployeeDescriptor() { oracle.toplink.publicinterface.Descriptor descriptor = new oracle.toplink.publicinterface.Descriptor(); }// SECTION: DESCRIPTOR
// specify the class to be made persistent
descriptor.setJavaClass(examples.ejb.cmp11.advanced.EmployeeBean.class);// specify the tables to be used and primary key
Vector tables = new Vector(); tables.addElement("EJB_EMPLOYEE"); descriptor.setTableNames(tables); descriptor.addPrimaryKeyFieldName("EJB_EMPLOYEE.EMP_ID");// SECTION: PROPERTIES
descriptor.setIdentityMapClass( oracle.toplink.internal.identitymaps. FullIdentityMap.class); descriptor.setExistenceChecking("Check cache"); descriptor.setIdentityMapSize(100);// SECTION: COPY POLICY
descriptor.createCopyPolicy("constructor");// SECTION: INSTANTIATION POLICY
descriptor.createInstantiationPolicy("constructor");// SECTION: DIRECTTOFIELDMAPPING
oracle.toplink.mappings.DirectToFieldMapping firstNameMapping = new oracle.toplink.mappings .DirectToFieldMapping(); firstNameMapping.setAttributeName("firstName"); firstNameMapping.setIsReadOnly(false); firstNameMapping.setFieldName("EJB_EMPLOYEE.F_NAME"); descriptor.addMapping(firstNameMapping);// ... Additional mappings are added to the descriptor using the addMapping() method.
},}
To deploy the OracleAS TopLink project, specify the project class name in the project-class
element in the toplink-ejb-jar.xml
file for your entity beans.
For example:
<session> <name>EmployeeDemo</name> <project-class>oracle.toplink.demos.ejb.cmp.wls.employee.EmployeeProject </project-class> <login> <connection-pool>ejbPool</connection-pool> </login> </session>
In most cases, the OracleAS TopLink Mapping Workbench is the preferred tool to create OracleAS TopLink elements however, OracleAS TopLink also supports building components of your application in Java code. You can code components ranging in size from small elements to complete projects. This section illustrates the techniques required for building several of these components, and includes discussions on:
Use the ObjectRelationalDescriptor
class to define object-relational descriptors. This descriptor subclass contains the following additional properties:
The OracleAS TopLink Remote (RMI) Example illustrates an object-relational data model and descriptors. For more information, see the OracleAS TopLink Examples at <ORACLE_HOME>
\toplink\doc\examples.htm
.
import oracle.toplink.objectrelational.*; ObjectRelationalDescriptor descriptor = new ObjectRelationalDescriptor() descriptor.setJavaClass(Employee.class); descriptor.setTableName("EMPLOYEES"); descriptor.setStructureName("EMPLOYEE_T"); descriptor.setPrimaryKeyFieldName("OBJECT_ID"); descriptor.addFieldOrdering("OBJECT_ID"); descriptor.addFieldOrdering("F_NAME"); descriptor.addFieldOrdering("L_NAME"); descriptor.addFieldOrdering("ADDRESS"); descriptor.addFieldOrdering("MANAGER"); descriptor.addDirectMapping("id", "OBJECT_ID"); descriptor.addDirectMapping("firstName", "F_NAME"); descriptor.addDirectMapping("lastName", "L_NAME"); //Refer to the mappings section for examples of object relational mappings. ...
If a single field constitutes the primary key, send the setPrimaryKeyFieldName()
message to the descriptor. For a composite primary key, send the addPrimaryKeyFieldName()
message for each field that makes up the primary key.
Alternatively, use the setPrimaryKeyFieldNames( )
message that sends a Vector
of the fields used as the primary key.
// Define a new descriptor and set the primary key. descriptor.setPrimaryKeyFieldName("ADDRESS_ID");
// Define a new descriptor and set the primary key.
descriptor1.addPrimaryKeyFieldName("PHONE_NUMBER");
descriptor1.addPrimaryKeyFieldName("AREA_CODE");
Although you can implement inheritance hierarchy in Java, under most circumstances, we recommend you use the OracleAS TopLink Mapping Workbench.
To implement an inheritance hierarchy completely in Java, modify the descriptors for the superclass and its subclasses. The inheritance implementation for a descriptor is encapsulated in an InheritancePolicy
object, which is accessed by sending getInheritancePolicy()
to the descriptor:
setClassIndicatorFieldName()
message to the InheritancePolicy
of the root class. The parameter is a string that indicates the table column that holds the subclass type information.
addClassIndicator()
message for each of the instantiable subclasses in the hierarchy. This message requires two parameters--the indicator value and the subclass it represents.
useClassNameAsIndicator()
message. This stores the full name of the class in the class indicator field.
setParentClass()
message to the descriptor for each subclass.
dontReadSubclassesOnQueries()
method.
If a superclass is configured to read subclasses and its subclasses define additional tables, build multiple queries to obtain all the rows for all the subclasses. For best performance in this situation, create a view against which to execute the query using the setReadAllSubclassesViewName()
method. The view must internally perform an outer join or union on all the subclass tables and return a single result set with all the data.
Occasionally, using the default OracleAS TopLink inheritance mechanism is not possible. For these cases, you can customize the inheritance mechanism. Instead of using a class indicator field and mapping, use a class extraction method. This method takes the object's row and returns the class to be used for that row. The setClassExtractionMethodName()
method is used to accomplish this.
Queries for inherited classes usually also require filtering of the table rows. By default, OracleAS TopLink generates this from the class indicator information. If you provide the class extraction method, specify the filtering expressions. These can be set for concrete classes through setOnlyInstancesExpression()
and for branch classes through setWithAllSubclassesExpression()
.
Figure 3-10 illustrates an example of an inheritance hierarchy. The Vehicle-Bicycle
branch demonstrates how you can store all subclass information in one table. The FueledVehicle-Car
branch demonstrates how you can store subclass information in two tables.
The Car
and Bicycle
classes are leaf classes. Queries performed on them return instances of Car
and Bicycle
respectively.
FueledVehicle
is a branch class. By default, branch classes are configured to read instances and subclass instances. Queries for FueledVehicle
return instances of FueledVehicle
and instances of Car
.
NonFueledVehicle
is a branch class and is configured to read subclasses. Because it does not have a class indicator defined in the root, it cannot be written to the database. Queries performed on NonFueledVehicle
return instances of its subclasses.
Vehicle
is a root class, which is configured to read instances of itself and instances of its subclass by default. Queries performed on the Vehicle
class return instances of any of the concrete classes in the hierarchy.
// Vehicle is a root class. Because it is the root class, it must add the class indicators for its subclasses. public static Descriptor descriptor() { Descriptor descriptor = new Descriptor(); descriptor.setJavaClass(Vehicle.class); descriptor.setTableName("VEHICLE"); descriptor.setPrimaryKeyFieldName("ID"); // Class indicators must be supplied for each of the subclasses in the hierarchy that can have instances. InheritancePolicy policy = descriptor.getInheritancePolicy(); policy.setClassIndicatorFieldName("TYPE"); policy.addClassIndicator(FueledVehicle.class, "Fueled"); policy.addClassIndicator(Car.class, "Car"); policy.addClassIndicator(Bicycle.class, "Bicycle"); descriptor.addDirectMapping("id", "ID"); descriptor.addDirectMapping("passengerCapacity", "CAP"); return descriptor; } // FueledVehicle descriptor; it is a branch class and a subclass of Vehicle. Queries made on this class will return instances of itself and instances of its subclasses. public static Descriptor descriptor() { Descriptor descriptor = new Descriptor(); descriptor.setJavaClass(FueledVehicle.class); descriptor.addTableName("FUEL_VEH"); descriptor.getInheritancePolicy().setParentClass(Vehicle.class); descriptor.addDirectMapping("fuelCapacity", "FUEL_CAP"); descriptor.addDirectMapping("fuelType", "FUEL_TYPE"); return descriptor; }// Car descriptor; it is a leaf class and subclass of FueledVehicle.
public static Descriptor descriptor() { Descriptor descriptor = new Descriptor(); descriptor.setJavaClass(Car.class); descriptor.addTableName("CAR"); descriptor.getInheritancePolicy().setParentClass(FueledVehicle.class);// Next define the attribute mappings.
descriptor.addDirectMapping("description", "DESCRIP"); descriptor.addDirectMapping("fuelType", "FUEL_VEH.FUEL_TYPE"); return descriptor; }// NonFueledVehicle descriptor; it is a branch class and a subclass of Vehicle. Queries made on this class will return instances of its subclasses.
public static Descriptor descriptor() { Descriptor descriptor = new Descriptor(); descriptor.setJavaClass(NonFueledVehicle.class); descriptor.getInheritancePolicy().setParentClass(Vehicle.class); return descriptor; } // Bicycle descriptor; it is a leaf class and subclass of NonFueledVehicle. public static Descriptor descriptor() { Descriptor descriptor = new Descriptor(); descriptor.setJavaClass(Bicycle.class); descriptor.getInheritancePolicy().setParentClass(NonFueledVehicle.class); descriptor.addDirectMapping("description", "BICY_DES"); return descriptor; }// FueledVehicle class; If a class extraction method is used, the following needs to be added to specify that only the branch class itself needs to be returned. This example is just specifying the class indicator field, which can also be specified in the OracleAS TopLink Mapping Workbench in the Descriptor Advanced Properties dialog.
public void addToDescriptor(Descriptor descriptor) { ExpressionBuilder builder = new ExpressionBuilder(); descriptor.getInheritancePolicy().setOnlyInstancesExpression(builder.getField ("VEHICLE.TYPE").equal("F")); }
Table 3-7 summarizes the most common public methods for InheritancePolicy
. For more information about the available methods for InheritancePolicy
, see the Oracle Application Server TopLink API Reference.
Element | Default | Method Name |
---|---|---|
Class indicators |
use indicator mapping |
setClassIndicatorFieldName |
Parent classes |
not applicable |
|
To create indirection objects in code, the application must replace the relationship reference with a ValueHolderInterface
. It must also call the useIndirection()
method of the mapping if the mapping does not use indirection by default. Likewise, call the dontUseIndirection()
method to disable indirection. ValueHolderInterface
is defined in the oracle.toplink.indirection
.
// Define the One-to-One mapping. Note that One-to-One mappings have indirection enabled by default, so the "dontUseIndirection()" method must be called if indirection is not used. OneToOneMapping oneToOneMapping = new OneToOneMapping(); oneToOneMapping.setAttributeName("address"); oneToOneMapping.setReferenceClass(Address.class); oneToOneMapping.setForeignKeyFieldName("ADDRESS_ID"); oneToOneMapping.dontUseIndirection(); oneToOneMapping.setSetMethodName("setAddress"); oneToOneMapping.setGetMethodName("getAddress"); descriptor.addMapping(oneToOneMapping);
The following code illustrates a mapping using indirection.
// Define the One-to-One mapping. One-to-One mappings have indirection enabled by default, so the "useIndirection()" method is unnecessary if indirection is used. OneToOneMapping oneToOneMapping = new OneToOneMapping(); oneToOneMapping.setAttributeName("address"); oneToOneMapping.setReferenceClass(Address.class); oneToOneMapping.setForeignKeyFieldName("ADDRESS_ID"); oneToOneMapping.setSetMethodName("setAddressHolder"); oneToOneMapping.setGetMethodName("getAddressHolder"); descriptor.addMapping(oneToOneMapping);
Descriptors can own their parent interfaces. They can set multiple interfaces if they have implemented multiple interfaces. The query keys are defined in a normal way except that they must define the abstract query key from the interface descriptor in their descriptors. An abstract query key on the interface descriptor enables it to write expression queries on the interface.
ExpressionBuilder contact = new ExpressionBuilder(); session.readObject(Contact.class, contact.get("id").equal(2));
The Descriptor
class provides three methods that determine how an object is cloned:
useInstantiationCopyPolicy()
: the default method; OracleAS TopLink creates a new instance of the object using the technique indicated by the descriptor's instantiation policy. The default behavior is to use the default constructor. The new instance is then populated by using the descriptor's mappings to copy attributes from the original object.
useCloneCopyPolicy()
: OracleAS TopLink calls the clone()
method of the object; ensure that the clone method is written correctly and returns a logical shallow clone of the object
useCloneCopyPolicy(String)
: this method is called by passing in a string that contains the name of a method that clones the object; ensure that the method specified returns a logical shallow clone of the object
To define a multiple table descriptor, call the addTableName()
method for each table the descriptor maps to. If the descriptor inherits its primary table and is defining only a single additional one, then the descriptor is mapped normally to this table.
Normally, the primary key is defined only for the primary table of the descriptor. The primary table is the first table specified through addTableName()
. The primary key is not defined for the additional tables and is required to be the same as in the primary table. If the additional table's key is different, refer to the next example.
By default, all the fields in a mapping are presumed to be part of the primary table. If a mapping's field is for one of the additional tables, it must be fully qualified with the field's table name.
//Define a new descriptor that uses three tables. Descriptor descriptor = new Descriptor(); descriptor.setJavaClass(Employee.class); descriptor.addTableName("PERSONNEL"); // Primary table descriptor.addTableName("EMPLOYMENT"); descriptor.addTableName("USERS"); descriptor.addPrimaryKeyFieldName("PER_NUMBER"); descriptor.addPrimaryKeyFieldName("DEP_NUMBER"); descriptor.addDirectMapping("id", "PER_NUMBER"); descriptor.addDirectMapping("firstName", "F_NAME"); descriptor.addDirectMapping("lastName", "L_NAME"); OneToOneMapping department = new OneToOneMapping(); department.setAttributeName("department"); department.setReferenceClass(Department.class); department.setForeignKeyFieldName("DEP_NUMBER"); descriptor.addMapping(department); // Mapping the primary key fields in the additional tables is not required descriptor.addDirectMapping("salary", "EMPLOYMENT.SALARY"); AggregateObjectMapping period = new AggregateObjectMapping(); period.setAttributeName(period); period.setReferenceClass(EmployementPeriod.class); period.addFieldNameTranslation("EMPLOYMENT.S_DATE", "S_DATE"); period.addFieldNameTranslation("EMPLOYMENT.E_DATE", "E_DATE"); descriptor.addMapping(period); descriptor.addDirectMapping("userName", "USERS.NAME"); descriptor.addDirectMapping("password", "USERS.PASSWORD");
If the additional table's primary key is named differently, then call the descriptor method addMultipleTablePrimaryKeyName()
, which provides:
//Define a new descriptor that uses three tables.
Descriptor descriptor = new Descriptor(); descriptor.setJavaClass(Employee.class); descriptor.addTableName("PERSONNEL");// Primary table
descriptor.addTableName("EMPLOYMENT"); descriptor.addTableName("USERS"); descriptor.addPrimaryKeyFieldName("PER_NUMBER"); descriptor.addPrimaryKeyFieldName("DEP_NUMBER"); descriptor.addMultipleTablePrimaryKeyName("PERSONEL.PER_NUMBER", "USERS.PERSONEL_NO"); descriptor.addMultipleTablePrimaryKeyName("PERSONEL.DEP_NUMBER", "USERS.DEPARTMENT_NO");// Assumed EMPLOYMENT uses same primary key
descriptor.addDirectMapping(id, PER_NUMBER); OneToOneMapping department = new OneToOneMapping(); department.setAttributeName("department"); department.setReferenceClass(Department.class); department.setForeignKeyFieldName("DEP_NUMBER"); descriptor.addMapping(department); // Primary key does not have to be mapped for additional tables. ...
For OracleAS TopLink to support read, insert, update, and delete operations on an object mapped to multiple tables:
The API is addMultipleTableForeignKeyFieldName()
. This method builds the join expression and adjusts the table insertion order to respect the foreign key constraints.
Example 3-54 illustrates the setup of a descriptor for an object mapped to multiple tables in which the tables are related by a foreign key relationship from the primary table to the secondary table. The addMultipleTableForeignKeyFieldName()
method is used to specify the direction of the foreign key relationship.
If the foreign key is in the secondary table and refers to the primary table, then the order of the arguments to addMultipleTableForeignKeyFieldName()
is reversed.
Descriptor descriptor = new Descriptor(); descriptor.setJavaClass(Employee.class); Vector vector = new Vector(); vector.addElement("EMPLOYEE"); vector.addElement(ADDRESS"); descriptor.setTableNames(vector); descriptor.addPrimaryKeyFieldName("EMPLOYEE.EMP_ID");// Map the foreign key field of the employee table and the primary key of the address table.
descriptor.addDirectMapping("addressID", "EMPLOYEE.ADDR_ID");// Setup the join from the address table to the country employee table to the address table by specifying the FK info to the descriptor. Set the foreign key info from the address table to the country table.
descriptor.addMultipleTableForeignKeyFieldName("EMPLOYEE.ADDR_ID", "ADDRESS.ADDR_ID");
Occasionally the join condition can be nonstandard. In this case, the descriptor's query manager can be used to provide a custom multiple table join expression. The getQueryManager()
method is called on the descriptor to obtain its query manager, and the setMultipleTableJoinExpression()
method is used to customize the join expression.
Simply specifying the join expression allows OracleAS TopLink to perform read operations for the object. Insert operations can also be supported if the table insertion order is specified and the primary key of the additional tables is mapped manually.
The insertion order is required to conform to foreign key constraints when inserting to the multiple tables. Specify the insert order using the descriptor method setMultipleTableInsertOrder
().
Example 3-55 illustrates the use of the setMultipleTableJoinExpression()
and setMultipleTableInsertOrder()
methods. In addition, it illustrates the use of a custom join expression without specifying the table insert order.
Using this method allows only read and insert operations to be performed on Employee objects. Note that the primary key of the secondary table, and the foreign key of the primary table must be mapped and maintained by the application for insert operations to work.
Descriptor descriptor = new Descriptor(); descriptor.setJavaClass(Employee.class); Vector vector = new Vector(); vector.addElement("EMPLOYEE"); vector.addElement(ADDRESS"); descriptor.setTableNames(vector);// Specify the primary key information for each table.
descriptor.addPrimaryKeyFieldName("EMPLOYEE.EMP_ID");// Map the foreign key field of the employee table and the primary key of the address table.
descriptor.addDirectMapping("employee_addressID", "EMPLOYEE.ADDR_ID"); descriptor.addDirectMapping("address_addressID", "ADDRESS.ADDR_ID");// Setup the join from the employee table to the address table using a custom join expression and specifying the table insert order.
ExpressionBuilder builder = new ExpressionBuilder(); descriptor.getQueryManager().setMultipleTableJoinExpression(builder.getField ("EMPLOYEE.ADDR_ID").equal(builder.getField("ADDRESS.ADDR_ID"))); Vector tables = new Vector(2); tables.addElement(new DatabaseTable("ADDRESS")); tables.addElement(new DatabaseTable("EMPLOYEE")); descriptor.setMultipleTableInsertOrder(tables); ...
In this example, only read operations are supported.
//Define a new descriptor that uses three tables. Descriptor descriptor = new Descriptor(); descriptor.setJavaClass(Employee.class); descriptor.addTableName("PERSONNEL");// Primary table
descriptor.addTableName("EMPLOYMENT"); descriptor.addPrimaryKeyFieldName("PER_NO"); descriptor.addPrimaryKeyFieldName("DEP_NO"); ExpressionBuilder builder = new ExpressionBuilder(); descriptor.getQueryManager().setMultipleTableJoinExpression((builder.getField ("PERSONEL.EMP_NO").equal(builder.getField("EMPLOYMENT.EMP_NO"))); descriptor.addDirectMapping("personelNumber", "PER_NO"); OneToOneMapping department = new OneToOneMapping(); department.setAttributeName("department"); department.setReferenceClass(Department.class); department.setForeignKeyFieldName("DEP_NO"); descriptor.addMapping(department);// The primary key field on the EMPLOYMENT does not have to be mapped.
...
To implement sequence numbers using Java code, send the setSequenceNumberFieldName
( ) message to the descriptor to register the name of the database field that holds the sequence number. The setSequenceNumberName
( ) method also holds the name of the sequence. This name can be one of the entries in the SEQ_NAME
column or the name of the sequence object (if you are using Oracle native sequencing).
Use the API to set optimistic locking completely in code. All the API is on the descriptor:
useVersionLocking(String)
: sets this descriptor to use version locking and increments the value in the specified field name for update or delete
useChangedFieldsLocking()
: tells this descriptor to compare only modified fields for an update or delete
useTimestampLocking(String)
: sets this descriptor to use timestamp locking and writes the current server time in the field every update or delete
useAllFieldsLocking()
: tells this descriptor to compare every field for an update or delete
useSelectedFieldsLocking(Vector)
: tells this descriptor to compare the field names specified in this vector of Strings for an update or delete
// Set the field that control optimistic locking. No mappings are set for fields
which are version fields for optimistic locking.
descriptor.useVersionLocking("VERSION");
The code in Example 3-57 stores the optimistic locking value in the identity map. If the value must be stored in a nonread only mapping, then the code can be:
descriptor.useVersionLocking("VERSION", false);
The false
indicates that the lock value is not stored in the cache, but is stored in the object.
Use the API to set optimistic locking in code. All the API is on the descriptor:
useVersionLocking(String)
: sets this descriptor to use version locking and increments the value in the specified field name for every update or delete
useTimestampLocking(String)
: sets this descriptor to use timestamp locking and writes the current server time in the specified field name for every update or delete
useChangedFieldsLocking()
: tells this descriptor to compare only modified fields for an update or delete
useAllFieldsLocking()
: tells this descriptor to compare every field for an update or delete
useSelectedFieldsLocking(Vector)
: tells this descriptor to compare the field names specified in this vector of Strings for an update or delete
Example 3-58 illustrates how to implement optimistic locking using the VERSION
field of EMPLOYEE
table as the version number of the optimistic lock.
descriptor.useVersionLocking("VERSION");
The code in Example 3-58 stores the optimistic locking value in the identity map. If the value must be stored in a nonread only mapping, then the code appears as follows:
descriptor.useVersionLocking("VERSION", false);
The false
indicates that the lock value is not stored in the cache, but is stored in the object.
|
Copyright © 2000, 2003 Oracle Corporation. All Rights Reserved. |
|