Container-managed EJB persistence lets you separate the entity beans from the actual data stored in the underlying database. The XML deployment descriptors allow you to map the abstract persistence model to the underlying database schema. In CMP 2.0 persistence, you also can let the EJB container handle relationships between entity beans. WebLogic's EJB container supports associations that can be navigated in either direction (bidirectional), or perhaps restricted to one direction (unidirectional).
A CMR field (or a relationship field) is defined between local interfaces of entity beans. If a bean is involved in a relationship, the bean may be aware of EJB instances at the other end. Thus, if you have defined CMR fields for EJBs at both "ends" of the relationship, the association between the two entity beans can be navigated in either direction. Typically, unidirectional associations are modeled with remote entity beans i.e., when the entity bean does not reside in the same EJB JAR as the entity beans related to it. However, a bidirectional association may be defined only when both entity beans are packaged in the same EJB JAR. This means that you must use the same XML deployment descriptors to define their abstract persistence schema.
WebLogic's persistence framework supports one-to-one, one-to-many, and many-to-many associations. Suppose you've defined an association between two entity beans, A and B. The ejb-jar.xml descriptor file then will define the multiplicity and CMR fields for both ends (roles) of the EJB relationship. When you set the multiplicity for entity bean A to One, the type of the CMR field at B's end is implicitly the local interface of A. When you set the multiplicity for entity bean B to Many, the CMR field at A's end must be of type java.util.Collection (or java.util.Set if you want to ensure that duplicate EJB instances of B can never participate in the association).
|
Let's look at how you would define the underlying database schema for EJB relationships, and configure them using the weblogic-cmp-rdbms-jar.xml descriptor file.
11.3.1 One-to-One Association
In a one-to-one association between entity beans, you physically need to map the foreign key column from one EJB to the primary key column in the other EJB. For instance, if we were to store the login credentials for each employee separately, we could model a one-to-one relationship between the Employee and Login entity beans. The underlying database schema might look something like this:
create table tblLogin { id int(4) constraint pk_tblLogin primary key, username varchar2(15), password varchar2(15), lastlogin date };
In order to accommodate the association between Employee and Login EJBs, a foreign key column userid will be added to tblEmployee:
create table tblEmployee ( id number(4) constraint pk_tblEmployee primary key, lastname varchar2(30), ... userid number(4) constraint fk_userid references tblLogin(id) );
Example 11-7 shows how you would define the one-to-one association between the two entity EJBs.
Example 11-7. WebLogic-specific settings for a one-to-one association
LoginEJB myDS tblLogin username username Employee-Login Login-for-an-Employee userid id
Here, the relationship-role-map element maps the foreign key column tblEmployee.userid to the key column associated with the Login EJB's primary key field id. Remember, if the bean on the foreign-key side is mapped to multiple tables, you should use the foreign-key-table subelement to specify the name of the table that holds the foreign key. Similarly, if the bean on the primary-key side is mapped to multiple tables, you should use the primary-key-table subelement to specify the name of the table that holds the primary key.
To express EJB relationships using EJBGen, you need to annotate your code using the ejbgen:relation tag. The following defines a one-to-one relationship between the Employee EJB and the Login EJB:
/** * @ejbgen:entity prim-key- * ejb-name="Employee" * data-source-name = "MyDataSource" * table-name="tblEmployee" * @ejbgen:relation role-name="Employee-has-Login" fk-column="userid" * cmr-field="login" target-ejb="Login" * multiplicity="One" name="Employee-Login" */ public abstract class EmployeeEJB extends GenericEntityBean implements EntityBean { /** * @ejbgen:cmr-field * @ejbgen:local-method */ public abstract LoginLocal getLogin( ); /** * @ejbgen:local-method */ public abstract void setLogin(LoginLocal arg); // other methods }
Similarly, the Login EJB can be defined as follows:
/** * @ejbgen:entity prim-key- * ejb-name="Login" * data-source-name = "MyDataSource" * table-name="tblLogin" * @ejbgen:relation role-name="Login-has-Employee" cmr-field="employee" * target-ejb="Employee" multiplicity="One" name="Employee-Login" */ public abstract class LoginEJB extends GenericEntityBean implements EntityBean { /** * @ejbgen:cmr-field * @ejbgen:local-method */ public abstract EmployeeLocal getEmployee( ); /** * @ejbgen:local-method */ public abstract void setEmployee(EmployeeLocal arg); // other methods }
Note that both classes have the multiplicity attributes set to one, and that the only difference between the two is that the Employee EJB contains the foreign-key definition.
11.3.2 One-to-Many Association
In a one-to-many association between entity beans, you physically need to map the foreign key column from one EJB to the primary key column of the other. The associating foreign key always is taken from the Many side of the EJB relationship WebLogic does not permit you to create such an association using a third, intermediate, table. Let's consider a one-to-many association between a Department and Employee EJB, mapping a foreign key column deptid in tblEmployee (Many) to the primary key column id in tblDepartment (One).
In this relationship, each Department EJB owns a collection-based relationship field that holds references to multiple Employee EJBs, and each Employee EJB in turn holds a reference back to the aggregating Department EJB. We need to ensure that all Employee EJBs associated with the each Department EJB instance are unique. Thus, the Department EJB exposes a CMR field whose type is the collection interface java.util.Set.
The ejb-relation element in the ejb-jar.xml descriptor file lets you configure this association between the two entity EJBs. Example 11-8 shows how to define the one-to-many association between Department and Employee EJBs.
Example 11-8. The one-to-many bidirectional association
EmployeeEJB com.oreilly.ejbs.EmployeeHome ... DepartmentEJB com.oreilly.ejbs.DepartmentHome ... Department-Employee Department-has-many-Employees One DepartmentEJB employees java.util.Set Employee-belongs-to-Department Many EmployeeEJB department
The ejb-relationship-role element lets you define metadata for each end of the association. At the Department end, we have set the multiplicity to One and declared a CMR field employees, whose type is java.util.Set. At the Employee end, we have set the multiplicity to Many and defined a CMR field department, whose type is implicitly com.oreilly.ejbs.Department.
The weblogic-rdbms-relation element in the weblogic-cmp-rdbms-jar.xml descriptor file lets you specify the table schema corresponding to all EJB relationships defined in the ejb-jar.xml descriptor. In this case, the foreign key column tblEmployee.deptid (on the "many" side) is associated with the primary key column tblDepartment.id (on the "one" side):
Department-Employee Employee-belongs-to-Department deptid id
Here are the EJBGen-annotated EJBs that can create these mapping files. The Department EJB is quite straightforward. Note how the CMR field for the employees is defined in terms of a java.util.Set:
/** * @ejbgen:entity prim-key- * ejb-name = "Department" * data-source-name = "MyDataSource" * table-name = "tblDepartment" * * @ejbgen:relation role-name="Department-has-Employees" cmr-field="employees" * target-ejb="Employee" multiplicity="One" * name="Department-Employee" */ abstract public class DepartmentEJB extends GenericEntityBean implements EntityBean { /** * @ejbgen:cmr-field * @ejbgen:local-method */ public abstract java.util.Set getEmployees( ); /** * @ejbgen:local-method */ public abstract void setEmployees(java.util.Set arg); // other methods }
The Employee EJB source code will look similar to this:
/** * @ejbgen:entity prim-key- * ejb-name="Employee" * data-source-name = "MyDataSource" * table-name="tblEmployee" * * @ejbgen:relation role-name="Employees-have-Department" * fk-column="Department_DepartmentID" cmr-field="department" * target-ejb="Department" multiplicity="Many" * name="Department-Employee" */ public abstract class EmployeeEJB extends GenericEntityBean implements EntityBean { /** * @ejbgen:cmr-field * @ejbgen:local-method */ public abstract DepartmentLocal getDepartment( ); /** * @ejbgen:local-method */ public abstract void setDepartment(DepartmentLocal arg); // other methods }
Note how the CMR field is defined in terms of the local interface for the Department EJB.
11.3.3 Many-to-Many Association
In order to model a many-to-many relationship between EJBs, you physically need to map the association to a join table. In a join table, each row consists of two foreign keys, in which each foreign key maps to the primary keys of the entities involved in the relationship. For instance, suppose we need to model a concept of roles i.e., each employee can assume multiple roles while she's working for company XYZ. Of course, we also may have multiple employees that can assume the same role at any given time. The SQL table corresponding to the Role entity EJB could be similar to this:
create table tblRole { id int(2) constraint pk_tblRole primary key, name varchar2(15), description varchar2(100), };
In this scenario, we need to be able to model a many-to-many association between the two entity EJBs, Employee and Role.
An easy way to implement this relationship is through a join table tblRoleMembers, in which each row of the table holds two foreign keys, one that maps to tblEmployee and another that maps to tblRole.
create table tblRoleMembers ( roleid number(4) constraint fk_roleid references tblRole(id), empid number(2) constraint fk_empid references tblEmployee(id) );
Example 11-9 describes the EJB relationship in terms of the join table we just defined.
Example 11-9. WebLogic-specific settings for a many-to-many association
Employee-Role tblRoleMembers Roles-have-Employees roleid id Employees-have-Roles empid id
Here, we see that the table-name subelement specifies the name of the join table. For each role involved in the EJB relationship, we define a weblogic-relationship-role element that maps a foreign key column to the primary key column of the EJB that occupies that end of the association. If this many-to-many association were bidirectional, we need to define CMR fields at both ends of the EJB relationship of type java.util.Collection, or of type java.util.Set if duplicate EJB instances must not be associated with the other EJB instance. For example, here are the EJBGen annotations for the Employee EJB:
/** * @ejbgen:entity prim-key- * ejb-name="Employee" * data-source-name = "MyDataSource" * table-name="tblEmployee" * @ejbgen:relation role-name="Employees-have-Roles" * fk-column="roleid" joint-table="tblRoleMembers " * cmr-field="roles" target-ejb="Role" multiplicity="Many" * name="Employee-Role" */ public abstract class Employee extends GenericEntityBean implements EntityBean /** * @ejbgen:cmr-field * @ejbgen:local-method */ public abstract java.util.Set getRoles( ); /** * @ejbgen:local-method */ public abstract void setRoles(java.util.Set arg); // other methods }
The Role EJB is similar:
/** * @ejbgen:entity prim-key- * ejb-name = "Role" * data-source-name = "MyDataSource" * table-name = "tblRole" * @ejbgen:relation role-name="Roles-have-Employees" * fk-column="empid" joint-table="tblRoleMembers " * cmr-field="employee" target-ejb="Employee" multiplicity="Many" * name="Employee-Role" */ abstract public class Role extends GenericEntityBean implements EntityBean { /** * @ejbgen:cmr-field * @ejbgen:local-method */ public abstract java.util.Set getEmployee( ); /** * @ejbgen:local-method */ public abstract void setEmployee(java.util.Set arg); // other methods }
11.3.4 Cascade Delete
Usually, when an entity EJB instance is removed, the EJB container automatically purges its associations with other entity EJB instances. Sometimes, you want to ensure that all EJB instances that are related to an entity EJB instance also are deleted when the entity EJB is removed. That is, when an entity EJB is removed, the delete operation should be cascaded to all related entity EJB instances. The ejb-jar.xml descriptor file allows you to specify a cascade-delete element on one of the roles involved in the EJB relationship. For instance, given an association between two entity beans, A and B, if you specify cascade-delete at B's end of the EJB relationship, all related instances of B are deleted when an instance of A is removed.
However, cascade deletes may be enabled only for one-to-one or one-to-many associations. So, if A's end of an EJB relationship has multiplicity One, you may then specify cascade-delete at B's end of the association. Thus, the entity bean that triggers the cascade delete must have a multiplicity of One in the EJB relationship. In addition, the cascade-delete element affects only the EJB relationship in which it is defined, and has no effect on other EJB relationships. For instance, if you've enabled cascade delete on the association between EJBs A and B, but not on the association between EJBs A and C, only related instances of B will be removed when an instance of A is removed. All related instances of C still will remain; only their associations with the instance of A will be deleted.
Consider the situation in which all employees are fired when a Department is scrapped. We would like to automatically remove all related Employee EJB instances when a Department EJB instance is removed. The following XML fragment shows how you can configure the EJB container to automatically cascade a delete operation on any Department EJB instance:
Department-Employee Department-has-many-Employees One ... Employee-belongs-to-Department Many ...
WebLogic can improve the performance of cascade deletes by making the EJB container rely on the underlying database support for cascade deletes. When you specify a db-cascade-delete element in the weblogic-cmp-rdbms-jar.xml descriptor file, WebLogic's EJB container will implicitly use the cascade delete features of the underlying DBMS. By default, this feature is disabled, and the EJB container issues SQL DELETE commands when it needs to remove related EJB instances.
The following XML stanza shows how to configure database-driven cascade deletes for the Department EJB:
Department-Employee Employee-belongs-to-Department deptid id
You also need to modify the table schema in order to use database cascade deletes. In this instance, we have modified the foreign key constraint in tblEmployee so that any related rows in tblEmployee are deleted automatically when the parent row in tblDepartment is deleted:
create table tblEmployee ( id number(4) constraint pk_tblEmployee primary key, ... deptid number(2) constraint fk_deptid references tblDepartment(id) on delete cascade ); create table tblDepartment { id number(2) constraint pk_tblDepartment primary key, name varchar2(40), };
Typically you will need to enforce cascade deletes when the association demands that the EJB instance "own" all related EJB instances i.e., the lifetime of all related EJBs is bound by the lifetime of the aggregating bean. If the related EJB instances can be shared by multiple EJB instances or can survive without necessarily being associated with an EJB, you should not enable cascade deletes.
Use cascade deletes with great caution, because a single EJB remove could trigger a chain reaction. If you've configured your EJBs so that a delete on EJB A cascades to B, and a delete on EJB B cascades to C, then removing an instance of A will automatically remove all related instances of B and instances of C that are linked to those instances of B. The deletes will cascade through the associations to related EJB instances.
11.3.4.1 Avoiding deadlocks
Transactions that use EJB instances configured for exclusive concurrency sometimes can create a deadlock situation. For instance, in order to complete a cascade delete operation, a transaction may need access to an EJB instance locked by another transaction. A typical deadlock situation arises when a transaction holds a lock on an EJB that is required by another transaction, and vice versa. In fact, two transactions, T1 and T2, that need to operate upon (say) two EJB instances, E1 and E2, will be deadlocked if both of the following conditions hold simultaneously:
WebLogic lets you avoid this deadlock situation by setting a lock-order for the EJBs involved. In this case, whenever the two EJBs are involved in the same transaction, they are locked according to their lock-order value.
So, if you set the lock-order for E1 to 1, and the lock-order for E2 to 2, any transaction in which both EJB instances are involved locks E1 before E2. In this way, the EJB container can reduce the chance of deadlocks between transactions.
|
The following portion from the weblogic-cmp-rdbms-jar.xml descriptor shows how to set the lock order for entity EJBs:
DepartmentEJB testds 1 EmployeeEJB testds 2
Assuming that both the Department and Employee EJBs are configured for exclusive concurrency, this means that transactions, which require a delete operation on a Department EJB instance to cascade to all related Employee EJB instances, will not deadlock with each other.
11.3.5 Caching EJB Relationships
In certain situations, it is more optimal to load related entity beans into the cache when a query method is executed so that later you can avoid multiple queries to retrieve related EJBs. The relationship-caching element in the weblogic-cmp-rdbms-jar.xml descriptor file allows the EJB container to eagerly load related EJB instances when a query method is executed. The following XML fragment illustrates how we can configure caching for EJB relationships:
EmployeeEJB testds ... cacheDept department noHistory ...
Here, the caching-element tag instructs WebLogic to automatically load the related Department EJB when an Employee EJB is found. The caching-name element identifies the caching rule that we've defined. This element is important because it means that subsequent query methods can determine which of the caching rules WebLogic should apply. The cmr-field tag identifies the one-to-many relationship between the two EJBs, in this case from the Employee end.
The group-name element indicates that we would like to initialize only a subset of the CMP fields in the Department EJB when it's loaded. In this example, we are referring to the field group defined earlier in Example 11-7. If you don't specify the name of a field group in the caching-element tag, WebLogic uses the default field group that automatically initializes all CMP fields of the Department EJB when it is loaded. Now that you've set up how WebLogic should cache the EJB relationship, you can define a find method that utilizes this caching feature:
EmployeeEJB ... findByName java.lang.String java.lang.String cacheDept 10 True True ...
The caching-name subelement within the weblogic-query tag identifies the name of the relationship cache defined earlier. When a client invokes the findByName( ) query method on the Employee EJB home object, WebLogic will load the related Department EJB into the cache for all Employee EJBs returned by the find method. This load is quite optimal, as it is implemented under the hood using simple SQL join queries. In addition, it will initialize only the specified subset of CMP fields in the related Department EJB.
WebLogic also allows you to specify multiple levels of relationship caching. By nesting one caching-element tag within another, you can instruct WebLogic to load more than one level of related beans. The following XML stanza shows how to configure WebLogic to load the related Department EJB, and in turn all related Employee EJBs for that Department EJB:
cacheDept department deptgroup employees empgroup
Here, department is a CMR field in the Employee EJB, and employees is a CMR field defined in the Department EJB. In general, too many levels of nesting for the cache-element tags will degrade the performance of your application. When you enable caching for EJB relationships, you also should be aware of certain key issues:
ORACLE
WebLogic maps relationship caching to outer joins in the underlying database. It needs the database-type element to determine the exact syntax for the join queries.
Clearly, WebLogic provides powerful support for caching EJB relationships, which allows you to optimize the performance of your EJB query methods.
Introduction
Web Applications
Managing the Web Server
Using JNDI and RMI
JDBC
Transactions
J2EE Connectors
JMS
JavaMail
Using EJBs
Using CMP and EJB QL
Packaging and Deployment
Managing Domains
Clustering
Performance, Monitoring, and Tuning
SSL
Security
XML
Web Services
JMX
Logging and Internationalization
SNMP