Oracle9iAS TopLink CMP for Users of BEA WebLogic Guide Release 2 (9.0.3) Part Number B10065-01 |
|
This chapter discusses some of the relevant run-time issues surrounding writing an application that uses TopLink Container-Managed Persistence in the BEA WebLogic Server container. Other facets of the run-time execution that relate to EJB's and the BEA WebLogic Server are beyond the scope of this document and should be reviewed in the EJB specification and/or the BEA WebLogic Server documentation.
Entity beans that use container-managed persistence may participate in transactions that are either client-demarcated or container-demarcated.
Clients of entity beans may directly set up transaction boundaries using the javax.transaction.UserTransaction
interface. Invocations on entity beans are automatically wrapped in transactions that are initiated by the container based upon the transaction attributes supplied in the EJB deployment descriptor.
For more information on how to use transactions with EJBs, consult the EJB specification and the BEA WebLogic Server documentation. The following sections describe briefly how TopLink participates in EJB transactions.
Within the BEA WebLogic Server, TopLink provides a persistence layer for entity beans. While the BEA WebLogic Server controls all aspects of transaction management, the TopLink layer is synchronized with the BEA WebLogic transaction service so that updates to the database are carried out at the appropriate times. BEA WebLogic, through its JTS JDBC Driver, determines the majority of transactional behavior
In general, TopLink does not issue updates to the underlying data store until the transaction that the enterprise beans are active in begins its two-stage commit process. This allows for:
All modifications to persistent beans and objects should be carried out in the context of a transaction. The transaction may either be client-controlled or container-controlled.
The TopLink container does not support modifying beans through their remote interface when no transaction is active. In this case, TopLink does not write out any changes to the data. Modifying entity beans without a transaction leads to an inconsistent state, potentially corrupting the values in the TopLink cache. Transactional attributes MUST be properly specified in the bean deployment descriptors, to ensure that data is not corrupted.
Although it is not valid to modify entity beans through their remote interface without a transaction, in the current release it is permitted to invoke methods on EJB homes that change the state in the underlying database. Invocation of removes and creates that are invoked against homes in the absence of a transaction are permitted.
When one-to-one or many-to-many mappings are bi-directional, the back-pointers must be correctly maintained as the relationships change. When the relationship is between two entity beans (in EJB 2.0), 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 2.0 specification, either
If back-pointers are set 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, an EmployeeBean
might have a number of dependent phoneNumbers
. When a phoneNumber
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, 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 entity beans be modeled such that all dependent objects are regular Java objects and not entity beans. If a dependent or privately owned object is to be exposed to the client application it must be serializable (it must implement the java.io.Serializable interface
) so that it may be sent over to the client and back to the server.
Recall that entity beans are remote objects. This results in a "pass-by-reference" situation when entity beans are referenced remotely. When an entity bean is returned to the client, a remote reference to the bean is returned.
Regular Java objects are not remote objects like entity beans are. Instead of a "pass-by-reference" situation, when regular Java objects are referenced remotely they are "passed-by-value" and serialized (copied) from the remote machine that they were originally on.
One of the side-effects of serializing regular Java objects from server to client and vice-versa is a loss of object identity, due to the copying semantics inherent in serialization. When a dependent object is serialized from the server to the client and then back, two objects with the same primary key but different object identity 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:
Following the first option, you would use the class oracle.toplink.ejb.WebLogic.SessionAccessor
to perform merges for you within your "set" methods on your bean class that take regular Java objects as their arguments.
There are two static methods defined on SessionAccessor that allow you to do the register/merge operation. One is called registerOrMergeObject()
and the other is called registerOrMergeAttribute()
.
The registerOrMergeObject() method takes two arguments: the object to merge and the EntityContext for the bean. For example:
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); }
The registerOrMergeAttribute()
call can be used "as is" for collection mappings: you pass in the whole 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 can be used, but the collection must be iterated through, invoking the registerOrMergeObject()
for each element in the collection. A new collection, set in the entity bean, must be created 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, the phones must be iterated through 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); }
As noted above, the issue that arises with serializing is that multiple copies of the same object (with different object identity) can exist within the server cache. These objects must be "merged", either using the SessionAccessor
methods described above, or manually.
There may be several ways to merge the objects. For example, you could use a set()
method as follows:
public void setAddress(Address address) { if(this.address == null){ this.address = address; } else{ this.address.merge(address); } }
Merging must also be done when objects are added to a collection on the entity bean. If it is certain that objects are never "re-added" to a collection, then merging is not necessary.
Merging a whole collection requires a little more work. For each object in the new collection, it must be determined if a copy of the object already exists in the collection. If it does, it must be merged. If not, the new object must simply be added to the collection.
Collections typically 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. In this case, the isIdentical()
method should be used instead. Consequently, you cannot expect the standard collection methods such as remove()
or contains()
to work properly when applied to a collection of EJBObjects
.
Several options are available when dealing with collections of EJBObjects
. One option is to create a helper class to assist with collection-type operations. An example of such a helper is provided in the distribution named EJBCollectionHelper
:
public void removeOwner(Employee previousOwner){ EJBCollectionHelper.remove(previousOwner, getOwners()); }
The implementation of remove() and indexOf() in EJBCollectionHelper is shown in the next example:
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;
}
}
If JDK 1.2 is used, a special Collection class could be created that uses isIdentical()
instead of equals()
for all its comparison operations. For isIdentical()
to function correctly, the equals()
method must be properly defined for the primary key class.
|
Copyright © 2002 Oracle Corporation. All Rights Reserved. |
|