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.5. Database Object Pooling in Servlets

12.6. Using the Application Server's Connection Pool


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':

  1. Specify the JDBC driver in the the Database panel as 'weblogic.jdbc.jts.Driver'
  2. Put the following entry in the Properties text box of the Database panel:

    connectionPoolID=myConnectionPool

    where myConnectionPool is the name of the connection pool you defined in the WebLogic server.
  3. Specify the Login URL as 'jdbc: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.
  4. In the Transaction section of the in the General Options panel check  the 'Leave auto-commit enabled' and 'Disable automatic transactions' options. 

(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:

  1. In the mapping tool DB Configuration General Options panel:
    A. In the Login section check the 'Client logins' option and leave the login URL, username & password blank.
    B. In the Transaction section check the 'Leave auto-commit enabled' and 'Disable automatic transactions' options.
  2. In the Connection Pool section of the DB Configuration Advanced Options panel set the # of Initial Connections to 0 to disable the VBSF connection pool.
  3. At runtime use the 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:

  1. In the mapping tool DB Configuration General Options panel:
    A. In the Properties section check the 'Client can supply connection' option.
    B. In the Login section leave the login URL, username & password blank.
    C. In the Transaction section check the 'Leave auto-commit enabled' and 'Disable automatic transactions' options.
  2. In the Connection Pool section of the DB Configuration Advanced Options panel set the # of Initial Connections to 0 to disable the VBSF connection pool.
  3. At runtime use the 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.

 

Return to Table of Contents