Oracle9iAS TopLink Foundation Library Guide Release 2 (9.0.3) Part Number B10064-01 |
|
A descriptor is a TopLink object that describes how an object's attributes and relationships are to be represented in relational database table(s). A "TopLink descriptor" is not the same as an "EJB deployment descriptor", although it plays a similar role.
Most descriptors can be created in the Mapping Workbench, but you may also have reasons to specify them in native Java code. This chapter illustrates
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, you could use a convenience method, setPrimaryKeyFieldNames( )
, sending 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");
To implement an inheritance hierarchy completely in Java, you must 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 indicating 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 causes the full name of the class to be stored in the class indicator field.
setParentClass()
message to the descriptor for each subclass.
dontReadSubclassesOnQueries()
method.
setTableName()
and addTableName()
messages for the tables they inherit. Only the root class defines the primary key.
Queries for inherited superclasses can require multiple queries to obtain all of the rows for all of the subclasses. This is only required if the superclass is configured to read subclasses and its subclasses define additional tables. This situation can be optimized by providing TopLink with a view to execute the query against. This view can internally perform an outer join or union on all of the subclass tables and return a single result set with all of the data. This view can be set through the setReadAllSubclassesViewName()
method.
Using TopLink's default inheritance mechanism may not always be possible. In this case the inheritance mechanism can be further customized. Instead of using a class indicator field and mapping, a class extraction method may be used. This method takes the objects row and returns the class to be used for that row. The setClassExtractionMethodName()
method is used to accomplish this.
Normally queries for inherited classes also require filtering of the tables rows; by default, TopLink generates this from the class indicator information. However, if the class extraction method is given, the filtering expressions must be specified. These can be set for concrete classes through setOnlyInstancesExpression()
and for branch classes through setWithAllSubclassesExpression()
.
Figure 8-1 shows an example of an inheritance hierarchy. The Vehicle-Bicycle
branch demonstrates how all subclass information can be stored in one table. The FueledVehicle-Car
branch demonstrates how subclass information can be stored in two tables.
The Car
and Bicycle
classes are leaf classes, so queries done 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 also return 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 done 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 made 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 would need 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 Mapping Workbench in the Descriptor Advanced Properties dialog. public void addToDescriptor(Descriptor descriptor) { ExpressionBuilder builder = new ExpressionBuilder(); descriptor.getInheritancePolicy().setOnlyInstancesExpression(builder.getField("V EHICLE.TYPE").equal("F")); }// FueledVehicle class; If a class extraction method is used, the following would need to be added to specify that the branch class and its subclasses need to be returned. This example can also be specified in Mapping Workbench in the Descriptor Advanced Properties dialog.
public void addToDescriptor (Descriptor descriptor) { ExpressionBuilder builder = new ExpressionBuilder(); descriptor.getInheritancePolicy().withAllSubclassesExpression(builder.getField(" VEHICLE.TYPE").equal("F")); }
Table 8-1 summarizes the most common public methods for InheritancePolicy
:
For a complete description of all available methods for InheritancePolicy
, see the TopLink JavaDocs.
Element | Default | Method Names |
---|---|---|
Class indicators |
use indicator mapping |
setClassIndicatorFieldName |
Parent classes |
not applicable |
|
Descriptors can their own 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 can be used to determine how an object is cloned:
useInstantiationCopyPolicy()
-- the default method; 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()
-- TopLink calls the clone()
method of the object; you must 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; you must 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 only defining 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 assumed 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 tables 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 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.
The following example shows the setup of a descriptor for an object mapped to multiple tables where 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("employee_addressID", "EMPLOYEE.ADDR_ID"); descriptor.addDirectMapping("address_addressID", "ADDRESS.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 non-standard. In this case, the descriptors 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 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 so as not to violate foreign key constraints when inserting to the multiple tables. The insert order can be specified using the descriptor method setMultipleTableInsertOrder
().
The following example shows the use of the setMultipleTableJoinExpression()
and setMultipleTableInsertOrder()
methods. In addition, it shows 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("EM PLOYEE.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("P ERSONEL.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).
// Set the sequence number information.
descriptor.setSequenceNumberName("EMP_SEQ");
descriptor.setSequenceNumberFieldName("EMP_ID");
The Descriptor
class provides the following methods to specify how objects get instantiated.
useDefaultConstructorInstantiationPolicy() i
nstructs 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) i
nstructs 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 that is used can be public, private, protected, or default/package, and requires no arguments.
useMethodInstantiationPolicy(String) i
nstructs 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) i
nstructs 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. TopLink instantiates the factory by invoking 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) i
nstructs 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. TopLink instantiates the factory by invoking 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.
Use the API to set optimistic locking completely in code. All of 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 the example above, stores the optimistic locking value in the identity map. If the value should be stored in a non-read only mapping, then the code would be:
descriptor.useVersionLocking("VERSION", false);
The false
indicates that the lock value is not stored in the cache but is stored in the object.
To change the identity map type for a descriptor from the default to specify it explicitly as full identity map), useNoIdentityMap()
, useCacheIdentityMap()
, useWeakIdentityMap()
, useSoftCacheWeakIdentityMap()
, useHardCacheWeakIdentityMap()
or useFullIdentityMap()
message to the descriptor.
To change the size of the identity map from the default of 100, use the setIdentityMapSize()
message.
// Set any identity map parameters.
descriptor.useCacheIdentityMap();
descriptor.setIdentityMapSize(10);
Register query keys with a descriptor using the addQueryKey()
method of the Descriptor
class. Direct query keys can also be defined with the addDirectQueryKey()
method, specifying the name of the query key and the name of the table field. Abstract query keys are registered to interface descriptors through the addAbstractQueryKey()
method.
// Add a query key for the foreign key field using the direct method direct descriptor.addDirectQueryKey("managerId", "MANAGER_ID"); // The same query key could also be added through the add method DirectQueryKey directQueryKey = new DirectQueryKey(); directQueryKey.setName("managerId"); directQueryKey.setFieldName("MANAGER_ID"); descriptor.addQueryKey(directQueryKey); // Add a one-to-one query key for the large project that the employee is a leader of (this assumes only one project) OneToOneQueryKey lprojectQueryKey = new OneToOneQueryKey(); lprojectQueryKey.setName("managedLargeProject"); lprojectQueryKey.setReferenceClass(LargeProject.class); ExpressionBuilder builder = new ExpressionBuilder(); projectQueryKey.setJoinCriteria(builder.getField("PROJECT.LEADER_ ID").equal(builder.getParameter("EMPLOYEE.EMP_ID"))); descriptor.addQueryKey(lprojectQueryKey); // Add a one-to-many query key for the projects that the employee multiple projects) OneToManyQueryKey projectsQueryKey = new OneToManyQueryKey(); projectsQueryKey.setName("managedProjects"); projectsQueryKey.setReferenceClass(Project.class); ExpressionBuilder builder = new ExpressionBuilder(); projectsQueryKey.setJoinCriteria(builder.getField("PROJECT.LEADER_ ID").equal(builder.getParameter("EMPLOYEE.EMP_ID"))); descriptor.addQueryKey(projectsQueryKey); // Next define the mappings. ...
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);
To enable proxy indirection in Java code, use the following API for ObjectReferenceMapping
:
useProxyIndirection()
- indicates that TopLink should use proxy indirection for this mapping. When the source object is read from the database, a proxy for the target object is created and used in place of the "real" target object. When any method other than toString()
is called on the proxy, the "real" data will be read from the database.
// Define the 1:1 mapping, and specify that Proxy Indirection should be used OneToOneMapping addressMapping = new OneToOneMapping(); addressMapping.setAttributeName("address"); addressMapping.setReferenceClass(AddressImpl.class); addressMapping.setForeignKeyFieldName("ADDRESS_ID"); addressMapping.setSetMethodName("setAddress"); addressMapping.setGetMethodName("getAddress"); addressMapping.useProxyIndirection(); descriptor.addMapping(addressMapping); . . .
Use the ObjectRelationalDescriptor
class to define object-relational descriptors. This descriptor subclass contains the following additional properties:
The demo application provided in <INSTALL_DIR>\examples\core\examples \sessions\remote\rmi
illustrates an object-relational data model and descriptors.
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. ...
Attributes using indirection must conform to the ValueHolderInterface
. You can change your attribute types in the Class Editor without re-importing your Java classes. Ensure that you change the attribute types in your Java code as well. Attributes that are typed incorrectly will be marked as deficient.
In addition to changing the attribute's type, you may also need to change its accessor methods. If you use method access, TopLink requires accessors to the indirection object itself, so your get method returns an instance that conforms to ValueHolderInterface
and your set method accepts one argument that conforms to the same. If the instance variable returns a Vector instead of an object then the value holder should be defined in the constructor as follows:
addresses = new ValueHolder(new Vector());
In any case, the application uses the getAddress()
and setAddress()
methods to access the Address
object. When indirection is used, TopLink uses the getAddressHolder()
and setAddressHolder()
methods when saving and retrieving instances to and from the database.
The class definition is modified so that the address attribute of Employee
is a ValueHolderInterface
instead of an Address
and appropriate get and set methods are supplied.
// 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
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);
}
The Descriptor
class provides methods that are 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
In this example, we check to see if there is a lock conflict whenever an instance of Employee
is built from information in the database:
//In the employee class, declare the event method which will be invoked when the event occurs. public void postBuild(DescriptorEvent event) {// Uses objects row to integrate with some application level locking service. if ((event.getRow().get("LOCKED")).equals("T")) {LockManager.checkLockConflict(this);}}
TopLink only supports registering events to call methods on the domain object. If you want an object other than the domain object to handle these events, you must register it as a listener with the descriptor's event manager. If you want a LockManager to receive events for all Employees
, you could 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 is shown in the following example.
public static void addToDescriptor(Descriptor descriptor) { descriptor.getEventManager().addListener
(LockManager.activeManager()); }
Table 8-2 summarizes the most common public methods for DescriptorEventManager:
For a complete description of all available methods for DescriptorEventManager
, see the TopLink JavaDocs.
|
Copyright © 2002 Oracle Corporation. All Rights Reserved. |
|