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.
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.)
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.
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.
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.
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.
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.
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.