Details on the Container’s default CMP engine

Isn’t the use of Java Reflection a performance hit?

Inprise EJB Container uses reflection rather than generated code to implement container managed persistence. Evaluators have wondered that the reflection approach is much slower than the use of pre generated code.

We too had concerns about its performance, but as long as you cache the lookups, reflection is very_ fast. What is slow is doing things like like looking up a method on a class, or looking up an instance member. We do all these lookups once, and then cache the results. Subsequently, we are simply doing the assignments of CMP members, or the invocation of bean methods using reflection, and this is very fast.

Also, bear in mind that by using reflection, we can do optimizations such as "tuned writes" and "field-level diff detection" which are very hard to do with generated code.

How is CMP metadata provided to the Container?

The EJB spec relies on the “deployer” providing metadata to the CMP engine in a Container specific way. With the Inprise Container most of this metadata is captured in the XML deployment descriptor, specifically the vendor specific deployment descriptor. Please refer to the DTD of this deployment descriptor for the detailed syntax of finder and OR mapping metadata.

Do read only transactions incur redundant database roundtrips?

With entities marked as Option A transaction commit mode, and putting this together with tuned updates (whereby the store is avoided for container-managed persistence entities if the container determines that the CMP fields were not modified), read-only transactions can be performed completely in the container, without touching the database. The load is avoided because the entity is “ready” in the container (as per Option A), and the store is avoided because the entity CMP fields have not changed (as per tuned updates). Should be fast!

What information needs to be provided for Container Managed finders?

Let's take a simple example: findAccountsLargerThan(int). This finder method attempts to find all bank accounts with a balance larger than the specified value. To support this method, the "where" clause to be used in the "select" statement needs to be specified in the deployment descriptor .

The value of the where clause in this example is "$1 > balance", which translates to "the value of the first argument (e.g., the only argument to findAccountsLargerThan(int)) is greater than the value of the balance column".

The default CMP implementation supports this finder method by constructing the complete SQL "select" statement from this provided information:

        select * from Accounts where ? > balance
and then substituting "?" with the int parameter.

We then do the work of converting the result set into either an Enumeration or Collection of primary keys, as required by the EJB specification.

You can inspect the various SQL statements that our CMP implementation is constructing by enabling the "EJBDebug" flag on the container. It prints out exactly what it is doing.

We avoid code generating for the CMP support. Other products have instead used code generation to solve this problem. This has some serious limitations, but it also seems to work.

(One of the limitations is that it makes it very hard for them to support "tuned update" feature, since this requires n! different update statements, where n is the number of container-managed fields.)

What is the complete syntax of the WHERE clause?

The syntax of a where clause is fairly complex.

First, the "where" literal is optional, and will be automatically supplied if the where clause is not the empty string. E.g. "a = b" gets converted to "where a = b" but "" is not modified. This makes it easy to specify findAll, using the empty string, meaning "select [values] from [table]".

We do parameter substitution, using the standard SQL substitution prefix colon. These parameters correspond to the names of the parameters in the original finder specification in the XML descriptor. A simple example is:

  <finder>
    <method-signature>findAccountsLargerThan(float balance)</method-signature>
    <where-clause>balance > :balance</where-clause>
  </finder>
Here, we will compose a SQL select statement using the where clause:
        balance > ?
and we will substitute the value of the parameter "balance" for the ? in the where clause.

Compound parameters are also supported, using the standard dot syntax. For example:

  <finder>
     <method-signature>findByCity(Address address)</method-signature>
     <where-clause>city = :address.city AND state = :address.state</where-clause>
   </finder>
In this where clause, we are using the city and state fields of the address compound object to select particular records. The underlying Address object could either have JavaBeans style getter methods corresponding to the attributes city and state, or it could alternatively have public fields corresponding to the attributes.

Entity beans can be used as parameters in the finder method. If not used as a compound type (i.e. you want to tell the cmp engine to use a field from the passed refrence for the sql query), their primary key will be substituted in the where clause. For example, if we have a set of OrderItems associated with an Order object, we could have the following finder:

        java.util.Collection OrderItemHome.findByOrder(Order order);
which returns all OrderItems associated with a particular Order. The where clause for this would be:
        <finder>
          <method-signature>findByOrder(Order order)</method-signature>
          <where-clause>order_id = :order[ejb/orders]</where-clause>
        </finder>
In this where clause, the primary key of the Order object is substituted for the ":order[ejb/orders]" string. The string between the brackets must be the ejb-ref corresponding to the home of the parameter type. In this example, the ejb/orders is an ejb-ref (actually it must be an ejb-link) pointing to the OrderHome.

Alternatively, one can use the EJBObject as a compound type, and access its method in the finder, as in:

        order_id = :order.orderId
this will call the getOrderId() method on the order EJBObject, and use the result in the selection criterion.

Verifier did not pick up my mistake in finders definition

Scenario:

My finder method in EmployeeHome :
  Employee findByNetworkID(String networkID) throws
java.rmi.RemoteException, javax.ejb.FinderException;
My XML DD:
  <finder>
    <method-signature> findByNetworkID (String networkID)</method-
signature>
    <where-clause> network_id = :network_id</where-clause>
  </finder>
Note *network_id* in my WHERE clause does not match *networkID* in DD findByNetworkID definition which however does match *networkID* in my home interface.

Explanation

We do consider this a bug. Currently, the verifier does not check the entity bean mapping information, including any typos in the finder's where specification.

The GUI does quite a bit in terms of holding your hand when specifying the OR mapping. However, errors in your SQL will go undetected until you actually hit the SQL parser in the DB.

Is there a simple CM finder method to return all* DB rows. I.e. Is there any way to have the container generate a simple findAll method?

Simply leave the where clause empty.

I have EntityB which inherits from EntityA. Can I have TableB containing fields added to class EntityB, and TableA containing fields of EntityA ?

No, this feature is not supported.

We do support the following:

public class PersonBean implements javax.ejb.EntityBean {
  // this field is container managed...
  public String gender;

    ... bean methods and getter/setter methods deleted for brevity

}

public class CustomerBean extends PersonBean {

  // these fields are container managed...
  public String name;
  public String ID;

  public String address;


   ... bean methods and getter/setter methods deleted for brevity

}
It is possible to have this structure when you store all of the CMP fields for both classes in one table.

If a JDBC2 driver provides java.sql.Array support, then can CMP provide support for 1-to-N dependent objects?

TBD.

How do I specify relationships between entities ?

When using references among entity beans, you simply (and naturally) have a field in the entity that is of the type of Remote interface of the referred to entity. Then in the deployment descriptor you need to map the field-name to the target entity by using an ejb-link. That's it! The information provided in the XML DD is enough for the CMP engine to locate the associated entity. See the EJB pigs example for the details.

A typical development model will be like this:
Code your entities to refer to each other by declaring cmp-fields that are of type Remote. Pull up the bean in the DD Editor and in the "EJB References" tab specify an ejb-link to all entities associated with the bean you are editing. Then go to the "Persistence" tab and for the cmp-fields that are references, hook up the appropriate ejb-link.

Why do you enforce relationships between entities to have an ejb-link relationship at the XML level?

With ejb-link constraints it is possible for the Container to maintain better semantics between related entities. When EntityA refers to another EntityB and they have an ejb-link between them, the Container can ensure that when A asks for B, preference is given to return a collocated bean B instance. Also there are issues with regard to garbage collection when unloading EJB Jars that have related entities.

How does the Container handle entity bean fields that are references to other entities?

In RDBMSes associations take the form of foreign keys. It is possible for any of your CMP fields to correspond to a foreign key field in the corresponding table. At the EJB code level these foreign key CMP field can be surfaced as object references.

As an example, lets say you have an address table, and it has a reference to the country table:

create table address (
    addr_id             number(10),
    addr_street1        varchar2(40),
    addr_street2        varchar(40),
    addr_city           varchar(30),
    addr_state          varchar(20),
    addr_zip            varchar(10),
    addr_co_id          number(4)       * foreign key *
);

create table country (
    co_id               number(4),
    co_name             varchar2(50),
    co_exchange         number(8, 2),
    co_currency         varchar2(10)
);

You map them to entities thus:

public class Address extends EntityBean {
  public int id;
  public String street1;
  public String street1;
  public String city;
  public String state;
  public String zip;
  public Country country; // this is a direct pointer to the Country
  // etc.
}

public class Country extends EntityBean {
  public int id;
  public String name;
  public int exchange;
  public String currency;
  // etc.
}
Note that the reference is an EJBObject reference, not a direct Java reference to the implementation Bean.

Then, using the deployment descriptor, you can hook up the reference Address.country to the JNDI name of CountryHome. A further explaination of this is provided in the pigs example.

The container optimizes this cross-entity reference to be pretty much as fast as just storing the value of the foreign key. Two important differences are:

1) You don't have to call CountryHome.findByPrimaryKey(addresss.country) to get the Country object corresponding to the country id.
2) The state of the Country entity is only loaded when you actually use it. E.g., loading in an Address object does not actually load in a Country object. Think of the Country field in Address as a "lazy" reference, which only load in its corresponding state when the object is used. (This lazy behavior actually comes free of charge via the EJB model.) The life-cycle of Address.country is decoupled from the life-cycle of the Address bean instance itself simply by virtue of the EJB model. Address.country is a normal entity EJBObject reference, and thus the state of Address.country is only loaded when (if) it is used. Likewise, the state of AddressBean.country will be controlled as any other EJBObject is.

Also, note that we changed the CMP field names to be more Java friendly (e.g., the SQL column "address.addr_city" maps to the Java field "Accress.city"). This too is achieved via the deployment descriptor, e.g.:

            <env-entry>
                <env-entry-name>ejb.cmp.jdbc.column:city</env-entry-name>
                <env-entry-type>String</env-entry-type>
                <env-entry-value>addr_city</env-entry-value>
            </env-entry>

Java programmers certainly prefer "normal" field names.