kapok

垃圾桶,嘿嘿,我藏的这么深你们还能找到啊,真牛!

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  455 随笔 :: 0 文章 :: 76 评论 :: 0 Trackbacks

http://www.onjava.com/pub/a/onjava/2002/04/10/j2eedesign.html

J2EE Design Patterns: CMP-to-BMP Pattern

by Dion Almaer
04/10/2002

EJB 2.0 finally gives us a Container-Managed Persistence (CMP) model to work with. Whenever possible, I like to use CMP beans in my EJB projects; however, for the odd thing, I need to change my entity to be bean-managed. I use the following pattern to allow me to go from a CMP model to a Bean-Managed Persistence (BMP) model in a clean way.

In this article, we will discuss:

  1. CMP 2.0: What has changed?
  2. The Inventory EJB application.
  3. Developing a CMP bean.
  4. Migrating the bean to use BMP.

CMP 2.0: What Has Changed?

When EJB first came out, it got a real hammering over CMP. When EJB 2.0 came out, along came a revamped CMP model, giving us features we really wanted like relationships and standardizing on a query language.

When it comes to writing a CMP bean, we do so in a very different way, compared to doing so with EJB 1.1. We create an abstract class, and instead of creating public fields for the managed variables, we create abstract getters and setters (like JavaBeans). This allows the vendor persistence manager to implement the accessors to the data in their own way.

This helps them put in logic that says things like:

  • Since they didn't call any set methods, don't do anything in ejbStore().
  • They only changed one field, so let's just SET that field in the UPDATE query.
  • Let's lazy-load some of the data, so that we only get it when the user asks for it via the get method.

Side Question: Why do we have to create abstract methods in this abstract class? Why can't the persistence manager just create the methods in the subclass?

Answer: We need to access these methods in the abstract class. For example, in ejbCreate(...), we go through the passed-in arguments and set*() them.

Inventory EJB Application

To show this entity pattern, we will look at a simple application that models an inventory system. The parts of the application are:

  • Inventory Entity Bean: This is our focus. It will map to a database table inventory, which holds the item name (primary key), price, and the number of items in stock.
  • Pricer Stateless Session Bean: This bean uses the inventory entity bean to get the price(s) of items. It uses local interfaces to access the entities (new in EJB 2.0)
  • Pricer Client: This command-line application runs methods on the session to test that this is all working.

One key point is that when we migrate the inventory entity bean from CMP to a BMP implementation, nothing else will change.

Let's look at implementing the inventory entity bean using CMP.

Developing a CMP Bean

The inventory CMP requires us to create an abstract class (compliant to the Entity spec), and XML descriptor files telling the mapping framework what to map. Figure 1 shows what these items look like.

Diagram.
Figure 1.

Abstract Entity Bean

The abstract entity has the following properties:

Implements javax.ejb.EntityBean.

abstract public class InventoryBean implements EntityBean {.

Implements methods declared in EntityBean interface:

public void setEntityContext(EntityContext context) {
   ctx = context;
}

public void unsetEntityContext() {
   ctx = null;
}

public void ejbActivate() {}

public void ejbPassivate() {}

public void ejbRemove() throws RemoveException {}

public void ejbStore() {}

public void ejbLoad() {}

Implements an ejbCreate() and an ejbPostCreate() method that corresponds to the create() method in the home interface (uses abstract set methods to set all incoming params):

public String ejbCreate(String item, float price, int stock) throws CreateException {
   setItem(item);
   setPrice(price);
   setStock(stock);
   return null;
}

public void ejbPostCreate(String item, float price, int stock) throws CreateException {}

Implements the abstract get and set methods:

public abstract String getItem();
public abstract void setItem(String item);

public abstract float getPrice();
public abstract void setPrice(float price);

public abstract int getStock();
public abstract void setStock(int stock);

Implements a helper method to get access to the entity context (this will be used in the BMP bean later):

public EntityContext getEntityContext() {
   return ctx;
}

EJB Deployment Descriptors

We have created a CMP-compliant entity bean class, and now it's time to create the deployment descriptors. First we create the "standard" ejb-jar.xml, and then we need to configure the vendor-specific information. We will show the WebLogic 6.1 deployment descriptors because we have to pick one.

Standard ejb-jar.xml

Configure basic entity information:

We configure the class names, using the "local" variants, which means that these are local entity beans. We tell the container that this entity is CMP via the persistence-type XML tag.

<entity>
  <ejb-name>InventoryBean</ejb-name>
  <local-home>InventoryHome</local-home>
  <local>Inventory>/local<
  <ejb-class>InventoryBean</ejb-class>
  <persistence-type>Container</persistence-type>
  <prim-key-class>java.lang.String</prim-key-class>
  <reentrant>False</reentrant>

Next, we tell the container which fields are to be managed, and that the item is the primary key:

  <cmp-field>
    <field-name>item</field-name>
  </cmp-field>
  <cmp-field>
    <field-name>price</field-name>
  </cmp-field>
  <cmp-field>
    <field-name>stock</field-name>
  </cmp-field>
  <primkey-field>item</primkey-field>

Finally, we configure the findAllPrices() method using EJB-QL:

  <query>
    <query-method>
      <method-name>findAllPrices</method-name>
      <method-params>
      </method-params>
    </query-method>
    <ejb-ql><![CDATA[WHERE 1=1]]></ejb-ql>
  </query>


Vendor Specific: weblogic-ejb-jar.xml:

We define the vendor-specific information first in the weblogic-ejb-jar.xml. Here we tell the server where to place the home stub in JNDI, and where to look for the CMP mapping.



Persistence Mapping Info:

<persistence-type>
  <type-identifier>WebLogic_CMP_RDBMS</type-identifier>
  <type-version>6.0</type-version>
  <type-storage>META-INF/weblogic-cmp-rdbms-jar.xml</type-storage>
</persistence-type>

Local JNDI Name:

<local-jndi-name>InventoryHome</local-jndi-name>

Vendor Specific: weblogic-cmp-rdbms-jar.xml

Datasource name:

(This datasource has to be created in the server configuration!)

<data-source-name>InventoryDB</data-source-name>

Table name:

<table-name>inventory</table-name>

Field mapping:

<field-map>
  <cmp-field>stock</cmp-field>
  <dbms-column>stock</dbms-column>
</field-map>
<field-map>
  <cmp-field>item</cmp-field>
  <dbms-column>item</dbms-column>
</field-map>
<field-map>
  <cmp-field>price</cmp-field>
  <dbms-column>price</dbms-column>
</field-map>

Now we have everything that we need. The container will take the abstract class and the deployment information to get everything working.

For some reason, we may want to take this CMP bean and migrate it to BMP. A few reasons why we would want to do this are:

  • Portability: Currently, it only works on WebLogic 6.1, and we don't want to learn all of the other tools. This is common if we want to sell this DB component -- it should run on any server.
  • Performance: If we wanted to fine tune the SQL that we use (e.g. use stored procedures, join across tables, etc.)
  • Alternate data sources: If we wanted to access a different data source, instead of a RDBMS.

Migrating the Bean to Use BMP

Here is where the pattern kicks in. We can migrate to BMP by "becoming the persistence manager." We saw that the persistence manager extends the abstract class that we created, where it implements the abstract methods and "does its stuff." We can do the same thing! The design will end up looking like Figure 2:

Diagram.
Figure 2. BMP design.

BMP Entity Bean

Let's walk through the bean class that will become the BMP bean.

Extend the abstract CMP bean class:

public class InventoryBeanBMP extends InventoryBean {

Create fields that hold the mapping:

public String item;
public float  price;
public int    stock;

Override the ejb*() methods:

To override the EJB methods, we need to remember what our responsibility is for each method in the BMP world.

EJB Method Responsibility
ejbLoad() Load the data from the database (SELECT)
ejbStore() Update the data back to the database (UPDATE)
ejbRemove() Delete the data from the database (DELETE)
ejbCreate() Insert a new row (INSERT)
ejbFindByPrimaryKey(primary key) Make sure the primary key exists
ejbFindAllPrices() Return a collection of primary keys for each item

Most of the methods look the same, so let's look at one of them (you can see the rest in the code download):

public String ejbCreate(String item, float price, int stock) throws CreateException {
   // insert row into database
   this.item  = item;
   this.price = price;
   this.stock = stock;

   // Insert database record
   try {
     Connection connection = getConnection();
     PreparedStatement statement = connection.prepareStatement
      ("INSERT INTO inventory (item, price, stock) VALUES (?, ?, ?)");
     statement.setString(1, item);
     statement.setFloat(2, price);
     statement.setInt(3, stock);
     if (statement.executeUpdate() != 1) {
      statement.close();
      connection.close();
      throw new CreateException("Could not create: " + item);
     }
     statement.close();
     connection.close();
     return item;
   }
   catch(SQLException e) {
     throw new EJBException("Could not create: " + item, e);
   }
}

Implement get and set methods:

public String getItem() {
   return this.item;
}
public void setItem(String item) {
   this.item = item;
}

public float getPrice() {
   return this.price;
}
public void setPrice(float price) {
   this.price = price;
}

public int getStock() {
   return this.stock;
}
public void setStock(int stock) {
   this.stock = stock;
}

Helper function to get a JDBC connection:

private Connection getConnection() throws SQLException {
   DataSource ds = null;

   try {
      Context ctx = new InitialContext();
      ds = (DataSource) ctx.lookup ("java:comp/env/jdbc/InventoryDB") ;
   } catch (NamingException exp) {
      exp.printStackTrace() ;
   }

   return (ds == null) ? null : ds.getConnection();
}

EJB Deployment Descriptors:

Standard ejb-jar.xml

Change settings from CMP:

The class points to the BMP class, not the abstract class, and we let the container know that we are managing the persistence:

<ejb-class>InventoryBeanBMP</ejb-class>
<persistence-type>Bean</persistence-type>

Add reference to JDBC data source: (used in getConnection() method above)

<resource-ref>
  <res-ref-name>jdbc/InventoryDB</res-ref-name>
  <res-type>javax.sql.DataSource</res-type>
  <res-auth>Container</res-auth>
</resource-ref>

Vendor Specific: weblogic-ejb-jar.xml

We don't have to do much with the vendor-specific files, as we did in the CMP version. We just need to point to the local JNDI name (same as before), and point the datasource to the real location, which is configured in the app server.

<reference-descriptor>
  <resource-description>
    <res-ref-name>jdbc/InventoryDB</res-ref-name>
    <jndi-name>inventoryDB</jndi-name>
  </resource-description>
</reference-descriptor>

And there we have it. We have reused the abstract class, overridden it to do the BMP implementation that we need, and then tweaked the deployment descriptors.

Conclusion

We have investigated a pattern that allows us to switch between CMP and BMP implementations in a nice clean manner. Our BMP implementation simply extends the CMP abstract bean class, and does the work! Notice that we could put in some logic in the get/set methods to make sure that we didn't do anything in ejbStore(), or did a tuned query. I still recommend that CMP beans be used when possible, and BMP used as a last resort.

Notes on Running the Sample Application

You can download the code for this sample pattern. There are two code sets, BMP and CMP (under directories of that name). A sample build script is provided (build.cmd) for compiling and deploying to BEA WebLogic 6.1. You may need to change a few directory paths in that script to get it working. To test the application, run the client via javac PricerClient. Check out the README.txt file for more information (setting up database table, etc.).

Dion Almaer is a Principal Technologist for The Middleware Company, and Chief Architect of TheServerSide.Com J2EE Community.

posted on 2005-03-11 11:38 笨笨 阅读(304) 评论(0)  编辑  收藏 所属分类: J2EEALL

只有注册用户登录后才能发表评论。


网站导航: