OracleAS TopLink 10g (9.0.4)

Date: April 2004

How-To: Use a custom indirection class in your persistent model


  • Implement a custom indirect container to be used in the model
  • Use a session event listener to configure the mapping's custom indirection policy
  • Use the Mapping Workbench to map a model using custom indirection

Software Requirements

  • OracleAS TopLink 9.0.3.0.0 or above

Introduction

One of the benefits of the TopLink's persistence capabilities for Java Objects is the non-intrusiveness of the persistence layer into the mapped classes. One optional feature that is very beneficial to performance but requires intrusion into the source of your classes is indirection. Indirection is TopLink's 'just-in-time' loading approach for object relationships. TopLink offers basic, transparent, proxy and custom indirection. Although transparent indirection offers an excellent solution for collection mappings (1:M, M:M, and other collection mappings) the options for 1:1 mappings are limited to basic, using TopLink's value-holder interface, proxy indirection, or using a custom indirection approach. For those who want the performance benefits of indirection but do not want any intrusion into their mapped classes there are two options.

  1. Proxy Indirection: Involves the use of interfaces between the source of the indirect relationship and the target that implements the interface. This approach allows TopLink to dynamically generate the necessary runtime components to make the indirection work without having any TopLink code in the classes. The only downfall to this approach is the addition and maintenance of another interface.
  2. Custom Indirection: Involves the developer providing a class of their own, which wraps TopLink's ValueHolder, to be used in the persistent model.

This how-to discusses the custom indirection approach and walks you through the steps necessary to easily use this in your development.

The Custom Indirection Container

The first step is to write your own class that you wish to use in place of TopLink's value-holder. This class must implement the IndirectContainer (oracle.toplink.indirection) interface. This is basically a wrapper for the value holder.

public class SerValueHolder implements IndirectContainer {

private ValueHolderInterface valueHolder;

// Note: Public zero-arg constructor is a requirement
public SerValueHolder() {
}
public SerValueHolder(Object value) {
    this.valueHolder = new ValueHolder(value);
}
public Object getValue() {
    return getValueHolder().getValue();
}
public void setValue(Object value) {
    getValueHolder().setValue(value);
}
public ValueHolderInterface getValueHolder() {
    return this.valueHolder;
}
public boolean isInstantiated() {
    return getValueHolder().isInstantiated();
}
public void setValueHolder(ValueHolderInterface valueHolder) {
    this.valueHolder = valueHolder;
}

}

Model Development

As the model shows, the 1:1 relationship that would typically go from Person to Address now goes through the new custom indirection container. This allows the TopLink persistence framework to put its proxy object, a ValueHolder, in the relationship until the application asks for the Address. This will offer huge performance benefits in rich models with varying application use cases.

The Person class uses the custom indirect container, SerValueHolder, within its code but the public API of the class will not show this implementation detail to the application that will use the model. The declaration appears as:

public class Person {
private int id;
private String firstName;
private String lastName;
private SerValueHolder addressHolder = new SerValueHolder(null);

Now the code in the Person class that accesses the custom indirection type can look like:

public Address getAddress() {
    return (Address) this.addressHolder.getValue();
}

public void setAddress(Address address) {
    this.addressHolder.setValue(address);
}

As you can see the application API is simple and the cost of introducing this performance benefit is minimal.

Working with the Mapping Workbench

The Mapping Workbench does not currently have support for using a custom indirect container so the mapping is a little different then using basic, transparent, or proxy indirection. Inthis case, you use the Mapping Workbench as you normally would for any 1:1 mapping except the 'use indirection' is not selected.

This leaves the mapping somewhat incomplete in the generated project meta-data. The next step will show how a generic session event listener can be used to configure the indirection settings dynamically at system startup.

Note: In the case of collection mappings it is still possible to use the custom indirect container instead of transparent indirection. Unfortunately this will cause the Mapping Workbench to report the inconsistency between the indirection type and the attribute type. This warning can be safely ignored if the rest of the steps in the how-to are followed.

Configuration of Mappings

Now that the custom indirection container is written, used in the model, and the mappings are partially configured, a small piece of custom code is required to configure the indirection policy on the relevant mappings. This session-event-listener approach is completely model independent and will configure all mappings that use the custom indirect container class. Although this is simple to use, it is very important since without it the runtime will encounter validation exceptions.

The session-event-listener (SerVHSessionEventListener) implements the preLogin method so that all of the configuration will be completed prior to connection with the database and meta-data validation/initialization.

public void preLogin(SessionEvent event) {
    Iterator descI = event.getSession().getDescriptors().values().iterator();

while (descI.hasNext()) {

        Descriptor descriptor = (Descriptor) descI.next();
        for (Iterator mI = descriptor.getMappings().iterator(); mI.hasNext(); ) {
            DatabaseMapping mapping = (DatabaseMapping) mI.next();
            if (mapping.isForeignReferenceMapping()) {
                Field field = null;

                try {
                    field = descriptor.getJavaClass().getDeclaredField(mapping.getAttributeName());
                } catch (NoSuchFieldException nsfe) {}

                if (field != null && field.getType() == CONTAINER_CLASS) {
                ContainerIndirectionPolicy policy = new ContainerIndirectionPolicy();
                policy.setMapping(mapping);
                policy.setContainerClass(CONTAINER_CLASS);
                ((ForeignReferenceMapping) mapping).setIndirectionPolicy(policy);
            }
         }
       }
    }
}

This event-listener must of course be configured for use. The most common approach is through the use of the sessions.xml in conjunction with the SessionManager. If this is not the case then the above code can be used to configure the project if your application loads the project and creates the session manually. For the sessions.xml the event-listener simply needs to be added.

<event-listener-class>mypackage.SerVHSessionEventListener</event-listener-class>

Note: Use the Sessions Editor tool to complete the sessions.xml configuration or refer to the complete sessions.xml for placement details.

An Example

The complete example is provided here includes the classes and configuration files discussed as well as the Mapping Workbench project. The simple test case included can be run using its main. The only challlenge is that you will need to ensure the sessions.xml has the right JDBC credentials for a database you create. The Mapping Workbench can facilitate this.

Summary

In this document you should have:

  • Implemented a custom indirect container to be used in the model
  • Used a session event listener to configure the mapping's custom indirection policy
  • Used the Mapping Workbench to map a model using custom indirection
Left Curve
Popular Downloads
Right Curve
Untitled Document