9.7. Entity Bean RelationshipsBusiness systems frequently define relationships between entity beans. These relationships can be complex or simple. The concept of a cabin and its relationship to a ship embodies both simple and complex relationships. A cabin always belongs to a particular ship--a relationship that's obviously fairly simple. From the ship's perspective, the relationship is more complex: a ship has many cabins and must maintain a relationship to all of them. This section explores how to write entity beans that use container-managed persistence and maintain relationships with other beans. This information will be most useful to EJB 1.0 developers. EJB 1.0 does not allow references to other beans to be container-managed. This means that a bean needs to manage persistence for references to other beans within its own code. If you're using EJB 1.1, you can probably ignore this section, particularly if your server has robust support for the persistence of bean references. While EJB 1.1 allows bean references to be container-managed fields, a few EJB 1.1 servers may not be able to persist relationships between beans. This section will be useful to developers using these limited EJB 1.1 servers. EJB 1.1 developers using bean-managed persistence may also find the strategies in this section useful. However, since this is predominately a EJB 1.0 problem, the code in this section has been left in the EJB 1.0 style. EJB 1.1 developers must make minor changes to the code for it to work in EJB 1.1 servers. 9.7.1. Simple AssociationsIn Titan Cruises, the business concept of a cabin models the real-world cabins that are in all of Titan's ships. Regardless of the ship, cabins all have certain attributes that we want to capture in the Cabin business concept: cabin name, deck level, and so forth. Important to this discussion is the cabin's relationship to its ship. How do we model and implement this simple relationship? There are several alternatives, which can be grouped into two general categories: implementation-specific and non-implementation-specific. Both categories have their own strengths and weaknesses, which we will explore later in this section. Let's start by defining the Cabin bean's remote interface. We add methods that allow the cabin to set and get the Ship as a bean, rather than by its ID. The advantage of this approach is that it encapsulates the Ship bean's unique identifier and deals with business concepts as beans, not database-dependent IDs.
9.7.1.1. Maintaining the database mappingThe simplest strategy for managing the cabin-to-ship relationship would be to support the relationship as defined in the database. In the relational database, the CABIN table includes a foreign key to the SHIP table's primary key, called SHIP_ID. In its current definition, we maintain the database mapping by preserving the SHIP_ID as an integer value. We can modify the behavior slightly by using the Ship bean in the get and set methods rather than the ID:
From the client's standpoint, the Cabin bean now models its relationship to the Ship bean as a business concept and not as a database ID. The advantage of this approach is that we maintain the database mapping defined by the CABIN table while hiding the mapping from the client. The disadvantage is that the bean must frequently dereference the ship's primary key whenever a client invokes the getShip() method. If the entity bean has been deactivated since the ship's remote reference was last obtained, the reference will need to be reobtained. After you've deployed this new version of the Cabin bean, you can use a client application to see if your code works. You can use code like this in your client:
9.7.1.2. Mapping serializable to VARBINARYWith the Cabin bean, the use of the database mappings is the most straightforward approach to preserving the Cabin bean's relationship to the Ship bean. With other relationships, a relational database foreign key may not already exist. In this case, we need to preserve the relationship in the form of primary keys or handles. Both primary keys and handles are serializable, so they can be preserved in a relational database as the JDBC type VARBINARY. The data type in the actual database will vary; some databases use BLOB while others use a different data type to indicate variable-length binary data. These data types are typically used for storing arbitrary binary data-like images. If you are using an existing database and need to preserve a nonrelational association between entities, you must update the table structure to include a VARBINARY type column. Whether we use the primary key or the handle, we need to obtain it from the Ship bean and preserve it in the database. To do this, we use Java serialization to convert the primary key or handle into a byte array, which we can then save in the database as binary data. We can convert this data back into a usable key or handle as needed. One way to do this is to define a couple of simple methods that can take any Serializable object and make it into a byte array and convert it back. These methods are defined as follows:
9.7.1.3. Preserving the primary keyIf you can't preserve a foreign database ID, you can preserve the primary key directly. We can use the serialize() method defined earlier in setShip(Shipship) to preserve the primary key of the Ship bean in the database:
We have replaced the ship_id field with shipBinary. Remember, we are now looking at situations where the relationship between entities cannot be modeled in a relational database. When the CabinBean class is persisted to the database, the shipBinary field is written to the database with the primary key's serialized value. If the bean uses container-managed persistence, the shipBinary field will need to be mapped to the BLOB or binary column of the table. If the bean uses bean-managed persistence, the JDBC API will simply return a binary data type from the appropriate column. 9.7.1.4. Preserving the handleUsing the handle to preserve simple relationships is almost exactly the same as using the primary key. To preserve the handle, we can use the same shipBinary field that we used to save the primary key strategy; we only need to make a couple of simple changes to the setShip() and getShip() methods:
In many cases, serializing the handle is simpler then using the primary key. This version of the getShip() method is much simpler: you don't need to reconstruct the primary key, and the getInitialContext() method is no longer needed. However, the use of handles over primary keys should be done with care. Handles are simpler, but also more volatile. A change in the container, naming, networking, or security can cause the handle to become invalid. This strategy is especially useful when the primary key is more complex than our simple ShipPK. If the primary key is made up of several fields, for example, using a binary format provides more benefits. 9.7.1.5. Native Java persistenceSome database products support native persistence of Java objects. These might be object databases or even relational databases that can store Java objects. Entity beans that use native persistence are the simplest to develop because there is no need to convert fields to byte streams so they can be stored in the database. In the following listing, the CabinBean class has been changed so that it uses native Java persistence with the Ship bean's primary key:
9.7.2. Complex Entity RelationshipsSituationsin which an entity has a relationship to several other entities of a particular type are as common as simple relationships. An example of a complex relationship is a ship. In the real world, a ship may contain thousands of cabins. To develop a Ship bean that models the relationship between a real-world ship and its cabins, we can use the same strategies that we discussed earlier. The most important difference is that we're now discussing one-to-many associations rather than one-to-one. Before we get started, let's add a couple of new methods to the Ship bean's remote interface:
The Ship bean's remote interface indicates that it is associated with many Cabin beans, which makes perfectly good sense as a business concept. The application developer using the Ship remote interface is not concerned with how the Cabin beans are stored in the Ship bean. Application developers are only concerned with using the two new methods, which allow them to obtain a list of cabins assigned to a specific ship and to add new cabins to the ship when appropriate. 9.7.2.1. One-to-many database mappingThe database structure provides a good relational model for mapping multiple cabins to a single ship. In the relational database, the CABIN table contains a foreign key called ship_id, which we used previously to manage the cabin's relationship to the ship. As a foreign key, the ship_id provides us with a simple relational mapping from a ship to many cabins using an SQL join: "select ID from CABIN where SHIP_ID = "+ship_id Nothing complicated about that. Unfortunately, this relationship cannot be done conveniently with container-managed persistence, because we would need to map a collection to the join, which is not as straightforward as mapping a single entity field to a database field. More advanced object-to-relational mapping software will simplify this task, but these advanced tools are not always available and have their own limitations. If your EJB server doesn't support object-to-relational mapping, you will need an alternative solution. One alternative is to use bean-managed persistence to leverage the database mapping. If, however, your EJB server supports JavaBlend or some other object-to-relational technology, you may still be able to use container-managed persistence with a one-to-many database mapping. If you examine the SHIP table definition, there is no column for storing a list of cabins, so we will not store this relationship directly in the SHIP table. Instead, we use a Vector called cabins to store this relationship temporarily. Every time the bean's ejbLoad() method is called, it populates the vector with cabin IDs, as it does all the other fields. Here are the getCabins() and ejbLoad() methods of the ShipBean class, with the changes for managing the vector of cabin IDs in bold:
9.7.2.2. Mapping serializable to VARBINARYUsing byte arrays and Java serialization works with complex relationships just as well as it did with simple relationships. In this case, however, we are serializing some kind of collection instead of a single reference. If, for example, the Ship bean were container-managed, the ejbLoad() and ejbStore() methods could be used to convert our cabins vector between its representation as a Vector and a byte array. The following code illustrates how this could work with container-managed persistence; it applies equally well to Cabin primary keys or Cabin bean Handle objects:
9.7.2.3. Native Java persistenceAs with the simple relationships, preserving complex relationships is the easiest with native Java persistence. The ShipBean definition is much simpler because the cabins vector can be stored directly, without converting it to a binary format. Using this strategy, we can opt to preserve the CabinPK types or Cabin bean handles for the aggregated Cabin beans. ![]() Copyright © 2001 O'Reilly & Associates. All rights reserved. |
|
|