Chapter 6. EJB 2.0 CMP: Basic Persistence
6.1 OverviewIn Chapter 4, we started developing some simple enterprise beans, skipping over a lot of the details about developing enterprise beans. In this chapter, we'll take a thorough look at the process of developing entity beans. Entity beans model business concepts that can be expressed as nouns. This is a rule of thumb rather than a requirement, but it helps in determining when a business concept is a candidate for implementation as an entity bean. In grammar school, you learned that nouns are words that describe a person, place, or thing. The concepts of "person" and "place" are fairly obvious: a person EJB might represent a customer or passenger, and a place EJB might represent a city or port-of-call. Similarly, entity beans often represent "things": real-world objects like ships, credit cards, and so on. An entity bean can even represent a fairly abstract thing, such as a reservation. Entity beans describe both the state and behavior of real-world objects and allow developers to encapsulate the data and business rules associated with specific concepts; a Customer EJB encapsulates the data and business rules associated with a customer, for example. This makes it possible for data associated with a concept to be manipulated consistently and safely. In Titan's cruise ship business, we can identify hundreds of business concepts that are nouns and, therefore, could conceivably be modeled by entity beans. We've already seen a simple Cabin EJB in Chapter 4, and we'll develop Customer and Address EJBs in this chapter. Titan could clearly make use of a Cruise EJB, a Reservation EJB, and many others. Each of these business concepts represents data that needs to be tracked and possibly manipulated. Entities represent data in the database, so changes to an entity bean result in changes to the database. There are many advantages to using entity beans instead of accessing the database directly. Utilizing entity beans to objectify data provides programmers with a simpler mechanism for accessing and changing data. It is much easier, for example, to change a customer's name by calling Customer.setName() than by executing an SQL command against the database. In addition, objectifying the data using entity beans provides for more software reuse. Once an entity bean has been defined, its definition can be used throughout Titan's system in a consistent manner. The concept of a customer, for example, is used in many areas of Titan's business, including booking, scheduling, and marketing. A Customer EJB provides Titan with one complete way of accessing customer information, and thus it ensures that access to the information is consistent and simple. Representing data as entity beans can make development easier and more cost-effective. When a new EJB is created, a new record must be inserted into the database and a bean instance must be associated with that data. As the EJB is used and its state changes, these changes must be synchronized with the data in the database: entries must be inserted, updated, and removed. The process of coordinating the data represented by a bean instance with the database is called persistence. There are two basic types of entity beans, distinguished by how they manage persistence: container-managed persistence beans and bean-managed persistence beans. For container-managed persistence beans, the container knows how a bean instance's persistence and relationship fields map to the database and automatically takes care of inserting, updating, and deleting the data associated with entities in the database. Entity beans using bean-managed persistence do all this work manually: the bean developer must write the code to manipulate the database. The EJB container tells the bean instance when it is safe to insert, update, and delete its data from the database, but it provides no other help. The bean instance does all the persistence work itself. Bean-managed persistence is covered in Chapter 10. In EJB 2.0, container-managed persistence has undergone a change so dramatic that it is not backward compatible with EJB 1.1. For this reason, EJB 2.0 vendors must support both EJB 2.0's and EJB 1.1's container-managed persistence models. The EJB 1.1 model is supported so that application developers can migrate their existing applications to the new EJB 2.0 platform as painlessly as possible. It's expected that all new entity beans and new applications will use the EJB 2.0 container-managed persistence and not the EJB 1.1 version. Although EJB 1.1 container-managed persistence is covered in this book, it should be avoided unless you maintain a legacy EJB 1.1 system. EJB 1.1 container-managed persistence is covered in Chapter 9. This chapter and the two that follow focus on developing entity beans that use EJB 2.0 container-managed persistence. In EJB 2.0, the data associated with an entity bean can be much more complex than was possible in EJB 1.1. In EJB 2.0, container-managed persistence entity beans can have relationships with other entity beans, which was not well supported in the older version—as a result, vendors sometimes offered proprietary solutions that were not portable. In addition, container-managed persistence entity beans, in EJB 2.0, can be finer in granularity so that they can easily model things such as the address, line item, or cabin. This chapter develops two very simple entity beans—the Customer and Address EJBs—which will be used to explain how Enterprise JavaBeans 2.0 container-managed persistence entity beans are defined and operate at runtime. The Customer EJB has relationships with several other entities, including the Address, Phone, CreditCard, Cruise, Ship, Cabin, and Reservation EJBs. In the next few chapters, you'll learn how to leverage EJB 2.0's powerful support for entity bean-to-bean relationships and will also come to understand their limitations. In addition, in Chapter 8 you will learn about the Enterprise JavaBeans Query Language (EJB QL), which is used to define how the find methods and the new select methods should behave at runtime. It is common to refer to EJB 2.0 container-managed persistence as CMP 2.0. In the chapters that follow, we will use this abbreviation to distinguish between CMP 2.0 and CMP 1.1 (EJB 1.1 container-managed persistence). 6.1.1 The Abstract Programming ModelIn CMP 2.0, entity beans have their state managed automatically by the container. The container takes care of enrolling the entity bean in transactions and persisting its state to the database. The enterprise bean developer describes the attributes and relationships of an entity bean using virtual persistence fields and relationship fields. They are called virtual fields because the bean developer does not declare these fields explicitly; instead, abstract accessor (get and set) methods are declared in the entity bean class. The implementations of these methods are generated at deployment time by the EJB vendor's container tools. It's important to remember that the terms relationship field and persistence field are referring to the abstract accessor methods and not to actual fields declared in the classes. This use of terminology is a convention in EJB 2.0 with which you should become comfortable. In Figure 6-1, the Customer EJB has six accessor methods. The first four read and update the last and first names of the customer. These are examples of persistence fields: simple direct attributes of the entity bean. The last two accessor methods obtain and set references to the Address EJB through its local interface, AddressLocal. This is an example of a relationship field called the homeAddress field. Figure 6-1. Class diagram of Customer and Address EJBs
6.1.2 Abstract Persistence SchemaThe CMP 2.0 entity bean classes are defined using abstract accessor methods that represent virtual persistence and relationship fields. As already mentioned, the actual fields themselves are not declared in the entity classes. Instead, the characteristics of these fields are described in detail in the XML deployment descriptor used by the entity bean. The abstract persistence schema is the set of XML elements in the deployment descriptor that describes the relationship fields and the persistence fields. Together with the abstract programming model (the abstract accessor methods) and some help from the deployer, the container tool will have enough information to map the entity and its relationships with other entity beans to the database. 6.1.3 Container Tools and PersistenceOne of the responsibilities of the vendor's container-deployment tool is generating concrete implementations of the abstract entity beans. The concrete classes generated by the container tool are called persistence classes. Instances of the persistence classes are responsible for working with the container to read and write data between the entity bean and the database at runtime. Once the persistence classes are generated, they can be deployed into the EJB container. The container informs the persistence instances (instances of persistence classes) when it's a good time to read and write data to the database. The persistence instances perform the reading and writing in a way that is optimized for the database being used. The persistence classes will include database access logic tailored to a particular database. For example, an EJB product might provide a container that can map an entity bean to a specific database, such as the Oracle relational database or the POET object database. This specificity allows the persistence classes to employ native database optimizations particular to a brand or kind of database, schema, and configuration. Persistence classes may also employ optimizations such as lazy loading and optimistic locking to further improve performance. The container tool generates all the database access logic at deployment time and embeds it in the persistence classes. This means that the bean developers do not have to write the database access logic themselves, which saves them a lot of work; it can also result in better-performing entity beans, because the implementations are optimized. As an entity bean developer, you will never have to deal with database access code when working with CMP 2.0 entities. In fact, you probably won't have access to the persistence classes that contain that logic, because they are generated by the container tool automatically. In most cases, the source code is not available to the bean developer. Figures 6-2 and 6-3 show different container tools, both of which are being used to map the Customer entity bean to a relational database. Figure 6-2. Borland AppServer deployment tool
6.2 The Customer EJBIn the following example we will develop a simple CMP 2.0 entity bean—the Customer EJB. The Customer EJB models the concept of a cruise customer or passenger, but its design and use are applicable across many commercial domains. As the chapter progresses, the Customer EJB will be expanded and its complexity will increase to illustrate concepts discussed in each section. This section serves only to introduce you to the entity bean and some basic concepts regarding its development, packaging, and deployment. To simplify things, we will skim over some concepts that are discussed in detail later in the chapter. Figure 6-3. J2EE 1.3 SDK deployment tool
6.2.1 The Customer TableAlthough CMP 2.0 is database independent, the examples throughout this book assume that you are using a relational database. This means that we will need a CUSTOMER table from which to get our customer data. The relational database table definition in SQL is as follows: CREATE TABLE CUSTOMER ( ID INT PRIMARY KEY NOT NULL, LAST_NAME CHAR(20), FIRST_NAME CHAR(20) ) 6.2.2 The CustomerBeanThe CustomerBean class is an abstract class that will be used by the container tool for generating a concrete implementation, the persistence entity class, which will run in the EJB container. The mechanism used by the container tool for generating a persistence entity class varies, but most vendors generate a subclass of the abstract class provided by the bean developer (see Figure 6-4). Figure 6-4. The container tool typically extends the bean class
The bean class must declare accessor (set and get) methods for each persistence and relationship field defined in the abstract persistence schema of the deployment descriptor. The container tool needs both the abstract accessor methods (defined in the entity bean class) and the XML elements of the deployment descriptor to fully describe the bean's persistence schema. In this book, the entity bean class is always defined before the XML elements, because it's a more natural approach to developing entity beans. Here is a very simple definition of the CustomerBean class that is developed and packaged for deployment by the bean developer: package com.titan.customer; import javax.ejb.EntityContext; public abstract class CustomerBean implements javax.ejb.EntityBean { public Integer ejbCreate(Integer id){ setId(id); return null; } public void ejbPostCreate(Integer id){ } // abstract accessor methods public abstract Integer getId(); public abstract void setId(Integer id); public abstract String getLastName(); public abstract void setLastName(String lname); public abstract String getFirstName(); public abstract void setFirstName(String fname); // standard callback methods public void setEntityContext(EntityContext ec){} public void unsetEntityContext(){} public void ejbLoad(){} public void ejbStore(){} public void ejbActivate(){} public void ejbPassivate(){} public void ejbRemove(){} } The CustomerBean class is defined as an abstract class. This is required by CMP 2.0 to reinforce the idea that the CustomerBean is not deployed directly into the container system. Since abstract classes cannot be instantiated, in order to be deployed the bean class must be subclassed by a persistence class generated by the deployment tool. Also, the accessor methods are themselves declared as abstract, which necessitates that the container tool implement them. The CustomerBean extends the javax.ejb.EntityBean interface, which defines several callback methods, including setEntityContext(), unsetEntityContext(), ejbLoad(), ejbStore(), ejbActivate(), ejbPassivate(), and ejbRemove(). These methods are important for notifying the bean instance about events in its life cycle, but we do not need to worry about them yet. We will discuss these methods in detail in Chapter 11. The first method in the entity bean class is ejbCreate(), which takes a reference to an Integer object as its only argument. The ejbCreate() method is called when the remote client invokes the create() method on the entity bean's home interface. This concept should be familiar, since it's the same way ejbCreate() worked in the Cabin bean developed in Chapter 4. The ejbCreate() method is responsible for initializing any persistence fields before the entity bean is created. In this first example, the ejbCreate() method is used to initialize the id persistence field, which is represented by the setId()/getId() accessor methods. The return type of the ejbCreate() method is an Integer, which is the primary key of the entity bean. The primary key is a unique identifier that can take a variety of forms, including that of a wrapper for primitive types and custom-defined classes. In this case, the primary key (the Integer) is mapped to the ID field in the CUSTOMER table. This will become evident when we define the XML deployment descriptor. Although the return type of the ejbCreate() method is the primary key, the value actually returned by the ejbCreate() method is null. The EJB container and persistence class will extract the primary key from the bean when it is needed. See Why ejbCreate( ) Returns Null for an explanation of ejbCreate()'s return type.
The ejbPostCreate() method is used to perform initialization after the entity bean is created but before it services any requests from the client. This method usually is used to perform work on the entity bean's relationship fields, which can occur only after the bean's ejbCreate() method is invoked and it's added to the database. For each ejbCreate() method there must be a matching ejbPostCreate() method that has the same method name and arguments but returns void. This pairing of ejbCreate() and ejbPostCreate() ensures that the container calls the correct methods together. We'll explore the use of the ejbPostCreate() in more detail later; for now it's not needed, so its implementation is left empty. The abstract accessor methods (setLastName(), getLastName(), setFirstName(), getFirstName()) represent the persistence fields in the CustomerBean class. These methods are defined as abstract without method bodies. As was already mentioned, when the bean is processed by a container tool, these methods will be implemented by a persistence class based on the abstract persistence schema (XML deployment descriptor elements), the particular EJB container, and the database used. Basically, these methods fetch and update values in the database and are not implemented by the bean developer. 6.2.3 The Remote InterfaceWe will need a CustomerRemote interface for the Customer EJB, because the bean will be accessed by clients outside the container system. The remote interface defines the business methods clients will use to interact with the entity bean. The remote interface should define methods that model the public aspects of the business concept being modeled; that is, those behaviors and data that should be exposed to client applications. Here is the remote interface for CustomerRemote: package com.titan.customer; import java.rmi.RemoteException; public interface CustomerRemote extends javax.ejb.EJBObject { public String getLastName() throws RemoteException; public void setLastName(String lname) throws RemoteException; public String getFirstName() throws RemoteException; public void setFirstName(String fname) throws RemoteException; } Any methods defined in the remote interface must match the signatures of methods defined in the bean class. In this case, the accessor methods in the CustomerRemote interface match persistence field accessor methods in the CustomerBean class. When the remote interface methods match the persistence field methods, the client has direct access to the entity bean's persistence fields. You are not required to match abstract accessor methods in the bean class with methods in the remote interface. In fact, it's recommended that the remote interface be as independent of the abstract programming model as possible. While remote methods can match persistence fields in the bean class, the specification prohibits the remote methods from matching relationship fields, which access other entity beans. In addition, remote methods may not modify any container-managed persistence fields that are part of the primary key of an entity bean. Notice that the remote interface does not define a setId() method, which would allow it to modify the primary key. 6.2.4 The Remote Home InterfaceThe remote home interface of any entity bean is used to create, locate, and remove entities from the EJB container. Each entity bean type may have its own remote home interface, local home interface, or both. As you learned in Chapter 5, the remote and local home interfaces perform essentially the same function. The home interfaces define three basic kinds of methods: home business methods, zero or more create methods, and one or more find methods.[1] The create() methods act like remote constructors and define how new entity beans are created. In our remote home interface, we provide only a single create() method, which matches the corresponding ejbCreate() method in the bean class. The find method is used to locate a specific Customer EJB using the primary key as a unique identifier. The following code contains the complete definition of the CustomerHomeRemote interface: package com.titan.customer; import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.FinderException; public interface CustomerHomeRemote extends javax.ejb.EJBHome { public CustomerRemote create(Integer id) throws CreateException, RemoteException; public CustomerRemote findByPrimaryKey(Integer id) throws FinderException, RemoteException; } A create() method may be suffixed with a name in order to further qualify it when overloading method arguments. This is useful if you have two different create() methods that take arguments of the same type. For example, we could declare two create() methods for Customer that both declare an Integer and a String argument. The String argument might be a social security number (SSN) in one case and a tax identification number (TIN) in another—individuals have social security numbers while corporations have tax identification numbers: public interface CustomerHomeRemote extends javax.ejb.EJBHome { public CustomerRemote createWithSSN(Integer id, String socialSecurityNumber) throws CreateException, RemoteException; public CustomerRemote createWithTIN(Integer id, String taxIdentificationNumber) throws CreateException, RemoteException; public CustomerRemote findByPrimaryKey(Integer id) throws FinderException, RemoteException; } Suffixes are useful when you need create() methods to be more descriptive or need to further qualify them for method overloading. Each create<SUFFIX>() method must have a corresponding ejbCreate<SUFFIX>() in the bean class. For example, the CustomerBean class needs to define ejbCreateWithSSN() and ejbCreateWithTIN() methods as well as matching ejbPostCreateWithSSN() and ejbPostCreateWithTIN() methods. We are keeping this example simple, so we need only one create() method and, therefore, no suffix. Enterprise JavaBeans specifies that create() methods in the remote home interface must throw the javax.ejb.CreateException. In the case of container-managed persistence, the container needs a common exception for communicating problems experienced during the create process. Entity remote home interfaces must define a findByPrimaryKey() method that takes the entity bean's primary key type as its only argument, but no matching method needs to be defined in the entity bean class. The implementation of findByPrimaryKey() is generated automatically by the deployment tool. At runtime, the findByPrimaryKey() method will automatically locate and return a remote reference to the entity bean with the matching primary key. The bean developer can also declare other find methods. For example, the CustomerHomeRemote interface could define a findByLastName(String lname) method that locates all the Customer entities with the specified last name. These types of finder methods are automatically implemented by the deployment tool based on the method signature and an EJB QL statement, which is similar to SQL but is specific to EJB. Custom finder methods and EJB QL are discussed in detail in Chapter 8. 6.2.5 The XML Deployment DescriptorCMP 2.0 entity beans must be packaged for deployment with an XML deployment descriptor that describes the bean and its abstract persistence schema. In most cases, the bean developer is not directly exposed to the XML deployment descriptor but rather will use the container's visual deployment tools to package the beans. In this book, however, I will describe the declarations of the deployment descriptor in detail so you have a full understanding of their content and organization. The XML deployment descriptor, for our simple Customer EJB, contains many elements that should be familiar to you from Chapter 4. The elements specific to entity beans and persistence are most important to us in this chapter. The following is the complete XML deployment descriptor for the Customer EJB: <!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN" "http://java.sun.com/dtd/ejb-jar_2_0.dtd"> <ejb-jar> <enterprise-beans> <entity> <ejb-name>CustomerEJB</ejb-name> <home>com.titan.customer.CustomerHomeRemote</home> <remote>com.titan.customer.CustomerRemote</remote> <ejb-class>com.titan.customer.CustomerBean</ejb-class> <persistence-type>Container</persistence-type> <prim-key-class>java.lang.Integer</prim-key-class> <reentrant>False</reentrant> <cmp-version>2.x</cmp-version> <abstract-schema-name>Customer</abstract-schema-name> <cmp-field><field-name>id</field-name></cmp-field> <cmp-field><field-name>lastName</field-name></cmp-field> <cmp-field><field-name>firstName</field-name></cmp-field> <primkey-field>id</primkey-field> <security-identity><use-caller-identity/></security-identity> </entity> </enterprise-beans> <assembly-descriptor> <security-role> <role-name>Employees</role-name> </security-role> <method-permission> <role-name>Employees</role-name> <method> <ejb-name>CustomerEJB</ejb-name> <method-name>*</method-name> </method> </method-permission> <container-transaction> <method> <ejb-name>CustomerEJB</ejb-name> <method-name>*</method-name> </method> <trans-attribute>Required</trans-attribute> <container-transaction> </assembly-descriptor> </ejb-jar> The first few elements, which declare the Customer EJB name, (CustomerEJB) as well as its home, remote, and bean class, should already be familiar to you from Chapter 4. The <security-identity> element was covered in Chapter 3. The <assembly-descriptor> elements, which declare the security and transaction attributes of the bean, were also covered briefly in Chapter 4. In this case, they state that all employees can access any CustomerEJB method and that all methods use the Required transaction attribute. Container-managed persistence entities also need to declare their persistence type, version, and whether they are reentrant. These elements are declared under the <entity> element. The <persistence-type> element tells the container system whether the bean will be a container-managed persistence entity or a bean-managed persistence entity. In this case it's container-managed, so we use Container. Had it been bean-managed, the value would have been Bean. The <cmp-version> element tells the container system which version of container-managed persistence is being used. EJB 2.0 containers must support the new container-managed persistence model as well as the old one defined in EJB 1.1. The value of the <cmp-version> element can be either 2.x or 1.x, for the EJB 2.0 and 1.1 versions, respectively. The <cmp-version> element is optional. If it is not declared, the default value is 2.x. It's not really needed here, but it's specified as an aid to other developers who might read the deployment descriptor. The <reentrant> element indicates whether reentrant behavior is allowed. In this case the value is False, which indicates that the CustomerEJB is not reentrant (i.e., loopbacks are not allowed). A value of True would indicate that the CustomerEJB is reentrant and that loopbacks are permitted. Reentrant behavior was covered in Chapter 3. The entity bean must also declare its container-managed persistence fields and its primary key: <entity> <ejb-name>CustomerEJB</ejb-name> <home>com.titan.customer.CustomerHomeRemote</home> <remote>com.titan.customer.CustomerRemote</remote> <ejb-class>com.titan.customer.CustomerBean</ejb-class> <persistence-type>Container</persistence-type> <prim-key-class>java.lang.Integer</prim-key-class> <reentrant>False</reentrant> <cmp-version>2.x</cmp-version> <cmp-field><field-name>id</field-name></cmp-field> <cmp-field><field-name>lastName</field-name></cmp-field> <cmp-field><field-name>firstName</field-name></cmp-field> <primkey-field>id</primkey-field> </entity> The container-managed persistence fields are the id, lastName, and firstName, as indicated by the <cmp-field> elements. The <cmp-field> elements must have matching accessor methods in the CustomerBean class. As you can see in Table 6-1, the values declared in the <field-name> element match the names of abstract accessor methods we declared in the CustomerBean class—the get and set parts of the method names are ignored when matching methods to <field-name> declarations.
CMP 2.0 requires that the <field-name> values start with a lowercase letter while their matching accessor methods take the form get<field-name value>(), set<field-name value>(), with the first letter of the <field-name> capitalized. The return type of the get method and the parameter of the set method determine the type of the <cmp-field>. It's the convention of this book, but not a requirement of CMP 2.0, that field names with multiple words are declared using "camel case," where each new word starts with a capital letter (e.g., lastName). Finally, we declare the primary key using two fields, <prim-key-class> and <primkey-field>. <prim-key-class> indicates the type of the primary key, and <primkey-field> indicates which of the <cmp-field> elements designates the primary key. The Customer EJB uses a single-field primary key, in which only one field of the entity bean's container-managed fields describes a unique identifier for the bean. The <primkey-field> must be declared if the entity bean uses a single-field primary key. Compound primary keys, which use more than one of the persistence fields as a key, are often used instead. In this case, the bean developer creates a custom primary key. The <prim-key-class> element is always required, whether it's a single-field, compound, or unknown primary key. Unknown keys use a field that may not be declared in the bean at all. The different types of primary keys are covered in more detail in Chapter 11. 6.2.6 The EJB JAR FileNow that you have created the interfaces, bean class, and deployment descriptor, you're ready to package the bean for deployment. As you learned in Chapter 4, the JAR file provides a way to "shrink-wrap" a component so it can be sold and/or deployed in an EJB container. The examples available from http://www.oreilly.com/catalog/entjbeans3/ contain a properly prepared JAR file that includes the Customer EJB's interfaces, bean class, and deployment descriptor. You may use these files or develop them yourself. The command for creating a new EJB JAR file is: \dev % jar cf customer.jar com/titan/customer/*.class com/titan/customer/META-INF/ejb-jar.xml F:\..\dev>jar cf cabin.jar com\titan\customer\*.class com\titan\customer \META-INF\ejb-jar.xml Most EJB servers provide graphical or command-line tools that create the XML deployment descriptor and package the enterprise bean into a JAR file automatically. Some of these tools even create the home and remote interfaces automatically, based on input from the developer. If you prefer to use these tools, the workbooks will step you through the process of deploying an entity bean using specific vendors' container- deployment tools. 6.2.7 DeploymentOnce the CustomerEJB is packaged in a JAR file, it's ready to be processed by the deployment tools. For most vendors, these tools are combined into one graphical user interface used at deployment time. The point is to map the container-managed persistence fields of the bean to fields or data objects in the database. (Earlier in this chapter, Figure 6-2 and Figure 6-3 showed two visual tools used to map the Customer EJB's persistence fields.) In addition, the security roles need to be mapped to the subjects in the security realm of the target environment and the bean needs to be added to the naming service and given a JNDI lookup name (name binding). These tasks are also accomplished using the deployment tools provided by your vendor. The workbooks provide step-by-step instructions for deploying the CustomerEJB in specific vendor environments. 6.2.8 The Client ApplicationThe Client application is a remote client to the CustomerEJB that will create several customers, find them, and then remove them. Here is the complete definition of the Client application: import javax.naming.InitialContext; import javax.naming.Context; import javax.naming.NamingException; import java.util.Properties; public class Client { public static void main(String [] args)) throws Exception { //obtain CustomerHome Context jndiContext = getInitialContext(); Object obj=jndiContext.lookup("CustomerHomeRemote"); CustomerHomeRemote home = (CustomerHomeRemote) javax.rmi.PortableRemoteObject.narrow(obj,CustomerHomeRemote.class); //create Customers for(int i =0;i <args.length;i++){ Integer primaryKey =new Integer(args [ i ]); String firstName = args [ i ]; String lastName = args [ i ]; CustomerRemote customer = home.create(primaryKey); customer.setFirstName(firstName); customer.setLastName(lastName); } //find and remove Customers for(int i =0;i <args.length;i++){ Integer primaryKey = new Integer(args [i ]); CustomerRemote customer = home.findByPrimaryKey(primaryKey); String lastName = customer.getLastName(); String firstName = customer.getFirstName(); System.out.print(primaryKey+"="); System.out.println(firstName+""+lastName); //remove Customer customer.remove(); } } public static Context getInitialContext( throws javax.naming.NamingException { Properties p =new Properties(); //...Specify the JNDI properties specific to the vendor. return new javax.naming.InitialContext(p); } } The Client application creates several Customer EJBs, sets their first and last names, prints out the persistence field values, and then removes the entities from the container system and, effectively, the database. Please refer to Workbook Exercise 6.1, Basic Persistence in CMP 2.0. This workbook is available free, in PDF format, at http://www.oreilly.com/catalog/entjbeans3/workbooks. 6.3 Persistence FieldsContainer-managed persistence (CMP) fields are virtual fields whose values map directly to the database. Persistence fields can be Java serializable types and Java primitive types. Java serializable types can be any class that implements the java.io.Serializable interface. Most deployment tools handle java.lang.String, java.util.Date, and the primitive wrappers (Byte, Boolean, Short, Integer, Long, Double, and Float) easily, because these types of objects are part of the Java core and map naturally to fields in relational and other databases. The CustomerEJB declares three serializable fields, id, lastName, and firstName, which map naturally to the INT and CHAR fields of the CUSTOMER table in the database. You can also define your own serializable types, called dependent value classes, and declare them as CMP fields. However, I recommend that you do not use custom serializable objects as persistence field types unless it is absolutely necessary—they are usually recommended for unstructured types, such as multimedia data (images, blobs, etc.). Arbitrary dependent value classes usually will not map naturally to database types, so they must be stored in their serializable forms in some type of binary database field. Serializable objects are always returned as copies and not references, so modifying a serializable object will not impact its database value. The entire value must be updated using the abstract set<field-name> method. The primitive types (byte, short, int, long, double, float, and boolean) are also allowed to be CMP fields. These types are easily mapped to the database and are supported by all deployment tools. As an example, the CustomerEJB might declare a boolean that represents a customer's credit worthiness: public abstract class CustomerBean implements javax.ejb.EntityBean { public Integer ejbCreate(Integer id){ setId(id); return null; } // abstract accessor methods public abstract boolean getHasGoodCredit(); public abstract void setHasGoodCredit(boolean creditRating); 6.4 Dependent Value ClassesAs discussed in the previous section, dependent value classes are custom serializable objects that can be used as persistence fields—(although this use is not recommended). Dependent value classes are useful for packaging data and moving it between an entity bean and its remote clients. They separate the client's view of the entity bean from its abstract persistence model, which makes it easier for the entity bean class to change without impacting existing clients. The remote interface methods of an entity bean should be defined independently of the anticipated abstract persistence schema. In other words, you should design the remote interfaces to model the business concepts, not the underlying persistence programming model. Dependent value classes can help separate a remote client's view from the persistence model by providing objects that fill the gaps in these perspectives. Dependent value classes are used a lot in remote interfaces where packaging data together can reduce network traffic, but are less useful in local interfaces. For example, the CustomerEJB could be modified so that its lastName and firstName fields are not exposed directly to remote clients through their accessor methods. This is a reasonable design approach, since most clients access the entire name of the customer at once. In this case, the remote interface might be modified to look as follows: import java.rmi.RemoteException; public interface CustomerRemote extends javax.ejb.EJBObject { public Name getName() throws RemoteException; public void setName(Name name) throws RemoteException; } The remote interface here is simpler than the one we saw earlier. It allows the remote client to get all the name information in one method call instead of two—this reduces network traffic and improves performance for remote clients. The use of the Name dependent value is also semantically more consistent with how the client interacts with the Customer EJB. To implement this interface, the CustomerBean class adds a business method that matches the remote interface methods. The setName() method updates the lastName and firstName fields, while the getName() method constructs a Name object from these fields: import javax.ejb.EntityContext; public abstract class CustomerBean implements javax.ejb.EntityBean { public Integer ejbCreate(Integer id){ setId(id); return null; } public void ejbPostCreate(Integer id) { } // business methods public Name getName() { Name name = new Name(getLastName(),getFirstName()); return name; } public void setName(Name name) { setLastName(name.getLastName()); setFirstName(name.getFirstName()); } // abstract accessor methods public abstract String getLastName(); public abstract void setLastName(String lname); public abstract String getFirstName(); public abstract void setFirstName(String fname); This is a good example of how dependent value classes can be used to separate the client's view from the abstract persistence schema. The getName() and setName() methods are not abstract persistence methods, they are business methods. Entity beans can have as many business methods as needed. Business methods introduce business logic to the Customer EJB; otherwise, the bean would be only a data wrapper. For example, validation logic could be added to the setName() method to ensure that the data is correct before applying the update. In addition, the entity bean class can use other methods that help with processing data—these are just instance methods and may not be exposed as business methods in the remote interface. How dependent value classes are defined is important to understanding how they should be used. The Name dependent value class is defined as follows: public class Name implements java.io.Serializable { private String lastName; private String firstName; public Name(String lname, String fname){ lastName = lname; firstName = fname; } public String getLastName() { return lastName; } public String getFirstName() { return firstName; } } You'll notice that the Name dependent value class has get accessor methods but not set methods. It's immutable. This is a design strategy used in this book, not a requirement of the specification; CMP 2.0 does not specify how dependent value classes are defined. We make dependent values immutable so that "remote clients cannot change the Name object's fields. The reason is simple: the Name object is a copy, not a remote reference. Changes to Name objects are not reflected in the database. Making the Name immutable helps to ensure that clients do not mistake this dependent value for a remote object reference, thinking that a change to the Name object is automatically reflected in the database. To change the customer's name, the client is required to create a new Name object and use the setName() method to update the Customer EJB. The following code listing illustrates how a client would modify the name of a customer using the Name dependent value class: // find Customer customer = home.findByPrimaryKey(primaryKey); name = customer.getName(); System.out.print(primaryKey+" = "); System.out.println(name.getFirstName()+" "+name.getLastName()); // change Customer's name name = new Name("Monson-Haefel", "Richard"); customer.setName(name); name = customer.getName(); System.out.print(primaryKey+" = "); System.out.println(name.getFirstName()+" "+name.getLastName()); The output will look like this: 1 = Richard Monson 1 = Richard Monson-Haefel Defining the bean's interfaces according to the business concept and not the underlying data is not always reasonable, but you should try to employ this strategy when the underlying data model doesn't clearly map to the business purpose or concept being modeled by the entity bean. The bean's interfaces may be used by developers who know the business but not the abstract programming model. It is important to them that the entity beans reflect the business concept. In addition, defining the interfaces independently of the persistence model enables the component interfaces and persistence model to evolve separately. This allows the abstract persistence programming model to change over time and allows for new behavior to be added to the entity bean as needed. While the dependent value classes serve a purpose, they should not be used indiscriminately. Generally speaking, it is foolish to use dependent value classes when a CMP field will do just fine. For example, checking a client's credit worthiness before processing an order can be accomplished easily using the getHasGoodCredit() method directly. In this case, a dependent value class would serve no purpose. Please refer to Workbook Exercise 6.2, Dependent Value Classes in CMP 2.0. This workbook is available free, in PDF format, at http://www.oreilly.com/catalog/entjbeans3/workbooks. 6.5 Relationship FieldsEntity beans can form relationships with other entity beans. In Figure 6-1, at the beginning of this chapter, the Customer EJB is shown to have a one-to-one relationship with the Address EJB. The Address EJB is a fine-grained business object that should always be accessed in the context of another entity bean, which means it should have only local interfaces and not remote interfaces. An entity bean can have relationships with many different entity beans at the same time. For example, we could easy add relationship fields for Phone, CreditCard, and other entity beans to the Customer EJB. At this point, however, we're choosing to keep the Customer EJB simple. Following Figure 6-1 (earlier in the chapter) as a guide, we define the Address EJB as follows: package com.titan.address; import javax.ejb.EntityContext; public abstract class AddressBean implements javax.ejb.EntityBean { public Integer ejbCreateAddress(String street, String city, String state, String zip) { setStreet(street); setCity(city); setState(state); setZip(zip); return null; } public void ejbPostCreateAddress(String street, String city, String state, String zip) { } // persistence fields public abstract Integer getId(); public abstract void setId(Integer id); public abstract String getStreet(); public abstract void setStreet(String street); public abstract String getCity(); public abstract void setCity(String city); public abstract String getState(); public abstract void setState(String state); public abstract String getZip(); public abstract void setZip(String zip); // standard callback methods public void setEntityContext(EntityContext ec){} public void unsetEntityContext(){} public void ejbLoad(){} public void ejbStore(){} public void ejbActivate(){} public void ejbPassivate(){} public void ejbRemove(){} } The AddressBean class defines an ejbCreateAddress() method, which is called when a new Address EJB is created, as well as several persistence fields (street, city, state, and zip). The persistence fields are represented by the abstract accessor methods, which is the idiom required for persistence fields in all entity bean classes. These abstract accessor methods are matched with their own set of XML deployment descriptor elements, which define the abstract persistence schema of the Address EJB. At deployment time, the container's deployment tool will map the Customer EJB's and Address EJB's persistence fields to the database. This means that there must be a table in our relational database that contains columns that match the persistence fields in the Address EJB. In this example, we will use a separate ADDRESS table for storing address information, but the data could just as easily have been declared in the other table: CREATE TABLE ADDRESS ( ID INT PRIMARY KEY NOT NULL, STREET CHAR(40), CITY CHAR(20), STATE CHAR(2), ZIP CHAR(10) ) Entity beans do not have to define all the columns from corresponding tables as persistence fields. In fact, an entity bean may not even have a single corresponding table; it may be persisted to several tables. The bottom line is that the container's deployment tool allows the abstract persistence schema of an entity bean to be mapped to a database in a variety of ways, allowing a clean separation between the persistence classes and the database. The ID column is an auto-increment field, created automatically by the database or container system. It is the primary key of the Address EJB. Once the bean is created the primary key must never again be modified. When primary keys are autogenerated values, such as the ID column in the ADDRESS table, the EJB container will obtain the primary key value from the database. In addition to the bean class, we will define the local interface for the Address EJB, which allows it to be accessed by other entity beans (namely, the Customer EJB) within the same address space or process: // Address EJB's local interface public interface AddressLocal extends javax.ejb.EJBLocalObject { public String getStreet(); public void setStreet(String street); public String getCity(); public void setCity(String city); public String getState(); public void setState(String state); public String getZip(); public void setZip(String zip); } // Address EJB's local home interface public interface AddressHomeLocal extends javax.ejb.EJBLocalHome { public AddressLocal createAddress(String street,String city, String state,String zip) throws javax.ejb.CreateException; public AddressLocal findByPrimaryKey(Integer primaryKey) throws javax.ejb.FinderException; } You may have noticed that the ejbCreate() method of the AddressBean class and the findByPrimaryKey() method of the home interface both define the primary key type as java.lang.Integer. As I mentioned earlier, the primary key is autogenerated. Most EJB 2.0 vendors will allow entity beans' primary keys to be mapped to autogenerated fields. If your vendor does not support autogenerated primary keys, you will need to set the primary key value in the ejbCreate() method. This is usually true of single-value primary keys, but not necessarily of compound primary keys. The relationship field for the Address EJB is defined in the CustomerBean class using an abstract accessor method, the same way that persistence fields are declared. In the following code, the CustomerBean has been modified to include the Address EJB as a relationship field: import javax.ejb.EntityContext; import javax.ejb.CreateException; public abstract class CustomerBean implements javax.ejb.EntityBean { ... // persistence relationship public abstract AddressLocal getHomeAddress(); public abstract void setHomeAddress(AddressLocal address); // persistence fields public abstract boolean getHasGoodCredit(); public abstract void setHasGoodCredit(boolean creditRating); ... The getHomeAddress() and setHomeAddress() accessor methods are self-explanatory; they allow the bean to access and modify its homeAddress relationship. These accessor methods represent a relationship field, which is a virtual field that references another entity bean. The name of the accessor method is determined by the name of the relationship field, as declared in the XML deployment descriptor. In this case we have named the customer's address homeAddress, so the corresponding accessor method names will be getHomeAddress() and setHomeAddress(). To accommodate the relationship between the Customer EJB and the home address, a foreign key, ADDRESS_ID, will be added to the CUSTOMER table. The foreign key will point to the ADDRESS record. In practice, this schema is actually the reverse of what is usually done, where the ADDRESS table contains a foreign key to the CUSTOMER table. However, the schema used here is useful in demonstrating alternative database mappings and is utilized again in Chapter 7: CREATE TABLE CUSTOMER ( ID INT PRIMARY KEY NOT NULL, LAST_NAME CHAR(20), FIRST_NAME CHAR(20), ADDRESS_ID INT ) When a new Address EJB is created and set as the Customer EJB's homeAddress relationship, the Address EJB's primary key will be placed in the ADDRESS_ID column of the CUSTOMER table, creating a relationship in the database: // get local reference AddressLocal address = ... // establish the relationship customer.setHomeAddress(address); To give the Customer a home address, we need to deliver the address information to the Customer. This appears to be a simple matter of declaring matching setHomeAddress()/getHomeAddress() accessors in the remote interface, but it's not! While it's valid to make persistence fields directly available to remote clients, persistence relationships are more complicated than that. The remote interface of a bean is not allowed to expose its relationship fields. In the case of the homeAddress field, we have declared the type to be AddressLocal, which is a local interface, so the setHomeAddress()/getHomeAddress() accessors cannot be declared in the remote interface of the Customer EJB. The reason for this restriction on remote interfaces is fairly simple: the EJBLocalObject, which implements the local interface, is optimized for use within the same address space or process as the bean instance and is not capable of being used across the network. In other words, references that implement the local interface of a bean cannot be passed across the network, so a local interface cannot declared as a return type of a parameter of a remote interface. We take advantage of the EJBLocalObject optimization for better performance, but that same advantage limits location transparency; we must use it only within the same address space. Local interfaces (interfaces that extend javax.ejb.EJBLocalObject), on the other hand, can expose any kind of relationship field. With local interfaces, the caller and the enterprise bean being called are located in the same address space, so they can pass around local references without a problem. For example, if we had defined a local interface for the Customer EJB, it could include a method that allows local clients to access its Address relationship directly: public interface CustomerLocal extends javax.ejb.EJBLocalObject { public AddressLocal getHomeAddress(); public void setHomeAddress(AddressLocal address); } When it comes to the Address EJB, it's better to define a local interface only because it's such a fine-grained bean. To get around remote-interface restrictions, the business methods in the bean class exchange address data instead of Address references. For example, we can declare a method that allows the client to send address information to create a home address for the Customer: public abstract class CustomerBean implements javax.ejb.EntityBean { public Integer ejbCreate(Integer id) { setId(id); return null; } public void ejbPostCreate(Integer id) { } // business method public void setAddress(String street,String city,String state,String zip) { try { AddressLocal addr = this.getHomeAddress(); if(addr == null) { // Customer doesn't have an address yet. Create a new one. InitialContext cntx = new InitialContext(); AddressHomeLocal addrHome = (AddressHomeLocal)cntx.lookup("AddressHomeLocal"); addr = addrHome.createAddress(street,city,state,zip); this.setHomeAddress(addr); } else { // Customer already has an address. Change its fields. addr.setStreet(street); addr.setCity(city); addr.setState(state); addr.setZip(zip); } } catch(Exception e) { throw new EJBException(e); } } ... The setAddress() business method in the CustomerBean class is also declared in the remote interface of the Customer EJB, so it can be called by remote clients: public interface Customer extends javax.ejb.EJBObject { public void setAddress(String street,String city,String state,String zip); public Name getName() throws RemoteException; public void setName(Name name) throws RemoteException; public boolean getHasGoodCredit() throws RemoteException; public void setHasGoodCredit(boolean creditRating) throws RemoteException; } When the CustomerRemote.setAddress() business method is invoked on the CustomerBean, the method's arguments are used to create a new Address EJB and set it as the homeAddress relationship field, if one doesn't already exist. If the Customer EJB already has a homeAddress relationship, that Address EJB is modified to reflect the new address information. When creating a new Address EJB, the home object is obtained from the JNDI ENC (environment naming context) and its createAddress() method is called. This results in the creation of a new Address EJB and the insertion of a corresponding ADDRESS record into the database. After the Address EJB is created, it's used in the setHomeAddress() method. The CustomerBean class must explicitly call the setHomeAddress() method, or the new address will not be assigned to the customer. In fact, simply creating an Address EJB without assigning it to the customer with the setHomeAddress() method will result in a disconnected Address EJB. More precisely, it will result in an ADDRESS record in the database that is not referenced by any CUSTOMER records. Disconnected entity beans are fairly normal and even desirable in many cases. In this case, however, we want the new Address EJB to be assigned to the homeAddress relationship field of the Customer EJB.
When the setHomeAddress() method is invoked, the container links the ADDRESS record to the CUSTOMER record automatically. In this case, it places the ADDRESS primary key in the CUSTOMER record's ADDRESS_ID field and creates a reference from the CUSTOMER record to the ADDRESS record. If the Customer EJB already has a homeAddress, we want to change its values instead of creating a new Address EJB. We don't need to use setHomeAddress() if we are simply updating the values of an existing Address EJB, because the Address EJB we modified already has a relationship with the entity bean. We also want to provide clients with a business method for obtaining a Customer EJB's home address information. Since we are prohibited from sending an instance of the Address EJB directly to the client (because it's a local interface), we must package the address data in some other form and send that to the client. There are two solutions to this problem: acquire the remote interface of the Address EJB and return that; or return the data as a dependent value object. We can obtain the remote interface for the Address EJB only if one was defined. Entity beans can have a set of either local interfaces or remote interfaces, or both. In the situation with which we're dealing the Address EJB is too fine-grained to justify creating a remote interface, but in many other circumstances a bean may indeed want to have a remote interface. If, for example, the Customer EJB referenced a SalesPerson EJB, the CustomerBean could convert the local reference into a remote reference. This would be done by accessing the local EJB object, getting its primary key (EJBLocalObject.getPrimaryKey()), obtaining the SalesPerson EJB's remote home from the JNDI ENC, and then using the primary key and remote home reference to find a remote interface reference: public SalesRemote getSalesRep(){ SalesLocal local = getSalesPerson(); Integer primKey = local.getPrimaryKey(); Object ref = jndiEnc.lookup("SalesHomeRemote"); SalesHomeRemote home = (SalesHomeRemote) PortableRemoteObject.narrow(ref, SalesHomeRemote.class); SalesRemote remote = home.findByPrimaryKey( primKey ); return remote; } The other option is to use a dependent value to pass the Address EJB's data between remote clients and the Customer EJB. This is the approach recommended for fine-grained beans like the Address EJB—in general, we don't want to expose these beans directly to remote clients. The following code shows how the AddressDO dependent value class is used in conjunction with the local component interfaces of the Address EJB (the DO in AddressDO is a convention used in this book—it's a qualifier that stands for "dependent object"): public abstract class CustomerBean implements javax.ejb.EntityBean { public Integer ejbCreate(Integer id) { setId(id); return null; } public void ejbPostCreate(Integer id) { } // business method public AddressDO getAddress() { AddressLocal addrLocal = getHomeAddress(); if(addrLocal == null) return null; String street = addrLocal.getStreet(); String city = addrLocal.getCity(); String state = addrLocal.getState(); String zip = addrLocal.getZip(); AddressDO addrValue = new AddressDO(street,city,state,zip); return addrValue; } public void setAddress(AddressDO addrValue) throws EJBException { String street = addrValue.getStreet(); String city = addrValue.getCity(); String state = addrValue.getState(); String zip = addrValue.getZip(); AddressLocal addr = getHomeAddress(); try { if(addr == null) { // Customer doesn't have an address yet. Create a new one. InitialContext cntx = new InitialContext(); AddressHomeLocal addrHome = (AddressHomeLocal)cntx.lookup ("AddressHomeLocal"); addr = addrHome.createAddress(street, city, state, zip); this.setHomeAddress(addr); } else { // Customer already has an address. Change its fields. addr.setStreet(street); addr.setCity(city); addr.setState(state); addr.setZip(zip); } } catch(NamingException ne) { throw new EJBException(ne); } catch(CreateException ce) { throw new EJBException(ce); } } ... Here is the definition for an AddressDO dependent value class, which is used by the enterprise bean to send address information to the client: public class AddressDO implements java.io.Serializable { private String street; private String city; private String state; private String zip; public AddressDO(String street, String city, String state, String zip ) { this.street = street; this.city = city; this.state = state; this.zip = zip; } public String getStreet() { return street; } public String getCity() { return city; } public String getState() { return state; } public String getZip() { return zip; } } The AddressDO dependent value follows the conventions laid out in this book. It's immutable, which means it cannot be altered once it is created. As stated earlier, immutability helps to reinforce the fact that the dependent value class is a copy, not a remote reference. You can now use a client application to test the Customer EJB's relationship with the Address EJB. Here is the client code that creates a new Customer, gives it an address, then changes the address using the method defined earlier: import javax.naming.InitialContext; import javax.rmi.PortableRemoteObject; import javax.naming.Context; import javax.naming.NamingException; import java.util.Properties; public class Client { public static void main(String [] args) throws Exception { // obtain CustomerHome Context jndiContext = getInitialContext(); Object obj=jndiContext.lookup("CustomerHomeRemote"); CustomerHome home = (CustomerHomeRemote) javax.rmi.PortableRemoteObject.narrow(obj, CustomerHomeRemote.class); // create a Customer Integer primaryKey = new Integer(1); Customer customer = home.create(primaryKey); // create an address AddressDO address = new AddressDO("1010 Colorado", "Austin", "TX", "78701"); // set address customer.setAddress(address); address = customer.getAddress(); System.out.print(primaryKey+" = "); System.out.println(address.getStreet()); System.out.println(address.getCity()+","+ address.getState()+" "+ address.getZip()); // create a new address address = new AddressDO("1600 Pennsylvania Avenue NW", "DC", "WA", "20500"); // change Customer's address customer.setAddress(address); address = customer.getAddress(); System.out.print(primaryKey+" = "); System.out.println(address.getStreet()); System.out.println(address.getCity()+","+ address.getState()+" "+ address.getZip()); // remove Customer customer.remove(); } public static Context getInitialContext() throws javax.naming.NamingException { Properties p = new Properties(); // ... Specify the JNDI properties specific to the vendor. //return new javax.naming.InitialContext(p); return null; } } The following listing shows the deployment descriptor for the Customer and Address EJBs. You don't need to worry about the details of the deployment descriptor yet; it will be covered in depth in Chapter 7. <!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN" "http://java.sun.com/dtd/ejb-jar_2_0.dtd"> <ejb-jar> <enterprise-beans> <entity> <ejb-name>CustomerEJB</ejb-name> <home>com.titan.customer.CustomerHomeRemote</home> <remote>com.titan.customer.CustomerRemote</remote> <ejb-class>com.titan.customer.CustomerBean</ejb-class> <persistence-type>Container</persistence-type> <prim-key-class>java.lang.Integer</prim-key-class> <reentrant>False</reentrant> <cmp-version>2.x</cmp-version> <abstract-schema-name>Customer</abstract-schema-name> <cmp-field><field-name>id</field-name></cmp-field> <cmp-field><field-name>lastName</field-name></cmp-field> <cmp-field><field-name>firstName</field-name></cmp-field> <primkey-field>id</primkey-field> <security-identity><use-caller-identity/></security-identity> </entity> <entity> <ejb-name>AddressEJB</ejb-name> <local-home>com.titan.address.AddressHomeLocal</local-home> <local>com.titan.address.AddressLocal</local> <ejb-class>com.titan.address.AddressBean</ejb-class> <persistence-type>Container</persistence-type> <prim-key-class>java.lang.Integer</prim-key-class> <reentrant>False</reentrant> <cmp-version>2.x</cmp-version> <abstract-schema-name>Address</abstract-schema-name> <cmp-field><field-name>id</field-name></cmp-field> <cmp-field><field-name>street</field-name></cmp-field> <cmp-field><field-name>city</field-name></cmp-field> <cmp-field><field-name>state</field-name></cmp-field> <cmp-field><field-name>zip</field-name></cmp-field> <primkey-field>id</primkey-field> <security-identity><use-caller-identity/></security-identity> </entity> </enterprise-beans> <relationships> <ejb-relation> <ejb-relation-name>Customer-Address</ejb-relation-name> <ejb-relationship-role> <ejb-relationship-role-name> Customer-has-an-Address </ejb-relationship-role-name> <multiplicity>One</multiplicity> <relationship-role-source> <ejb-name>CustomerEJB</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>homeAddress</cmr-field-name> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> <ejb-relationship-role-name> Address-belongs-to-Customer </ejb-relationship-role-name> <multiplicity>One</multiplicity> <relationship-role-source> <ejb-name>AddressEJB</ejb-name> </relationship-role-source> </ejb-relationship-role> </ejb-relation> </relationships> <assembly-descriptor> <security-role> <role-name>Employees</role-name> </security-role> <method-permission> <role-name>Employees</role-name> <method> <ejb-name>CustomerEJB</ejb-name> <method-name>*</method-name> </method> <method> <ejb-name>AddressEJB</ejb-name> <method-name>*</method-name> </method> </method-permission> <container-transaction> <method> <ejb-name>AddressEJB</ejb-name> <method-name>*</method-name> </method> <method> <ejb-name>CustomerEJB</ejb-name> <method-name>*</method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar> Please refer to Workbook Exercise 6.3, A Simple Relationship in CMP 2.0. This workbook is available free, in PDF format, at http://www.oreilly.com/catalog/entjbeans3/workbooks.
| ||||||||||||||||||
|