- Overview
- The javax.ejb Package for Entity Beans
- Entity Bean Types
- Remote Versus Local Interfaces
- BMP Entity Bean Lifecycle
- Specifying a BMP Entity Bean
- Implementing a BMP Entity Bean
- Configuring and Deploying a BMP Entity Bean
- Client's View
- Session Beans Revisited
- Patterns and Idioms
- Gotchas
- Summary
- Q&A
- Exercises
Specifying a BMP Entity Bean
Following the pattern of Session beans, specifying an Entity bean involves defining the local-home and the local interface:
The local-home interface extends javax.ejb.EJBLocalHome.
The local interface extends javax.ejb.EJBLocalObject.
A discussion on each of these interfaces follows.
Local-Home Interface
Listing 6.1 shows the complete JobLocalHome interface as an example.
Listing 6.1JobLocalHome Interface
1: package data; 2: 3: import java.rmi.*; 4: import java.util.*; 5: import javax.ejb.*; 6: 7: public interface JobLocalHome extends EJBLocalHome 8: { 9: JobLocal create (String ref, String customer) throws CreateException; 10: JobLocal findByPrimaryKey(JobPK key) throws FinderException; 11: Collection findByCustomer(String customer) throws FinderException; 12: Collection findByLocation(String location) throws FinderException; 13: void deleteByCustomer(String customer); 14: }
Each of these methods has a corresponding method in the bean class itself. Taking the JobBean code as an example:
The create(String ref, String customer) method in JobBean corresponds to ejbCreate(String ref, String customer) in the JobLocalHome interface.
The ejbFindByPrimaryKey(String name) method in JobBean corresponds to findByPrimaryKey(String name) in the JobLocalHome interface.
The ejbFindByCustomer() method in JobBean corresponds to findbyCustomer() in the JobLocalHome interface.
The ejbHomeDeleteByCustomer(String customer) in JobBean corresponds to deleteByCustomer(String customer) in the JobLocalHome interface.
Note
Note that for home methods, the convention is to append ejbHome, not just ejb, to the bean's method name.
This seems straight-forward enough, but note that the return types of for the bean's ejbCreate() and ejbFindXxx() methods are different from the return types of the methods in the local-home interface. Specifically, while the bean returns (to the EJB container) either primary key objects or Collections of primary key objects, the local-home interface methods return (to the client) either local proxies (that is, instances of objects that implement the JobLocal interface, for the example) or Collections of such.
Create and Remove Methods
The list of exceptions thrown by the local-home methods and the bean's corresponding methods should match in each case. For the createXXX() method, the list should be the union of the exceptions thrown by both ejbCreateXXX() and ejbPostCreateXXX(). If a home and a remote interface are being provided for the Entity bean, the java.rmi.RemoteException must be declared for the methods of the home interface.
As well as the create() method, the local-home interface inherits a remove(Object o) method from javax.ejb.EJBLocalHome. This corresponds to the ejbRemove() lifecycle method of the bean itself.
Finder Methods
Finder methods in the bean return either a single primary key (if a single bean matches the underlying query) or a java.util.Collection of primary keys (if there is more than one matching bean). The ejbFindByPrimaryKey() method is always required to be one of the bean's methods, al though it is not part of the EntityBean interface. This is because the argument type and return type will depend upon the bean.
NOTE
It is also possible for finder methods to return java.util.Enumerations. This dates from EJB 1.0 before the Java Collections API was introduced in J2SE 1.2 and should not be used.
Obviously, to specify the findByPrimaryKey() method, the primary key of the Entity bean must have been identified. As was noted earlier today, if persisting to an RDBMS, identifying the primary key is probably quite easy, because the primary key will correspond to the columns of the primary key of the underlying RDBMS table. A custom-developed primary key class is needed when two or more fields identify the bean; otherwise, the type of the single field of the bean that represents the key is used.
NOTE
If a single field of the bean is used as the primary key, that field must not be a primitive type (such as an int or long). Primary key fields must be actual classes, such as a java.lang.String. Furthermore, the EJB specification does not allow primary keys to change once assigned, so it is best if the class chosen is immutable.
Custom Primary Key Classes
As noted earlier, the primary key can be either a field of the bean (in which case, the primary key class is just the class of that field) or can be a custom-developed class. The latter is required if more than one field is needed to identify the bean (and can be used even for single field keys).
For the JobBean, the primary key is a combination of the customer and the job reference (the customer and ref fields, respectively). Because the primary key is composite, a custom primary key class is needed; this is the JobPK class.
Custom primary key classes are required to follow a number of rules. Specifically
The class must implement java.io.Serializable or java.io.Externalizable.
The values of the class must all be primitives or be references to objects that, in turn, are serializable.
The equals() method and the hashCode() methods must be implemented.
There must be a no-arg constructor (there can also be other constructors that take arguments, but they would only be provided for convenience).
In other words, the class must be what is sometimes referred to as a value type.
NOTE
At least conceptually, value types are immutable (there should be no setter methods; they cannot be changed). The requirement for a no-arg constructor does prevent this from actually being the case.
Listing 6.2 shows the JobPK primary key class.
Listing 6.2JobPK Class Identifies a Job
1: package data; 2: 3: import java.io.*; 4: import javax.ejb.*; 5: 6: public class JobPK implements Serializable { 7: public String ref; 8: public String customer; 9: 10: public JobPK() { 11: } 12: public JobPK(String ref, String customer) { 13: this.ref = ref; 14: this.customer = customer; 15: } 16: 17: public String getRef() { 18: return ref; 19: } 20: public String getCustomer() { 21: return customer; 22: } 23: 24: public boolean equals(Object obj) { 25: if (obj instanceof JobPK) { 26: JobPK pk = (JobPK)obj; 27: return (pk.ref.equals(ref) && pk.customer.equals(customer)); 28: } 29: return false; 30: } 31: public int hashCode() { 32: return (ref.hashCode() ^ customer.hashCode()); 33: } 34: public String toString() { 35: return "JobPK: ref=\"" + ref + "\", customer=\"" + customer + "\""; 36: } 37: }
Note that the ref and customer fields have public visibility. This is a requirement of the EJB specification. Each field must correspondin name and typeto one of the fields of the bean itself. This might seem like a strange requirement, but is needed by the EJB container to manage CMP beans.
To implement the equals() method, test that all fields of the object have the same value as the fields in the provided object. For primitive values, the regular == operator should be used, but for object references, the equals() method must be called.
To implement the hashCode() method, generate an int value that is based entirely and deterministically on the value of the fields, such that
if A.equals(B) == true then A.hashCode() == B.hashCode().
There are a couple of ways to accomplish this. A quick way to do this is to convert all the values of the primary key class' fields to Strings, concatenate them to a single String, and then invoke the hashCode() on this resultant string. Alternatively, the hashCode() values for all of the fields could be or'd together using the ^ operator. At runtime, this will execute more quickly than the concatenation approach, but it does mean that the distribution of hashcodes may be less good for primary keys with many fields. This is the approach used in Listing 6.2.
NOTE
Creating these primary key classes can be somewhat tedious. But remember that if there is a single (non-primitive) field in the bean that identifies that bean, this can be used instead.
Failing that, a single primary key class can be used for multiple beans. For example, you could create a IntPK class that just encapsulates an int primitive value.
Home Methods
In addition to finder, create, and remove methods, it is also possible to define home methods within the local-home interface. These are arbitrary methods that are expected to perform some business-type functionality related to the set of beans. In other words, they are an EJB equivalent of Java class methods (defined with the static keyword).
Some common uses for home methods include defining a batch operation to be performed on all bean instances (such as decreasing the price of all catalogue items for a sale), or various utility methods, such as formatting a bean's state for a toString() method.
NOTE
One question that sometimes arises is whether all database updates should be performed through Entity bean methods. One example given for a home method of a bean would be to decrease the price of all catalogue items for a sale. Iterating over perhaps 10,000 catalogue items and invoking setPrice( getPrice() * 0.9 ) is clearly going to cause massive amounts of SQL hitting the back-end RDBMS (or equivalent persistent data store).
In J2SE programs, a simple update, such as
UPDATE catalogue set price = price * 0.9
is clearly the way to go. The ejbLoad() lifecycle method will ensure that any catalog item Entity bean will re-sync its state with the RDBMS.
Local Interface
Just as for Session beans with their remote interfaces, the local interface defines the capabilities of the Entity bean. Because first and foremost an Entity bean represents data, it is entirely to be expected that many of the methods exposed through the local interface will be simple getter and setter methods. Listing 6.3 shows the local interface for the Job bean.
Listing 6.3JobLocal Interface
1: package data; 2: 3: import java.rmi.*; 4: import javax.ejb.*; 5: 6: public interface JobLocal extends EJBLocalObject 7: { 8: String getRef(); 9: String getCustomer(); 10: CustomerLocal getCustomerObj(); // derived 11: 12: void setDescription(String description); 13: String getDescription(); 14: 15: void setLocation(LocationLocal location); 16: LocationLocal getLocation(); 17: 18: Collection getSkills(); 19: void setSkills(Collection skills); 20: }
Note that the setLocation() method accepts a LocationLocal reference rather than, say, a String containing the name of a location. In other words, the Job bean is defining its relationships to other beans, in this case the Location bean directly, effectively enforcing referential integrity. The client of the Job Entity bean is thus required to supply a valid location or none at all.
This is not to say that Entity beans cannot provide further processing. An example often quoted might be for a SavingsAccountBean. This might provide withdraw() and deposit() methods. The withdraw() method might well ensure that the balance can never go below zero. The bean might also provide an applyInterest() method, but it almost certainly would not provide a setBalance() method (if only!).
NOTE
Actually, such an SavingsAccountBean might well provide a setBalance() method, but would restrict access to administrators. You will learn more about security on Day 15, "Security."
Each of these methods has a corresponding method in the bean itself, and the exceptions list matches exactly. The implementation is shown in the "Implementing the Local-Interface Methods" section later today.