Container-Managed vs. Bean-Managed Entity Beans

Decisions are often the result of tradeoffs. As developers we trade art for engineering to make our creations come alive. This is especially true in the domain of EJB entity beans. By the time you decide to build your system with an architecture that uses J2EE entity beans, you will have undoubtedly mapped out your problem domain into business objects and recognized myriad use cases within the system. That is when you don your pragmatist cap and get down to making some very real decisions. Should you let your Entity JavaBean container manage bean persistence for you, or should you roll up your sleeves (it could get messy) and do it yourself?

Introduction

Decisions are often the result of tradeoffs. As developers we trade art for engineering to make our creations come

alive. This is especially true in the domain of EJB entity beans. By the time you decide to build your system with an architecture that uses J2EE entity beans, you will have undoubtedly mapped out your problem domain into business objects and recognized myriad use cases within the system. That is when you don your pragmatist cap and get down to making some very real decisions. Should you let your Entity JavaBean container manage bean persistence for you, or should you roll up your sleeves (it could get messy) and do it yourself? Entity beans have a defining quality called persistence, that is the ability to remain as one, in very Zen-like way, with the chosen data source. Entity beans are the extended alter egos of a data tuple, hopefully synchronized enough in time and state to be considered such in practical applications. Purists and pragmatists alike have recognized the philosophical impossibility of this being absolutely true, and have shunned the costs associated with bringing real persistence closer to the ideal. Regardless, it is happening, and we as builders of the fantastic can decide whether to code persistence duties ourselves, or reap the many benefits of letting our container do the dirty work. In the ideal world, an application developer will see entity beans as modules that can be plugged in wherever business object implementation is needed. While there are things about bean persistence that application developers need to know, what happens under the hood is the responsibility of a bean developer and his partner, the container provider. But the role each of these characters will play in managing persistence is a matter of choice.

Container-Managed Persistence

As you have probably concluded by now, container-managed persistence (CMP) beans are those where the bean container manages the persistence. The good thing about CMP is that it requires relatively little thought on your part to make an actual connection with your database. With CMP, you can leave your implementation class's create(), remove(), load(), and store() sessions empty. The links to your data are made through a graphical tool in the deployment phase when applying entity EJBs. (See figures 1 & 2.) Usually it's as simple as clicking, one at a time, on your bean's public field names, and choosing a corresponding row in an earlier selected database table where the fields will be mapped. The container handles all of the particulars. (See Listing 1 for a typical CMP implementation class.)

Another ease-of-use advantage of CMP is portability. When you use CMP, you are working with an object that represents a business object and is independent of your server and data source. The biggest drawback with CMP however, is that it generally limits you to relational databases. If you need to access legacy data, or proprietary sources, you'll have to write some code. Visit a few newsgroups, and you will quickly discover a second disadvantage: Users in the field claim CMP is slower in high volume transaction situations. So if your database access requires custom tweaking, you'll need bean-managed persistence.

FIGURE 1: SilverStream EJB Deployment Designer showing graphical bean to table mapping mechanism

FIGURE 2: SilverStream EJB Deployment Designer showing graphical field to row mapping mechanism

Listing 1: CMP Bean Implementation Class
 

package com.mike.jetenginetype;

import java.math.BigDecimal;

import java.sql.Date;

import java.util.*;

import javax.ejb.*;

import javax.naming.Context;

import com.sssw.rt.ejb.*;

import com.sssw.rt.util.*;

public class EBCMPJetEngineTypeBean

	implements javax.ejb.EntityBean

{

	protected EntityContext m_context;

	public int m_iD;

	public String m_manufacturer;

	public String m_model;

	public Integer m_thrustRating;  // no primitive to account for possible null values

	public int getID()

	{

		return m_iD;

	}

	public String getManufacturer()

	{

		return m_manufacturer;

	}



	public String getModel()
	{
		return m_model;
	}

	public Integer getThrustRating()
	{
		return m_thrustRating;
	}

	public void setID(int arg)
	{
		m_iD = arg;
	}
	public void setManufacturer(String arg)
	{
		m_manufacturer = arg;
	}
	public void setModel(String arg)
	{
		m_model = arg;
	}
	public void setThrustRating(Integer arg)
	{
		m_thrustRating = arg;
	}
	public void setEntityContext(javax.ejb.EntityContext entityContext1)
	{
		m_context = entityContext1;
	}
	public void unsetEntityContext()
	{
		m_context = null;
	}
	public EBCMPJetEngineTypePKey ejbCreate()

			throws CreateException
	{

		EBCMPJetEngineTypePKey pKey = new EBCMPJetEngineTypePKey();
		return pKey;
	}
	public void ejbPostCreate()

			throws CreateException {}

	public void ejbLoad() {}

	public void ejbActivate() {}

	public void ejbPassivate() {}

	public void ejbStore() {}

	public void ejbRemove() throws javax.ejb.RemoveException {}
}

Bean-Managed Persistence

The great advantage of bean-managed persistence (BMP) is its flexibility in accessing just about any data source. Once all of the unique access attributes are coded and tested, the bean user's job is pretty much plug-and-play. BMP beans can be used to access legacy databases, text file data sources, open interfaces with other software packages, and customized JDBC calls.

Using BMP requires writing custom code for the ejbCreate(), ejbRemove(), load(), store(), and ejbFindXXX() methods. Even with BMP, you won't be fully giving up container assistance. In fact, it's the container that makes the calls to these callback methods at the appropriate times. You are simply providing the behaviors for these events rather than accepting the default provided by the container in CMP.

As previously mentioned, there are those who say that they can write BMPs that outperform comparable CMPs in optimizing calls to the database. These people attest to the fact that each EJB container is different in terms of performance, and that weighs heavily in their decision to code with BMP.

BMP can also be used to access data that is spread across multiple tables or databases. But if business objects that are modeled by the beans aren't closely mirrored in the data sources, then BMP will be required to generate the SQL joins that are needed to load and store the bean. The EJB 2.0 draft specification promises enhanced CMP abilities in terms of dealing with relationships through the use of persistence schemas.

Bean-managed persistence isn't without it drawbacks though. First, once coded, the bean is pretty much slated for a life of serving a single-type data source. Remember, the code has been custom-written to serve that data source. Secondly, the bean programmer needs to have knowledge beyond that of the business domain, including at least a fair level of SQL understanding as well as a sound understanding of what it takes to get data into the database. Thirdly, there is little advantage of using BMP if you are simply replicating what the container is doing for you in a comparable CMP entity bean.

Listing 2: BMP Implementation Bean Source
 

package com.mike.jetenginetype;

import java.math.BigDecimal;

import java.sql.Date;

import java.util.*;

import javax.ejb.*;

import javax.naming.Context;

import com.sssw.rt.ejb.*;

import com.sssw.rt.util.*;

import java.sql.*;

import javax.sql.*;

import java.rmi.*;

import javax.naming.*;

public class EBBMPJetEngineTypeBean

	implements javax.ejb.EntityBean

{

	protected EntityContext m_context;

	public int m_id;

	public String m_manufacturer;

	public String m_model;

	public Integer m_thrustRating;  //Integer object allows null values

	public int getID()
	{

		return m_id;
	}

	public String getManufacturer()

	{
		return m_manufacturer;

	}

	public String getModel()
	{
		return m_model;
	}

	public Integer getThrustRating()

	{
		return m_thrustRating;

	}

	public void setID(int arg)

	{
		m_id = arg;
	}

	public void setManufacturer(String arg)
	{
		m_manufacturer = arg;
	}

	public void setModel(String arg)
	{
		m_model = arg;

	}

	public void setThrustRating(Integer arg)

	{
		m_thrustRating = arg;

	}

	public void setEntityContext(javax.ejb.EntityContext entityContext1)

	{

		m_context = entityContext1;

	}

	public void unsetEntityContext()

	{

		m_context = null;

	}

	public EBBMPJetEngineTypePKey ejbCreate(int id, String manufacturer,

String model, Integer thrustRating)

		throws CreateException
	{

		if (id < 1)

			throw new CreateException ("Invalid id");

		this.m_id = id;

		this.m_manufacturer = manufacturer;

		this.m_model = model;

		this.m_thrustRating = thrustRating;

		Connection con = null;

		PreparedStatement ps = null;

		try

		{
			System.out.println("Enter try in ejbCreate");

			con = this.getConnection();

			// Notice below that BMP requires the bean provider to have knowledge of the

			// actual table structure.  Also familiarity with SQL is necessary if JDBC is]

			// used.

			ps = con.prepareStatement(

				"INSERT INTO  tblJetEngineType (ID, Model, " +

"Manufacturer, ThrustRating) VALUES (?,?,?,?)");

			ps.setInt(1, id);

			ps.setString(2, manufacturer);

			ps.setString(3, model);

			System.out.println("ps: " + ps);

			if (thrustRating == null)

			{

				ps.setNull(4, java.sql.Types.INTEGER);

			}

			else

			{

				ps.setInt(4, thrustRating.intValue());

			}

			// Nested try/catch to provide isolated, non-fatal update

			// error for testing purposes

			try

			{

				if (ps.executeUpdate() != 1)

					throw new CreateException (

						"Failed to add JetEngineType to database");

			}

			catch (SQLException se)

			{

				System.out.println("Exception in ps.executeUpdate()  " + se);

			}

			EBBMPJetEngineTypePKey pk = new EBBMPJetEngineTypePKey();

			pk.m_id = id;

			return pk;

		}

		catch (CreateException ce)

		{

			System.out.println("" + ce);

			throw ce;  //rethrow

		}

		catch (Exception e)

		{

			throw new EJBException(e);

		}

		finally

		{

			try

			{

				if (ps != null) ps.close();

				if (con != null) con.close();

			}

			catch (Exception e)

			{

				System.out.println("nError closing PreparedStatement or Connectionn");

				e.printStackTrace();

			}

		}

	}

	// required by EJB 1.1 specification

	public void ejbPostCreate(int id, String manufacturer,

		String model, Integer thrustRating)

			throws CreateException {}

	public void ejbLoad()

		{
			BBMPJetEngineTypePKey pk = (EBBMPJetEngineTypePKey) m_context.getPrimaryKey();

			Cnnection con = null;

			PreparedStatement ps = null;

			ResultSet result = null;

		try

		{
			con = this.getConnection();

			ps = con.prepareStatement(

				"SELECT ID, Model, Manufacturer, ThrustRating " +

				"FROM tblJetEngineType WHERE ID = ?");

			ps.setInt(1, pk.m_id);

			result = ps.executeQuery();

			if (result.next())

			{

				m_id = result.getInt("ID");

				m_manufacturer=result.getString("Manufacturer");

				m_model=result.getString("Model");

				thrustRating = new Integer(result.getInt("ThrustRating"));

			}
		}

		catch (Exception e)

		{

			throw new EJBException(e);

		}

		finally

		{
			try

			{
				if (ps != null) ps.close();

				if (con != null) con.close();

				if (result != null) result.close();

			}

			catch (Exception e)

			{

				System.out.println("nError closing PreparedStatement, Connection, or ResultSetn");

				e.printStackTrace();

			}

		}

	} // end ejbLoad()

	// Writes bean to data source

	public void ejbStore()

	{
			Connection con = null;

			PreparedStatement ps = null;

		try

		{

			con = this.getConnection();

			ps = con.prepareStatement(

				"UPDATE tblJetEngineType SET Model=?, "+

					"Manufacturer=?, ThrustRating=? WHERE ID=?");

			if (m_model == null)  // not a required field in database

				ps.setNull(1, java.sql.Types.VARCHAR);

			else ps.setString(1, m_model);

			if (m_manufacturer == null)  // not a required field in database

				ps.setNull(2, java.sql.Types.VARCHAR);

			else ps.setString(2, m_manufacturer);

			if (m_thrustRating == null)  // not a required field in database

				ps.setNull(3, java.sql.Types.INTEGER);

			ps.setInt(3, m_thrustRating.intValue());

			ps.setInt(4, m_id);

			if (ps.executeUpdate() != 1)

				throw new EJBException("Update error in ejbStore");

		}

		catch (Exception e)

		{

			throw new EJBException(e);

		}

		finally
		{
			try

			{
				if (ps != null) ps.close();

				if (con != null) con.close();

			}

			catch (Exception e)

			{

				System.out.println("nError closing PreparedStatement, Connection, or ResultSetn");

				e.printStackTrace();

			}

		}  // end ejbStore()
	}

	// Removes record

	public void ejbRemove() throws javax.ejb.RemoveException

	{
			Connection con = null;

			PreparedStatement ps = null;

		try

		{

			con = this.getConnection();

			ps = con.prepareStatement(

				"DELETE FROM tblJetEngineType WHERE ID = ?");

			ps.setInt(1, m_id);

			if (ps.executeUpdate() != 1)

				throw new EJBException("Update error in ejbStore");

		}

		catch (Exception e)

		{

			throw new EJBException(e);

		}

		finally

		{

			try

			{

				if (ps != null) ps.close();

				if (con != null) con.close();

			}

			catch (Exception e)

			{

				System.out.println("nError closing PreparedStatement, Connection, or ResultSetn");

				e.printStackTrace();

			}

		}



	} // end ejbRemove()

	// Finds record based on primary key

	public EBBMPJetEngineTypePKey ejbFindByPrimaryKey(EBBMPJetEngineTypePKey pk) throws

	        FinderException

	{

			Connection con = null;

			PreparedStatement ps = null;

			ResultSet result = null;

		try

		{

			con = this.getConnection();

			ps = con.prepareStatement(

				"SELECT ID FROM tblJetEngineType WHERE ID = ?");

			ps.setInt(1, pk.m_id);

			result = ps.executeQuery();

			if (!result.next())

				throw new ObjectNotFoundException(

					"Can not find this jet engine type (ID = " + pk.m_id + ")");

		}

		catch (Exception e)

		{

			throw new EJBException(e);

		}

		finally
		{
			try

			{

				if (ps != null) ps.close();

				if (con != null) con.close();

			}

			catch (Exception e)

			{

				System.out.println("nError closing PreparedStatement, Connection, or ResultSetn");

				e.printStackTrace();
			}

		}

		return pk;  // return the primary key

	} // end ejbFindByPrimaryKey

	// Returns a Connection to a DataSource referenced in an environment

	// resource reference.  This is provided by the deployer using application

	// server tools.

	private Connection getConnection() throws EJBException

	{

		try

		{

			Context ctx = new InitialContext();

			System.out.println("getConnection got InitialContext: " + ctx);

			DataSource source = (DataSource) ctx.lookup("java:comp/env/jdbc/DBJetEngineType");

			System.out.println("getConnection() got source: " + source);

			return source.getConnection();

		}

		catch (Exception e)

		{

			System.out.println("Exception thrown in getConnection()");

			throw new EJBException(e);

		}

	}

	public void ejbActivate() {}

	public void ejbPassivate() {}

}



The Choice is Yours, But... 


Sometimes easier is better. Generally speaking, CMP, the easier of the two persistence types to implement, will be your choice. CMP is simple to use, portable, and will work in most any situation. Even if your data needs modification before storage or after retrieval, you can still access the callbacks that BMP performs. But remember, you can't talk to your data source with CMP. So maybe you do some string concatenation, data conversion, or pre-storage compression. Simply condition the datum in your load() or store() methods and save it to a public field that is mapped to storage at deployment time. If speed is your problem, try various techniques like enclosing EJB business method calls in transactions or delaying instantiation, before turning to BMP.

Creative Freedom


No one can argue the point that BMP gives you more control over the persistence characteristics of your bean package. Tools like connection pools, offered by your application server, really can make developing with it an achievable goal. So, if you need to know exactly what happens when your database is updated, want to maintain control while changing to your data source, or simply need to tweak database access to achieve performance gains, then BMP is probably for you.

The Mother of Invention
Necessity is, more often than not, the reason you'll decide to use BMP. But when you do, just remember there are tools that can make the process a lot easier. For example, create resource references to connection pool JDBC resources in your deployment descriptors; this simplifies the use of BMP beans. If you can use CMP, then do it. J2EE compliant application servers follow a strict set of rules regarding CMP, so CMP beans are portable and their performance will only improve as application servers evolve. Shop wisely for you container in the beginning. Performance can vary greatly. Also, remember that callback methods are available to you with CMP as well as BMP. The only difference is that with BMP, you must program the basic services. When you choose between CMP and BMP, carefully weigh the ease-of-use, portability, speed, and flexibility tradeoffs. And above all else, remember you are an artist and an engineer.

Dig deeper on Web services performance

Pro+

Features

Enjoy the benefits of Pro+ membership, learn more and join.

Related Discussions

Michael Demastrie asks:

Do you prefer container-managed or bean-managed entity beans?

0  Responses So Far

Join the Discussion

0 comments

Oldest 

Forgot Password?

No problem! Submit your e-mail address below. We'll send you an email containing your password.

Your password has been sent to:

SearchSoftwareQuality

SearchCloudApplications

SearchAWS

TheServerSide

SearchWinDevelopment

Close