12. EJB, Servlet and Other Middleware Environments
Contents
12.1. Introduction
12.2. EJB & the Value Object Paradigm
12.3. Implementing Relationships in EJB
12.4. EJB Transactions
12.1. Introduction
This section discusses various techniques and issues related to using VBSF in EJB, servlet and other middleware based environments. This document is based on the EJB 1.1 specification and the Servlet v2.2 specification. There might be some differences if using a newer or older version of these specifications.
12.2. EJB & the Value Object Paradigm
VBSF can be used in EJB environments using either session beans, entity beans, or
a combination. The most straightforward way of using VBSF with EJB is via session beans. In this
case the session beans contain the application logic and use VBSF to directly persist the business objects to the database.
VBSF can also be used with entity beans. In this case you must use a bean managed
persistence model. Container managed persistence is not supported. The main issue
encountered when using VBSF with entity beans is that VBSF cannot directly persist
entity beans. This is because the EJB specification prevents direct instantiation of beans
and VBSF directly instantiates objects when they are retrieved from the database.
The best way to use VBSF with EJB is to use a 'value object' paradigm. In this
approach, each entity bean wraps a value object, and it is the value object that VBSF persists.
Below is an example:
public class AccountBean implements EntityBean {
public AccountData myData; /* value object */
....
}
public class AccountData implements java.io.Serializable {
private long accountNo;
private String accountName;
...
}
In your beans ejbLoad, ejbStore
and related methods you use VBSF to persist the AccountData
object. This also has the added benefit that you can send the AccountData
object to the client directly for local manipulation. The client then sends back the object when all changes are done. This is much more
efficient than each attribute getter and setter resulting in a remote method invocation. Note that
value objects must implement the
java.io.Serializable interface if they are sent to the client.
Below is a sample entity bean without exception handling. It is not meant to be a complete bean since not all methods are implemented.
public class CustomerBean implements EntityBean {
CustomerData cust;
EntityContext context;
public java.lang.String getId() {
return cust.getId();
}
public java.lang.String getLastName() {
return cust.getFirstName();
}
public void setLastName(java.lang.String newLastName) {
cust.setLastName(newLastName);
}
public CustomerData getData() {
return cust;
}
public void setData(CustomerData cust) {
this.cust = cust;
}
public CustomerPK ejbCreate() throws CreateException {
Database db = getDatabase();
cust = (CustomerData)db.create(CustomerData.class);
db.insert(cust);
freeDatabase(db);
CustomerPK pk = new CustomerPK();
pk.id = cust.getID();
return pk;
}
public void ejbLoad() {
Database db = getDatabase();
CustomerPK key = (CustomerPK)context.getPrimaryKey();
cust = (CustomerData)db.lookup(CustomerData.class,key.id);
freeDatabase(db);
}
public void ejbStore() {
Database db = getDatabase();
db.update(cust);
freeDatabase(db);
}
public void ejbRemove() throws RemoveException {
Database db = getDatabase();
db.delete(cust);
freeDatabase(db);
}
public CustomerPK ejbFindByPrimaryKey(CustomerPK key) throws FinderException {
Database db = getDatabase();
cust = (CustomerData)db.lookup(CustomerData.class,key.id);
freeDatabase(db);
if (cust == null)
throw new ObjectNotFoundException();
return key;
}
public Collection ejbFindByLastNameLike(String value) throws FinderException {
Database db = getDatabase();
OQuery qry = new OQuery(CustomerData.class);
qry.add(value,"lastName",OQuery.LIKE);
CustomerData[] results = (CustomerData[])qry.execute(db);
freeDatabase(db);
return custArrayToPKCol(results);
}
public Database getDatabase() {
return Server.getServer("MyServer").getDatabase();
}
public freeDatabase(Database db) {
db.close();
}
private Collection custArrayToPKCol(CustomerData[] results) {
/* convert results to a Collection of CustomerPK objects */
}
}
/* CustomerBean Primary Key class */
public class CustomerPK implements Serializable {
public CustomerPK(){}
public long id;
}
/* This is the VBSF persistent value object */
public class CustomerData implements Serializable {
private long id;
private String lastName;
...
}
To ensure that all Database objects
are always returned to the pool in case of any errors, the freeDatabase(db) call in all
ejbXXXX methods in the CustomerBean should
always be performed within the finally
clause of a try/catch/finally block.
Note that the above example demonstrates just one way of doing things. You could also
chose to get a Database object from the pool in the
ejbActivate() method and free it in the
ejbPassivate() method to take advantage of the object
caching in the Database object, or do it some other way.
12.3. Implementing Relationships in EJB
In the example of the section above, if the bean has a list of PhoneNumber objects you
cannot simply call a CustomerData.getPhoneNumbers() if the
PhoneNumber class is a bean. Instead you have to look up the home of the referenced
object(s) using the appropriate context, and use one of its find methods with the
proper ID arguments to traverse the relationship. For example, inside the CustomerBean you would
have the following methods:
public AddressRemote getAddress() {
AddressHome home = (AddressHome)context.lookup(addressName);
return home.findByCustomer( new CustomerPK(cust.getId()) );
}
public Collection getPhoneNumbers() {
PhoneHome home = (PhoneHome)context.lookup(phoneName);
return home.findByCustomer( new CustomerPK(cust.getId()) );
}
You would also need to have the appropriate find() methods inside the
Address and Phone
bean classes. Again, this is just one way of doing things. There is probably many other ways.
Also note that if the PhoneNumber and Address objects are not beans, then the above
technique is not necessary and the referenced objects can simply be serialized directly to the client.
12.4. EJB Transactions
VBSF does not provide a way to synchronize VBSF transactions with the
application server transactions. However, there is no need to do this because when VBSF is used
in an EJB environment it relies on the proper transaction context being pre-established.
More specifically, VBSF takes advantage of the fact that the EJB server keeps track of the proper transaction context for each bean and supplies connections
to each bean in the proper context. As a result, VBSF obtains connections from the EJB server connection pool in the proper transaction context of the calling
bean and all database operations are automatically performed in this transaction
context.
For this to work properly you cannot explicitly issue database transaction demarcation commands using VBSF and instead use the EJB
UserTransaction object. In addition,
you must demarcate VBSF transactions in memory via the Database.startMemoryTrans()
and Database.commitMemory() methods in
order to keep the VBSF cache up-to-date with the database. Below is an example
of how this is done:
Database db = server.getDatabase();
UserTransaction tx = (UserTransaction)ctx.lookup("javax.transaction.UserTransaction");
tx.begin();
db.startMemoryTrans();
db.update(customer);
db.update(invoice);
tx.commit();
db.commitMemory();
Note that the use of the startMemoryTrans()
method is only required if a VBSF connection pool is used. To
prevent VBSF from internally demarcating database transactions you must also
check the 'Leave auto-commit enabled' and 'Disable automatic transactions' options
in the mapping tool DB Configuration General
Options panel.
12.5. Database Object Pooling in Servlets
When multiple servlets need to access Database
objects, then the init() method of a
servlet should not store the Server object in an attribute because each servlet will
have its own copy. Instead a named Server object should be shared across all
servlets. Below is an example:
public void init(ServletConfig cfg) {
Server.createServer("MyServer", "contactdemo.schema", 10,
20, 0, 0);
}
public void doGet(HttpServletRequest req, HttpServletResponse res) {
Database db = null;
try {
db = getDatabase();
//retrieve and persist objects with db
} catch (Exception e) {
....
} finally {
if (db != null)
this.freeDatabase(db);
}
}
public Database getDatabase() {
return Server.getServer("MyServer").getDatabase();
}
public freeDatabase(Database db) {
db.close();
}
In the init method a Server object named 'MyServer' will be created only
once, regardless of how may times createServer()
is called (by multiple servlet init() methods). In this manner, multiple servlets throughout your
application can share the same Server object.
12.6. Using the Application Server's Connection Pool
This section describes the settings that have to used to allow the use of VBSF in conjunction with
an application server's connection pool. The examples below use the WebLogic server as an example, but this technique
can be used with any EJB server. There are three basic approaches that can be
followed:
(I) This approach can be used if the application server provides a JTS driver
that wraps a JDBC driver. In this case you can have VBSF get the connection from the driver
directly. You may also define a VBSF connection pool in addition to the Server's
connection pool. Defining a VBSF connection pool avoids dedicating a connection
object for each Database object in the pool, which may defeat the purpose of
using a connection pool in the first place. The example below assumes that you are using the WebLogic's JTS JDBC
driver. Below are options that must be specified in the mapping tool 'DB
Configuration':
weblogic.jdbc.jts.Driver'connectionPoolID=myConnectionPooljdbc:weblogic:jts' and leave the username and password
blank in the Login section of the General
Options panel. The actual URL, username & password are specified at the time the
connection pool is defined in the Weblogic properties or Administration Console.(II) In this approach VBSF gets the connection from the server's JTS JDBC driver using a URL, username and password that you supply at runtime. To do this, follow the steps below:
Database.open(URL,username,password)
method and provide the URL, username and password as arguments. Make sure to call Database.close()
when you want VBSF to release the connection.(III) This approach is similar to the previous one, but in this case you can obtain the connection from the server's pool yourself and pass it to VBSF. To do this, follow the steps below:
Database.open(con) that accepts a connection object as
argument, and provide the connection object. Make sure to call Database.close()
when you want VBSF to release the connection.All approaches above are described in terms of using the mapping tool at
design time. However, you may still specify any of the above approaches at
runtime by setting the appropiate options in a runtime DBConfiguration
object that is passed to the Server
constructor.