Skip to main content

skip to main content

developerWorks  >  WebSphere | Java technology  >

The EJB Advocate: Making entity EJB components perform, Part 2

developerWorks
Document options

Document options requiring JavaScript are not displayed


My developerWorks needs you!

Connect to your technical community


Rate this page

Help us improve this content


Level: Intermediate

Geoff Hambrick (ghambric@us.ibm.com), Distinguished Engineer, IBM

06 Apr 2005

As discussed in last month's column, using poorly designed EJB™ components can lead to serious performance problems during system testing or (worse) in production. This month, the EJB Advocate shows how to use CMRs to get the benefit of using multiple related CMPs in a single unit of work.

From the IBM WebSphere Developer Technical Journal.

In each column, The EJB Advocate presents the gist of a typical back-and-forth dialogue exchange with actual customers and developers in the course of recommending a solution to an interesting design issue. Any identifying details have been obscured, and no "innovative" or proprietary architectures are presented. For more information, see Introducing the EJB Advocate.

The problem

Last month I mentioned that we would try to talk about Service Data Objects this time around, but a follow-up question came out of last month's discussion about performance that was much more important to address.

Dear EJB Advocate,

I read your article last time, and like No Longer a Fan, I softened my stance about CMP entity EJB components. So I developed a typical use case from our application, where the data model looked like this:

Figure 1. Data model
Data model

I built entity CMPs for each object in the class diagram above, with a Key and Data Transfer Object (DTO) returned by a custom method, just as you suggested, in order to minimize the chattiness between layers. A DTO showing a relationship has a property that references either the target DTO (if the cardinality is not greater than one) or an array of them (if greater than one). All of these DTOs are declared externalizable to speed up the marshaling time, and I even created some custom finders to make it easier to access the CMPs without creating primary key objects all the time. I then used a local interface to these within a session facade to ensure that only one transaction was issued. The session bean had a method that assumed the local homes of related CMPs were retrieved in the ejbCreate() method and set as instance variables. The method in question looked like:

public CustomerData  getOpenOrderForCustomer(int cId) {
	// Get the Customer DTO	
	Customer cRef = cHome.findByCustomerId(cId);
	CustomerData cData = cRef.getData();

	// Check to see if there is an open order
	int oId = cData.getOpenOrderId();
	if (oId == 0) {
		throw new OrderNotOpenException(cId);
	}

	// Get the Order entity and the DTO object	
	Order oRef = oHome.findByOrderId(oId);
	OrderData oData = oRef.getData();
	cData.setOrder(oData);
	
	// Get the array of Line Items DTOs set up
	Collection liList = liHome.findAllItemsForOrderId(oId);
	int liSize = liList.size();
	LineItemData[] liArray = new LineItemData[liSize];
	oData.setLineItems(liArray);
	Iterator liIterator = liList.iterator();
	LineItem liRef = null;
	LineItemData liData = null;
	Product pRef = null;
	for (int i = 0; i < liSize; i++) {
		// Get the Line Item DTO
		liRef = (LineItem) liIterator.next();
		liData = liRef.getData();

		// Get the Product DTO
		pRef = pHome.findByProductId(pId);
		liData.setProduct(pRef.getData());
		liArray[i] = liData;
	}
	
	return cData;
}

When I ran this session and set of CMPs against a hand-coded JDBC program, its throughput was significantly less (almost 2X). Needless to say, I was not impressed after all the work I did.

Sign me:
Won't Get Fooled Again

I had an idea that like the situation for No Longer from last month, this was not really an apples-to-apples comparison. But there was a lot to like about this example.

Dear Won't Get Fooled,

I have to say I am very impressed on a number of accounts -- not the least of which is that you actually read my column and followed the advice!

Seriously, I was impressed that you developed an object model to show me the basic design of the application. Too often, teams want a blanket answer about what is "best", when best is always situational. I was very happy that you included your EJB code, so that I do not need to guess whether you really followed the practices you described. Your code using EJBs went way beyond what folks normally do!

That said, it would help to see the hand-coded SQL used for your comparison before giving any advice, because "best" is also always best in comparison to an alternative.

At the same time, if you could answer a question for me: I was wondering if you used a concept called "container managed relationships" (or CMRs) with the entity beans?

Looking forward to your reply.

OK then,
Your EJB Advocate



Back to top


Apple one, meet apple two

Dear EJB Advocate,

Let me take your question on CMRs first because it is the simplest to answer: No, I did not use them because I hadn't heard much about them.

And here is the equivalent session bean method that used JDBC; it assumes that the driver was loaded in the ejbCreate() method and that run time exceptions will be handled by our delegate framework without having to be declared in the method signatures. Sorry in advance that the code is so long, which is why I didn't send it the first time:

public CustomerData  getOpenOrderForCustomer(int cId) 
throws OrderNotOpenException
{
	Connection con = null;
	try { 
		con = DriverManager.getConnection(
			"jdbc:db2:orderentry"
      		);
	}
	catch (SQLException e) {
		throw new RuntimeException,  
 			"Connection cannot be made.", e)
 		);
 	} 	
 	try {
 		con.setAutoCommit(false);
		con.setReadOnly(true);
		con.setTransactionIsolation(
			Connection.TRANSACTION_READ_COMMITTED
		);
	 	PreparedStatement stmt1 = con.prepareStatement("
 			SELECT NAME, OPEN_ORDER_ID
   			FROM CUSTOMER_USER.CUSTOMER 
 			WHERE ID = ?
	 	");
		stmt1.setInt(1, customerId);
	 	stmt1.setMaxRows(1);
	 	stmt1.setMaxFieldSize(30);
 		ResultSet result = stmt1.executeQuery();
	 	result.next();
 		String name = result.getString(1);
	 	int openOrderId = result.getInt(2);
	 	result.close();
	 	stmt1.close();

 		// Validate that the order is open
 		if (openOrderId == 0) {
 			throw new OrderNotOpenException(cId);
 		}

 		// Now go get the Line Items and Products together
 		PreparedStatement stmt2 = con.prepareStatement("
 			SELECT 
 				PRODUCT.SKU,
 				PRODUCT.DESC,
 				PRODUCT.PRICE,
 				LINEITEM.QUANTITY
 	   		FROM 
 				CUSTOMER_USER.LINEITEM,
 				CUSTOMER_USER.PRODUCT 
 			WHERE 
 				LINEITEM.ORDER_ID = ? 
 	   		
 			ORDER BY 
 				PRODUCT.SKU ASC
	 	");

 		stmt2.setInt(1, openOrderId);
 		stmt2.setFetchSize(10);
 		ResultSet result2 = stmt2.executeQuery();
		LineItemData liData = null;
 		ProductData pData = null;
		int productId = 0;
 		String description = null;
 		Int price = 0;
 		int quantity = 0;
 		Vector v = new Vector(10, 10);
 		while (result2.next()) {
 			// Get the fields
 			productId = result2.getInt(1);
 			description = result2.getString(2);
 			price = result2.getInt(3);
 			quantity = result2.getInt(4);

 			// Set the fields related to Order
 			liData = new LineItemData();
			pData = new ProductData();
 			liData.setKey(
 				new LineItemKey(openOrderId, productId)
 			);
			liData.setQuantity(quantity);
 			liData.setAmount(price * quantity);
 			liData.setProduct(pData);

 			// Set the fields related to Product
 			pData.setKey(new ProductKey(productId));
 			pData.setDescription(description);
 			pData.setPrice(price);

 			// Add the item to the Vector 
 			v.addElement(liData);
 		}
	
 		// Close the result set and statement
 		result2.close();
 		stmt2.close();
 		
 		// Now we can set the Customer and Order
 		CustomerData cData = new CustomerData();
 		OrderData oData = new CustomerData();
 		cData.setKey(new CustomerKey(cid));
 		cData.setName(name);
 		cData.setOpenOrderId(openOrderId);
 		cData.setOrder(oData);

 		// The order data can be defaulted for the most part
 		oData.setKey(new OrderKey(openOrderId));
 		oData.setStatus("Open");
 		oData.setLineItems((LineItem[])v.toArray());

		// Return the top of the graph
 		return cData;
 	}	
 	catch (SQLException e) {
 		throw new RuntimeException(
 			"Error during processing.", e
 		);
	}
 	finally {
		try {
			con.close();
		}
		catch (SQLException e) { 
			throw new RuntimeException(
 				"Error closing connection.", e
 	 		);
		}
	}
}

I know that after looking at this code you are going to laugh about "all the work I did" for the entity EJBs. But remember, here you are looking at ALL the code needed for the method to work. I did not show you any of the entity EJBs and the deployment descriptors that I had to write to get all of this to work with CMPs.

So nonetheless, still:
Won't Get Fooled Again

Wow! This guy is good. He sure knows how to cut you off at the pass.

Dear Won't Get Fooled,

Again, I am very impressed. Yes, I did laugh when I saw the quantity of the code, but on the other hand its quality is really great! Your code follows all the JDBC best practices I know about, and then some. Just to summarize what I see you doing:

  • Create resources as late as possible and release them as early as possible.
  • Handle errors properly, and make sure to use the finally clause to do actions that should occur whether there was an exception or not.
  • You set access intents on the connection and use prepared statements to optimize the communications. (I especially liked your using the setFetchSize() on the statement in conjunction with the Vector initializer and increment!)
  • And, of course, your SQL used specific column selects and joins where possible to minimize the number and size of statements sent to the database tier and the amount of data returned.

This code sets the bar pretty high with respect to comparisons we can make to the current code and any suggested improvements.

Believe it or not, though, I think you can do better that JDBC when using CMPs with CMRs. Just by looking at the session bean code, and without asking to look at your entity EJB code or deployment descriptors, I had suspected that you did not use CMRs in your entity EJB components. Why? Your session bean was procedural rather than object-oriented. That is, it procedurally handled all the logic of "navigating" the relationships between the four entities involved instead of delegating to the entity. If it were object-oriented, I would have expected to see the following code in your session:

public CustomerData  getOpenOrderForCustomer(int id) 
throws OrderNotOpenException
{
	// Get the Customer DTO	
	CustomerKey key = CustomerKey new(id)
	Customer ref = cHome.findByPrimaryKey(key);
	return ref.getDataWithOpenOrder();
}

As a brief aside, this is a true session facade pattern: the session bean delegates the business logic to another class - in this case the Customer entity, which represents the top of an object graph. The session bean merely adds transactions, security, and distribution as qualities of service.

That said, I appreciate the fact that you coded your CMP and JDBC access logic directly in the session bean rather than delegate to a helper POJO. You must have read my first article, too. But whether the logic calling the CMPs is in the session bean or a POJO, it is still procedural and not object-oriented in nature.

To get back to the main point, if I had seen the true session facade pattern passing through to an entity above, I still would not have known you were using CMRs until I looked at the Customer. Its "guts" might have looked like:

// CMP fields
public abstract String getName();
public abstract void setName(String value);
public abstract int getOpenOrderId();
public abstract void setOpenOrderId(int value);

// Custom getters
public CustomerData  getData() {
	CustomerData data = new CustomerData();
	data.setKey(getPrimaryKey());
	data.setOpenOrderId(getOpenOrderId());
	return data;
}
public CustomerData  getDataWithOpenOrder(int cId) {
	// Check to see if there is an open order
	int oId = getOpenOrderId();
	if (oId == 0) {
		throw new OrderNotOpenException(cId);
	}

	// Get the Order entity and the DTO object	
	Order oRef = oHome.findByOrderId(oId);
	OrderData oData = oRef.getDataWithLineItems();
	CustomerData data = getData();
	data.setOrder(oData);
	return data;
}

This code would have indicated that you were using object oriented delegation, but not using CMRs. If you were using CMRs, the code would have looked something like this:

// CMP fields
public abstract String getName();
public abstract void setName(String value);
public abstract int getOpenOrderId();
public abstract void setOpenOrderId(int value);

// CMR fields
public abstract Order getOpenOrder();
public abstract void setOpenOrder(Order value);
public abstract Collection getOrders();
public abstract void setOrders(Collection value);

// Custom getters
public CustomerData  getData() {
	CustomerData data = new CustomerData();
	data.setKey(getPrimaryKey());
	data.setOpenOrderId(getOpenOrderId());
	return data;
}
public CustomerData  getDataWithOpenOrder() {
	// Check to see if there is an open order REFERENCE
	Order oRef = getOpenOrder ();
	if (oRef == null) {
		throw new OrderNotOpenException(cId);
	}

	// Get the Open Order data	
	OrderData oData = oRef.getDataWithLineItems();
	CustomerData data = getData();
	data.setOrder(oData);
	return data;
}

I am sure you can generalize either the procedural or object-oriented pattern working its way through the graph, and that using CMRs is independent of this delegation. I will provide the guts of the Order, Line Item, and Product entities for the object-oriented CMR case, starting with the Order, since we ultimately want to compare to the procedural JDBC code above:

// CMP fields
public abstract String getStatus();
public abstract void setStatus(String value);

// CMR fields
public abstract Customer getCustomer();
public abstract void setCustomer(Customer value);
public abstract Collection getLineItems();
public abstract void setLineItems(Collection value);

// Custom getters
public OrderData  getData() {
	OrderData data = new OrderData();
	cData.setKey(getPrimaryKey());
	data.setStatus(getStatus());
	return data;
}
public OrderData  getDataWithLineItems() {
	// Use CMR to get the line items into an array
	Collection liList = getLineItems();
	int liSize = liList.size();
	LineItemData[] liArray = new LineItemData[liSize];
	Iterator liIterator = liList.iterator();
	LineItem liRef = null;
	for (int i = 0; i < liSize; i++) {
		// Get the Line Item DTO
		liRef = (LineItem) liIterator.next();
		liArray[i] = liRef.getDataWithProduct();
	}

	// Create the object and return
	OrderData data = getData();
	data.setLineItems(liArray);
	return data;
}

Here is the Line Item:

// CMP fields
public abstract int getQuantity();
public abstract void setQuantity(int value);
public abstract int getAmount();
public abstract void setAmount(int value);

// CMR fields 
public abstract Order getOrder();
public abstract void setOrder(Order value);
public abstract Product getProduct();
public abstract void setProduct(Product value);

// Custom getters
public LineItemData  getData() {
	LineItemData data = new LineItemData ();
	data.setKey(getPrimaryKey());
	data.setQuantity(getQuantity());
	data.setAmount(setAmount());
	return data;
}
public CustomerData  getDataWithProduct() {
	// Get the Product from the CMR	
	Product pRef = getProduct();
	ProductData pData = pRef.getData ();
	LineItemData data = getData();
	data.setProduct(pData);
	return data;
}

And, finally, here is the Product:

// CMP fields
public abstract String getDescription();
public abstract void setDescription(String value);
public abstract int getPrice();
public abstract void setPrice(int value);

// CMR fields
public abstract Collection getLineItems();
public abstract void setLineItems(Collection value);

// Custom getters
public ProductData  getData() {
	ProductData data = new ProductData();
	data.setKey(getPrimaryKey());
	data.setDescription(getDescription());
	data.setPrice(getPrice());
	return data;
}

So now we can truly compare apples, the variety of "apples" being:

  • Procedural/JDBC
  • Procedural/CMP
  • Procedural/CMR
  • OO/JDBC
  • OO/CMP
  • OO/CMR.

I include the OO/JDBC case just to be complete - which is basically any approach where "custom" objects are developed one-to-one with your business objects (four of them in your case). Entity EJBs with bean managed persistence (BMPs) would be included in this list, as would "JDO-like" objects without the tool or JVM support. In other words, if you custom code JDBC associated with a single business object with the expectation that it can call others within its own methods, and be called in the context of others, then it is considered OO/JDBC.

Now, on to the evaluation.

First off, and as you realized I would point out, any individual method in any CMP/CMR case is far, far simpler than any method with directly-coded JDBC because of the complexity of the SQL and framework. I also mention an "obvious" point that needs making: if you suddenly switch databases, the JDBC implementations no longer work (unless you modify the code, or use data sources or property files or environment variables as an approach to getting the connection URL). And even then, if you move away from relational database as your underlying datastore, then you are totally dead.

Next, and as you also pointed out, there are way more methods involved in developing a given unit of work when using any CMP/CMR approach (or even the OO/JDBC approach) than when using procedural/JDBC - in part because of all the custom gets and sets involved. But these custom object-oriented methods of any type are reusable, while procedural code, especially that using JDBC, has to be hand-crafted to optimize it to the scenario.

For an example of reuse, you can easily expose the various getData methods from any entity bean (CMP or CMR) up to the session with the appropriate key fields being passed in. This session method would be a true facade, as mentioned above. For example, say we wanted to get just the "standalone" DTO associated with a given entity type. We could write four very simple session facade methods:

public CustomerData  getCustomerData(int id) 
throws FinderException
{
	// Get the Customer DTO	
	CustomerKey key = CustomerKey new(id)
	Customer ref = cHome.findByPrimaryKey(key);
	return ref.getData ();
}
public OrderData  getOrderData(int id) 
throws FinderException
{
	// Get the Order DTO	
	OrderKey key = OrderKey new(id)
	Order ref = oHome.findByPrimaryKey(key);
	return ref.getData ();
}
public LineItemData  getLineItemData(int orderId, int productId) 
throws FinderException
{
	// Get the LineItem DTO	
	LineItemKey key = LineItemKey new(orderId, productId)
	LineItem ref = liHome.findByPrimaryKey(key);
	return ref.getData ();
}
public ProductData  getProductData(int id) 
throws FinderException
{
	// Get the Product DTO	
	ProductKey key = ProductKey new(id)
	Product ref = pHome.findByPrimaryKey(key);
	return ref.getData ();
}

The true benefit of reusability, though, is maintainability; by using the custom methods associated with the DTO derived from your data model, you only need change the various entity bean getData() and setData() methods when you add or remove an attribute (whether it is related to a CMP field or a CMR). Every procedural or OO/JDBC method related to those fields would need to change if you modified the schema.

When using CMRs, it is rarely necessary to get the home and then use a custom finder to retrieve one or more references to entities; a CMR does that automatically behind the scenes when one is declared. This simplification results in a lot less parameters being passed around when compared to procedural and CMP-only approaches. In fact, when using CMRs, even if procedural, only one finder needs to be explicitly called in a unit of work. And this count includes that for the stateless session bean reference, since it can be cached in the client! Also, in many cases with OO/CMRs, the entity lookup is using a findByPrimaryKey() method. This means that you have to specify fewer custom finders in the deployment descriptors. Less is more, when it comes to maintainability and usability.

Related to this point about the key, when using CMRs, often only the session bean (or "ultimate client", like the servlet or JSP) needs to know what the key fields of an entity are, even with respect to the custom methods of the entity itself. This makes entities even simpler and easier to maintain, and even more so if CMRs are consistently used. You could modify the key fields and not have to do more than redeploy. When not using CMRs, your code has to know the key fields to find a related object, and you are more likely to use custom finders in your code, whether procedural or OO style.

Now we will address your last and, presumably, most important point. It is clear from your load tests that the procedural/JDBC performs with better throughput than the procedural/CMP case. And there is no reason to think that an OO/CMP case would run any better or worse than a procedural CMP (meaning 2X worse in your case than the procedural JDBC), since the only thing that changes significantly is the delegation.

What is not clear is whether the CMR case would run as well as the hand-crafted JDBC code, regardless of whether either technology uses a procedural or OO approach. We submit that when using CMRs, the container can be tuned in some application servers, like IBM WebSphere Application Server, to optimize the SQL in ways similar to that which you hand coded. For example, "access intents" can be specified that enable you to set the read ahead size (similar to setFetchSize), read limit (similar to setMaxRows), and preload caching (similar to joins). You can also "tweak" the columns that are loaded in the selects. (See the IBM WebSphere Application Information Center document on application profiling.)

If you would be inclined to argue that dealing with CMRs and profiles and access intents makes the entities as complicated as directly coding optimized JDBC, I would offer a number of points:

  1. CMRs are model information similar to foreign keys in the database. Unlike foreign keys, however, they also make the application code simpler, regardless of whether they are OO or procedural in nature.
  2. Explicitly-specified access intents should only be used when the out-of-the-box performance does not meet specified goals; for example, the case we discussed last month, when one or more unrelated entities are the target of the unit of work, you discovered that performance is within just a few percent and well worth the simpler programming model.
  3. EJB containers are getting better at mapping CMPs with CMRs to the underlying data stores all the time, making it possible to redeploy later and get performance benefits that are impossible if you hand code the JDBC.
  4. Where access intents do need to be specified for performance reasons, the code of the EJBs does not need to change, only the tuning parameters do. Thus, the code will always function, even if it does not perform as well as it could. This guarantee has a huge impact on the development and testing cycle.
  5. Where a number of units of work share the same basic set of access intents, such as all those that need the order header with the customer (like a submit, cancel, and show orders method), you can create a common application profile and tune these functions together.

Without CMRs, your Procedural or OO/CMP code would implicitly issue a number of separate SQL statements: one for the customer, one for the order, one for the list of line items, and one for each product. No wonder CMPs don't perform as well as hand-coded JDBC.

But with full exploitation of CMRs, the application can sometimes be tuned with simple object navigation path expressions to issue just one SQL statement (with zero or more inner joins to handle multi-cardinality navigations; my friend Stacy calls these "honking big" joins). In other words, it may be possible that applications fully exploiting CMRs could perform better than the code your average JDBC programmer is willing to write (I hate to even look at SQL with inner joins - especially when someone else wrote it!).

So, hopefully this evaluation has convinced you to use CMRs. And, interestingly enough, even when your code does not use them explicitly, you still can add CMRs to the entities and redeploy without changing any methods. Then, as long as your code always uses the findByPrimaryKey() method, the container can try to exploit the CMRs in the generated SQL. This point is key - if you will pardon the pun. The EJB 2.0 specification makes it clear that calling a custom finder method cannot be circumvented since there may be essential logic hidden within. See point #3, above, to see that this is a no-risk option, even if you do not change one line of code.

But, it would not be that hard for you to go back and change your procedural/CMP method such that it is compatible with CMRs, like so:

public CustomerData  getOpenOrderForCustomer(int cId) {
	// Get the Customer DTO
	// Using PK here to discourage use of custom finders
	// But since this is the "root" of the call, it is optional
	Customer cRef = cHome.findByPrimaryKey(new CustomerKey(cId));
	CustomerData cData = cRef.getData();

	// Check to see if there is an open order
	int oId = cData.getOpenOrderId();
	if (oId == 0) {
		throw new OrderNotOpenException(cId);
	}

	// Get the Order entity and the DTO object -fBPK is mandatory
	Order oRef = oHome.findByPrimaryKey(new OrderKey(oId));
	OrderData oData = oRef.getData();
	cData.setOrder(oData);
	
	// Get the array of Line Items DTOs set up
	// When not using CMRs, this custom finder is mandatory!
	Collection liList = liHome.findAllItemsForOrderId(oId);
	int liSize = liList.size();
	LineItemData[] liArray = new LineItemData[liSize];
	oData.setLineItems(liArray);
	Iterator liIterator = liList.iterator();
	LineItem liRef = null;
	LineItemData liData = null;
	Product pRef = null;
	for (int i = 0; i < liSize; i++) {
		// Get the Line Item DTO
		liRef = (LineItem) liIterator.next();
		liData = liRef.getData();

		// Get the Product DTO -fBPK is mandatory to support join
		pRef = pHome.findByPrimaryKey(new ProductKey(pId));
		liData.setProduct(pRef.getData());
		liArray[i] = liData;
	}
	
	return cData;
}

This code could be tuned to issue as few as two SQL statements, because the custom findAllItemsForOrderId() method is called with a custom finder, forcing a "reset". With this level of tuning, the procedural CMP-CMR (meaning CMRs are defined but not explicitly used in the code) should perform within a few percent of your hand coded JDBC, which also issues two SQL statements.

But you will actually find it simpler to use the CMRs once you have gone to the trouble to specify them -- even if the code is procedural - and basically the same lines of code as above must change:

public CustomerData  getOpenOrderForCustomer(int cId) {
	// Get the Customer DTO
	// Using PK here to discourage use of custom finders
	// But since this is the "root" of the call, it is optional
	Customer cRef = cHome.findByPrimaryKey(new CustomerKey(cId));
	CustomerData cData = cRef.getData();

	// Check to see if there is an open order
	// Note that this check is now much more "meaningful" since
	//	the code does not have to interpret '0' as no open order
	Order oRef = cData.getOpenOrder();
	if (oRef == null) {
		throw new OrderNotOpenException(cId);
	}

	// Get the Order DTO object, since we already have the ref
	OrderData oData = oRef.getData();
	cData.setOrder(oData);
	
	// Get the array of Line Items DTOs using the CMR
	Collection liList = oRef.getLineItems();
	int liSize = liList.size();
	LineItemData[] liArray = new LineItemData[liSize];
	oData.setLineItems(liArray);
	Iterator liIterator = liList.iterator();
	LineItem liRef = null;
	LineItemData liData = null;
	Product pRef = null;
	for (int i = 0; i < liSize; i++) {
		// Get the Line Item DTO
		liRef = (LineItem) liIterator.next();
		liData = liRef.getData();

		// Get the Product DTO using the CMR
		liData.setProduct(liRef.getProduct().getData());
		liArray[i] = liData;
	}
	
	return cData;
}

Don't forget that you may get to throw away most of those pesky homes and IC lookups in your session ejbCreate() methods by building (or better yet, generating) a session facade per entity. What a win-win! After this, I hope you will start signing yourself as:

Led to Water

I also hope you drink it, and not the stronger stuff that gets passed around.

OK then,
Your EJB Advocate



Back to top


Conclusion

Last month's best practices were basically about using unrelated CMP entity EJBs in a single transaction. They form the basis for proper use of CMPs that should always be used. These best practices include:

  1. Always use a session facade to start a global transaction around the entities used.
  2. Always use the local interface to the CMP entity to avoid a possible double hop.
  3. Even when using locals, build custom creates, finds, gets and sets to minimize chattiness between layers and foster reuse and maintainability.
  4. Strive for no more than one call after a single cardinality find on a home, or a next on an iterator retrieved from a collection.

Through this month's exchange, we looked in detail at a scenario where multiple related entity EJBs are used in a single transaction. The truth is that CMPs without CMRs are almost always going to perform significantly worse than handwritten JDBC. That is basically why entity EJBs prior to 2.0 did not get widely used. However, EJB 2.0 has solved that problem for most scenarios.

In this exchange, we explored the following best practices associated with related entities:

  1. You have everything to gain and nothing to lose to set up the appropriate CMRs that match your data model, even if the application code does not explicitly use them.
  2. Measure the performance of the system, and where your entities do not meet expectations, try and tune the container to optimize the queries. It may be that performance can become nearly equal to or even better than that of a hand-coded JDBC routine - but with less work, more reuse, and datastore independence.
  3. Also, consider refactoring your session facades so that they are associated with a single "gateway" entity that is the natural starting point for the code. Build application profiles and custom finders around these gateway entities to reuse the tuning parameters.
  4. If the performance still does not meet expectations, examine the use of the entity EJBs to see whether findByPrimaryKey() or CMR calls can be used instead of custom finders. In other words, using custom finders should be considered an anti-pattern except for the first call to an entity EJB in a unit of work - this entity can be considered a "gateway" to those associated with it. If this anti-pattern is found, then try and re-tune the application again as in the second point above.
  5. If building the application from scratch, or when refactoring the code for any reason (for example, best practices #3 or #4), then consider using OO approaches to your session beans to make them true facades. This refactoring can be done on a method-by-method basis.
  6. If performance for a given unit of work still does not meet your expectations, then use an OO approach for the method invoked on the natural gateway objects used in that transaction, and write BMP methods that use JDBC directly (see Kyle Brown's book in Resources). And please send me the example!
  7. Every now and then, check the product updates to see if there is a new tuning option to try so that you can get rid of one or more BMP methods, or just generally speed up performance of the system. The ultimate goal should be 100% CMP/CMR with 0% BMP. But it may take some time to get there.

That's enough for one article. This month, I have learned enough to make no promises about what next month's article will be about.



Resources



About the author

Author photo

Geoff Hambrick is a lead consultant with the IBM Software Services for WebSphere Enablement Team and lives in Round Rock, Texas (near to Austin). The Enablement Team generally helps support the pre-sales process through deep technical briefings and short term Proof of Concept engagements. Geoff was appointed an IBM Distinguished Engineer in March of 2004 for his work in creating and disseminating best practices for developing J2EE applications hosted on IBM WebSphere Application Server.




Rate this page


Please take a moment to complete this form to help us better serve you.



 


 


Not
useful
Extremely
useful
 


Share this....

digg Digg this story del.icio.us del.icio.us Slashdot Slashdot it!



Back to top