Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 69 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
69
Dung lượng
3,06 MB
Nội dung
Practical Data Access It is simpler to create objects of this type, but we must still rely on the inherited execute( ) methods This query object can be used as follows: SqlQuery customerQuery = new CustomerQuery(dataSource); List customers = customerQuery.execute (6) ; As RdbmsOperation objects are threadsafe, we will typically construct such objects once, and hold them as instance variables in Data-Access Objects The following version is a more sophisticated query that not only hides SQL, parameter declaration and compilation inside its constructor, but implements a new query method, which enables it to take a combination of parameters for which there is no convenient execute( ) method in the SqlQuery class: The new query method can take strongly typed parameters, and can be given a meaningful name: public List findWithMeaningfulName( String myString, int id, String strings) { return execute (new Object[] { myString, new Integer(id), string3 } ) ; } } We can use this as shown below Note that this code is self-documenting: CustomerQuery customerQuery = new CustomerQuery (ds); List l = customerQuery.findWithMeaningfulName (" foo", 1, "bar"); It's easy to avoid code duplication in practice by the use of inheritance among query objects For example, the implementation of the extract ( ) method can be provided in a base class, while subclasses provide different SQL and variable declarations When only the SQL WHERE clause varies, multiple query objects of the same class can be created, each configured to execute different SQL 349 Brought to you by ownSky Performing Updates Using a SQL-based JDBC approach we can't have true O/R mapping, which typically results in transparent updates when object properties change However, we can achieve efficient updates where mapping is inappropriate, such as updates affecting multiple rows and updates using stored procedures The following update object performs the same update as the JdbcTemplate update shown above, but conceals the SQL used and the parameter declarations in its constructor: class PerformanceCleaner extends com interface21.jdbc.object.SqlUpdate { public PerformanceCleaner(DataSource dataSource) { setSql("UPDATE seat_status SET booking_id = null " + "WHERE performance_id = ? AND price_band_id = ? " ) ; setDataSource (dataSource); declareParameter (new SqlParameter (Types NUMERIC) ) ; declareParameter (new SqlParameter (Types NUMERIC) ) ; compile ( ) ; } } public int clearBookings(int performanceld, int type) { return update (new Object [] { new Integer(performanceld) , new Integer(type) }); } The clearBookings ( ) method invokes the superclass update (Object [ ] ) method to execute with the given parameters, simplifying the API for callers - the same approach we've previously seen for query execute ( ) methods This update object can be used as follows: PerformanceCleaner pc = new PerformanceCleaner (dataSource) ; pc.clearBookings ( 1, ) ; Calling Stored Procedures As the stored procedure invocations in our sample application are fairly complex and use proprietary Oracle features, let's consider an example outside the sample application of calling a stored procedure that has two numeric input parameters and one output parameter The constructor is very similar to those of the queries and updates we've seen, declaring parameters i invoking the compile( ) method Note that the SQL is the name of the stored procedure: class Addlnvoice extends com.interface21.jdbc.object.StoredProcedure { public Addlnvoice (DataSource ds) { setDataSource(ds); setSql( "add_invoice" ); declareParameter(new SqlParameter( "amount" , Types.INTEGER)); declareParameter(new SqlParameter( "custid", Types.INTEGER)); declareParameter(new OutputParameter( "newid", Types.INTEGER)); compile( ); } 350 Brought to you by ownSky Practical Data Access We must implement a method to execute the stored procedure (Although I've used the name execute(), we could give this method any name.) The highlighted line is a call to the StoredProcedure class's protected execute (Map) method We invoke this with a Map of input parameters built from the new method's arguments We build a return value from a Map of output parameters returned by the execute() method This means that code using our stored procedure object can use strong typing For more complex stored procedures, arguments and return values could be of application object types: public int execute (int amount, int custid) { Map in = new HashMap(); in.put( "amount", new Integer(amount)); in.put( "custid", new Integer(custid)); Map out = execute(in) ; Number Id = (Number) out get ( "newid" ); return Id.intValue(); } } Just 20 lines of Java code, and we have an object that could implement an interface that's not stored procedure or JDBC-specific An improvement in JDBC 3.0 makes it possible to use named parameters, instead of indexed parameters, with the CallableStatement interface This brings into the JDBC API one of the features of the StoredProcedure class I've presented However, it still makes sense to use a higher level of abstraction than the JDBC API, as the error handling issue remains JDBC Abstraction Summary The abstractions we've just described aren't the only valid approach to making JDBC easier to work with However, they are much preferable to using JDBC directly and can drastically reduce the amount of code in applications and the likelihood of errors The biggest gains concern error handling We've seen how a generic hierarchy of data-access exceptions can give application code the ability to react to specific problems, while allowing it to ignore the majority of unrecoverable problems This also ensures that business objects don't require any knowledge of the persistence strategy (such as JDBC) used by the application We've looked at two levels of abstraction The com inter face21.jdbc.core package, which uses callback interfaces to enable JDBC workflow and error handling to be managed by the framework, makes JDBC easier to use, but leaves application code to work with JDBC PreparedStatements and ResultSets The com.interface21.jdbc.object package builds on the com.interface21.jdbc.core package to offer a higher level of abstraction in which RDBMS operations (queries, updates, and stored procedures) are modeled as reusable objects This is usually a better approach, as it localizes SQL operations within RdbmsOperation objects and makes code using them simple and largely self-documenting Unlike most O/R mapping approaches, these abstractions don't sacrifice any control over use of the RDBMS We can execute any SQL we like; we can easily execute stored procedures We've simply made JDBC easier to use 351 Brought to you by ownSky The alert reader will have spotted that I've ignored my own advice (in Chapter 4) about externalizing strings The classes shown above include SQL string literals - not even constants The desirability of separating SQL from Java code is questionable While we should always externalize configuration strings, SQL is not really configuration data, but code If the SQL changes, the behavior of the Java code that uses it may also change, so externalizing SQL strings may be unwise For example, imagine that we have two versions of the same query, one with FOR UPDATE suffixed These are not the same query they'll behave very differently As making it easy to change the SQL without changing the Java code can be dangerous, the SQL belongs in DAOs (In contrast, making it easy to change configuration without changing code is wholly beneficial.) Note that while we may not want to separate SQL from Java, we definitely want to localize all SQL in DAO implementations Not only does this make it easy to change the SQL if necessary, but it hides SQL from the rest of the application Application code using these JDBC abstraction packages is easy to test As all framework classes take connections from a datasource, we can easily provide a test datasource, enabling data access code to be tested without an application server As any code running within a J2EE server can obtain a managed DataSource from JNDI, we can use such DAOs inside or outside an EJB container, boosting architectural flexibility The framework code itself is relatively simple It doesn't attempt to solve really complex problems: it just removes the obstacles that make working with JDBC awkward and error-prone Hence, it doesn't have a steep learning curve For example, this approach doesn't provide any special handling for locking or concurrency Essentially, it lets us use SQL with a minimum of hassle It's up to us to ensure proper behavior for our target RDBMS Similarly, the JDBC abstraction interface doesn't make any effort at transaction management The "global" JTA API or EJB CMT should be used for managing transactions If we use the JDBC API to manage transactions we deprive theJ2EE server of the ability to enlist multiple resources in the same transaction and to roll back all operations automatically if necessary Floyd Marinescu describes the "Data Access Command Bean" pattern in EJB Design Patterns, giving examples and common superclasses for JDBC This approach has similar goals to the approach described here, but differs in that data-access objects are commands (intended for single use), the API is based on the javax.sql.RowSet interface, and application code is forced to catch exceptions when extracting each column value from the result set When working with many database columns, this will prove extremely verbose (Nor is there any concept of data store-agnostic exception handling: application code is left to use JDBC error handling directly.) The "Data Access Command Bean" approach is preferable to usingJDBC directly, but I believe that the approach described in this chapter is superior An RdbmsOperation is not a command While commands are typically created per use case, an RdbmsOperation is created once and reused However, the RdbmsOperation model is compatible with the Command design pattern Once created and configured, an RdbmsOperation can execute commands repeatedly 352 Brought to you by ownSky Practical Data Access Implementing the DAO Pattern in the Sample Application Armed with our generic JDBC infrastructure, let's look at implementing data access in our sample application Let's focus on the booking process To use the DAO pattern, we need to separate persistence logic operations from business logic operations The first step is to try to create a technology-agnostic DAO interface, which we can then implement with whatever persistence technology we choose We'll look at the implementation of business logic in the next chapter For now, let's look at the most important methods on the BoxOf f ice interface - the public interface of the ticket reservation system: public interface BoxOffice { int getSeatCount (int perf ormanceld); int getFreeSeatCount (int perf ormanceld) ; SeatQuote allocateSeats (SeatQuoteRequest request) throws NotEnoughSeatsException; // Additional methods omitted For now, we can ignore the details of the SeatQuote and SeatQuoteRequest parameter classes The PriceBand class is a read-only object that we'll examine below We want to keep the seat allocation algorithm in Java In this case, it's a simple exercise to separate out the persistence operations required into a DAO interface (Often this separation is harder to accomplish; occasionally it is impossible.) We must try to ensure that the interface, while not dictating a persistence strategy, allows for efficient implementation A suitable DAO interface will look like this: public interface BoxOfficeDAO { /** * @return collection of Seat objects */ List getAHSeats (int perf ormanceld) throws DataAccessException; /** * Sreturn list of PriceBand objects for this performance */ List getPriceBands (int perf ormanceld) throws DataAccessException; /** * @return list of Integer ids of free seats * @param lock guarantees that these seats will be available * to the current transaction */ 353 Brought to you by ownSky List getFreeSeats(int performanceld, int classld, boolean lock) throws DataAccessException; int getFreeSeatCount(int performanceld) throws DataAccessException; String reserve(SeatQuote quote); // Purchase operation omitted } Nothing in this interface ties us to using JDBC, Oracle, or even an RDBMS Each of these methods throws our generic com interface21.dao.DataAccessException, ensuring that even in the event of error, business objects using it don't need to work with RDBMS concepts The third, lock parameter to the getFreeSeats () method allows us to choose programmatically whether to lock the seats returned If this parameter is true, the DAO must ensure that the seats with the returned IDs are locked against reservation by other users, and are guaranteed to be able to be reserved in the current transaction The lock will be relinquished once the current transaction completes If this parameter is false, we want to query current availability without impacting on other users, to display information, rather than as part of the reservation process This interface doesn't encompass transaction management, which should be accomplished using JTA We plan to use a stateless session EJB with CMT to handle this declaratively, but don't need to worry about transaction management to implement and test the DAO Now we've defined a DAO interface, we should write tests against it Tests involving persistent data tend to be verbose, so we won't show the test classes here, but this step is very important Once we have a test harness, we can use it to test any DAO implementation; this will prove invaluable if we ever need to migrate to another persistence technology or database To create effective tests, we must: o o o Ensure that we create test data before each test run We must never run such tests against a production database We may create test data in the setup() method of a JUnit test case, or using Ant's SQL capability before the test case is run Write test methods that verify database work For each test, we will write code that checks the results of the operation in question, connecting directly to the database Using our JDBC abstraction API (which we can assume has already been tested) we can issue queries without writing too much code Ensure that we return the database to its old state We will have committed many transactions during the test run, so we won't be able to roll back, so this step may be complex We can us the tearDown() method of a JUnit test case or Ant or a similar tool We can write and run tests before we attempt a real implementation of our DAO interface, by using a dummy implementation in which all tests should fail (The dummy implementation's methods should d nothing, return null, or throw java.lang.UnsupportedOperationException to check the test harness Any IDE will help us create such a dummy implementation of an interface without manual coding.) Now that we have a complete test harness, let's look at implementing our DAO interface using JDBC (We could use SQLJ, but with our JDBC abstraction layer it's hardly necessary.) See the com.wrox.expertJ2ee.ticket.boxoffice.support.jdbc.OracleJdbcSeatingPlanDAO for a full listing of the implementation 354 Brought to you by ownSky Practical Data Access Using the object-based JDBC abstraction described above, we will create an RdbmsOperation object each query, update, and stored procedure invocation required All the RdbmsOperation objects used will be modeled as inner classes, as they should only be visible inside the OracleJdbcSeatingPlanDAO class This also has the advantage that the enclosing class's dataSource member variable ds is visible, simplifying object construction First let's consider the implementation of the PriceBand query, which returns lightweight read-only objects (an ideal application for JDBC): This shows a slightly more complex query than we've seen so far The fact that the SQL accounts for much of the object's code shows that we have a concise Java API! The query behind the getAllSeats ( ) method will be similar We declare a PriceBandQuery object as an instance variable in the OracleJdbcSeatingPlanDAO class, and initialize it in the constructor as shown below: private PriceBandQuery priceBandQuery; …… this.PriceBandQuery = new PriceBandQuery( ) ; Using the PriceBandQuery instance, the implementation of the getPriceBands() method from the BoxOfficeDAO interface is trivial: public List getPriceBands(int performanceld) { return (List) priceBandQuery.execute(performanceld); } Now let's consider the queries for available seats Remember that the database schema simplifies our task in two important respects: it provides the AVAILABLE_SEATS view to prevent us needing to perform an outer join to find available seats, and it provides the reserve_seats stored procedure that conceals the generation of a new primary key for the BOOKING table and the associated updates and inserts This makes the queries straightforward To get the ids of the free seats of a given type for a performance, we can run a query like the following against our view: 355 Brought to you by ownSky SELECT seat_id AS id FROM available_seats WHERE performance_id = ? AND price_band_id = ? To ensure that we honor the contract of the getFreeSeats ( ) method when the lock parameter is true, we need a query that appends FOR UPDATE to the select shown above These two queries are quite distinct, so I've modeled them as separate objects Two points to consider here: Oracle, reasonably enough, permits SELECT FOR UPDATE only in a transaction We must remember this, and ensure that we have a transaction, when testing this code; and the FOR UPDATE clause used against a view will correctly lock the underlying tables Both queries share the same parameters and extraction logic, which can be gathered in a common base class The usage in which an abstract superclass implements the extraction of each row of data, while subclasses vary the query's WHERE clause and parameters is very powerful; this is merely a trivial example The two queries, and their superclass, will look like this: The OracleJdbcSeatingPlanDAO constructor (after a DataSource is available) can create new objects of each of these two concrete classes like this: freeSeatsQuery = new FreeSeatsInPerf ormanceOfTypeQuery( ) ; freeSeatsLockingQuery = new LockingFreeSeatsInPerf ormanceOfTypeQuery( ) ; 356 Brought to you by ownSky Practical Data Access We can now query for free seats with either of these queries by calling the execute( int , int ) method from the SqlQuery superclass We use the lock parameter to the getFreeSeats( ) method from the seatingPlanDAO interface to choose which query to execute The complete implementation is method is: Calling the stored procedure to make a reservation is a little more complicated Although we've implicitly coded to Oracle by relying on our knowledge of Oracle's locking behavior, so far we haven't done anything proprietary As the PL/SQL stored procedure takes a table parameter, enabling the IDs of the seats to be reserved to be passed in a single database call, we need to jump through some Oracle-specific hoops First, let's consider what we need to in the database We need to define the following custom types: CREATE or REPLACE TYPE seatobj AS object (id NUMERIC); / CREATE or REPLACE TYPE seat_range AS table OF seatobj; / Now we can use the seat_range type as a table parameter to the following PL/SQL stored procedure: To pass a Java array as the table parameter to the stored procedure, we need to perform some Oracle-specific operations in our JDBC as well 357 Brought to you by ownSky The StoredProcedure superclass allows us not merely to pass in a Map to the execute( ) method, but also to pass in a callback interface that creates a parameter map given a Connection: protected interface ParameterMapper { Map createMap (Connection con) throws SQLException; This is necessary when we need the Connection to construct the appropriate parameters, as we in this case The reserve_seats stored procedure object needs to make the database types seat and seat_range available to JDBC, before executing the stored procedure These types can only be made available using an Oracle connection Apart from this, the process of creating an input parameter Map and extracting output parameters is as in the simple StoredProcedure example we looked at earlier: 358 Brought to you by ownSky Infrastructure and Application Implementation public interface ListableBeanFactory extends BeanFactory { String[] getBeanDefinitionNames(); int getBeanDefinitionCount(); String[] getBeanDefinitionNames(Class type); } The ability to find all bean definitions of a given class or interface can be very useful during initialization: for example, it allows the web application framework described in the next chapter to obtain references to all classes that can handle HTTP requests Often a bean needs to know when all its properties have been set, so that it can validate its state and/or perform initialization Our bean factory defines an approach for this If a bean created by a bean factory implements the com.interface21.beans.factory.InitializingBean interface, it will receive a callback after all properties have been set The InitializingBean interface includes a single method: public interface InitializingBean { void afterPropertiesSet( ) throws Exception; } A typical implementation of this method might check that one or more properties have been set to valid values, before performing initialization The following example checks that a jndiName property has been set before trying to perform a JNDI lookup: public final void af terPropertiesSet() throws Exception { if (this.jndiName == null | | this jndiName.equals( " " ) ) throw new Exception("Property 'jndiName' must be set on " + getClass().getName()); Object o = lookup(jndiName); initialize(o); } As all application objects will be created by a bean factory, we can use this in any application class that does not have its own lifecycle management solution Sometimes we may want to create a bean definition that performs custom processing before returning a bean instance For example, we might need a bean definition that registers the returned bean as a JMS listener; creates an object of a specified class and returns a dynamic proxy wrapping it; or returns a bean resulting from a method invocation on another object The bean factory concept supports "custom bean definitions", in which a custom bean definition class can perform any action before returning a bean A custom bean definition works as follows: o The bean factory loads the custom definition class, which must be specified The custom definition class must be a JavaBean o The bean factory sets properties on the custom definition class, which must be distinguished with a special syntax 405 Brought to you by ownSky o The configured custom bean definition class returns a BeanWrapper wrapping the returned bean Before returning a BeanWrapper, the custom definition class can perform any processing it likes o The bean factory sets properties on the returned BeanWrapper as if it were a normal bean definition The following custom bean definition illustrates this: com.wrox.expertj2ee.ticket.referencedata.support.DataUpdateJmsListener jms/TopicFactory jms/topic/referencedata calendar The definitionClass attribute of the element indicates that this is a custom bean definition, and supplies the custom definition class The elements set properties on the custom definition The meaning of properties will vary with custom definition class In this example, the listenerBean property specifies the class of the actual returned bean In the above list, the custom definition will create an instance of DataUpdateJmsListener and register it as a JMS listener The element sets the cachingCalendar property of the actual returned bean This mechanism makes the bean factory interface indefinitely extensible It's particularly valuable for concealing the complexity of J2EE API usage The Application Context The bean factory concept solves many problems, such those resulting from overuse of the Singleton of pattern However, we can move to a higher level of abstraction to help to pull applications together Application Context Goals Building on the bean factory, an application context provides a namespace for the JavaBeans that compose an application or subsystem, and the ability to share working objects at run time The com.interface21.context.ApplicationContext interface extends the ListableBeanFact interface, adding the ability to: 406 Brought to you by ownSky Infrastructure and Application Implementation o Publish events using the Observer design pattern As we discussed in Chapter 4, the Observer design pattern provides the ability to decouple application components and achieve clean separation of concerns o Participate in a hierarchy of application contexts Application contexts can have parent contexts, allowing developers to choose the amount of configuration shared between subsystems o Share working objects between application components at run time o Look up messages by string name, allowing support for internationalization o Facilitate testing With an application context, as well as a bean factory, it's often possible to provide a test implementation that enables application code to be tested outside an application server o Provide consistent configuration management in different types of applications Application contexts behave the same way in different kinds of applications, such as Swing GUI applications or web applications, and inside and outside a J2EE application server The most important added methods in the ApplicationContext interface are: ApplicationContext getParent( ); void publishEvent(ApplicationEvent e); void shareObject( String key, Object o); An application context is a directory of cooperating beans that offers some additional services The sample application uses the com.interface21.web.context.support.XmlWebApplicationContext ApplicationContext implementation, which is associated with a javax.servlet.ServletContext object provided by the web container, and which uses the XML bean definition format we've already seen However other implementations not require a web container, and allow testing (or the building of applications) without a web interface Each bean in the application context is given access to the application context if it requires It simply needs to implement the optional com.interface21.framework.context.ApplicationContextAware callback interface, enabling it to access other beans, publish events, or look up messages This interface contains only two methods, making it very simple to implement: public interface ApplicationContextAware { void setApplicationContext(ApplicationContext ctx) throws ApplicationContextException; ApplicationContext getApplicationContext(); } However, this capability shouldn't be used without good reason One of the strengths of the framework described here is that application code can benefit from it without depending on it This minimizes lock-in to the framework and retains the option of configuring the application differently 407 Brought to you by ownSky Although it is possible for beans to register for notification of the application context they operate within by implementing an optional interface, there are no special requirements for beans used within a bean factory of application context Each application context has a parent context If the parent context is null, it is the root of the application context hierarchy If the parent context is non-null, the current context will pass to the parent requests for beans it cannot find, and requests for messages it cannot resolve This enables us to determine precisely how much application state should be shared between different components The Observer support provided by an ApplicationContext is limited to the current server; it makes no effort to broadcast messages in a cluster (JMS Pub/Sub messaging should be used for this) However, a lightweight event publication framework limited to the current server is still very useful Often there's no need for messages to be broadcast more widely Consider, for example, a message that should result in the sending of an e-mail Using the Observer design pattern, we are able to add listeners that perform such tasks without complicating the code of business objects that generate such events Web Application Context An application of type com.interface21.web.context WebApplicationContext integrates with t h e javax.servlet.ServletContext In a web application, the application context hierarchy works as follows: o The /WEB-INF/applicationContext xml file defines the root context of the application Definitions here are available to all objects managed by the WebApplicationContext The root context is added to the ServletContext as an attribute, making it available to all web tier objects, including servlet filters and custom tags, which often fall outside the domain of application configuration management o Each servlet using the framework has its own application context, a child of the root context, defined by a file with a name of the form /WEB-INF/-servlet.xml The servlet name is the value of the subelement of the relevant element in the web application's web.xml file This allows us, for example, to have multiple controller servlets, each with a distinct namespace, but able to access global objects Our sample application uses only one servlet, with the name ticket, and requires no custom global configuration Hence all bean definitions are placed in the /WEB-INF/ticket-servlet.xml file Testing Implications The infrastructure I've described here is test-friendly: an important consideration Although it's possible to integrate an application context with the Servlet API and use a bean factory in an EJB, the core interfaces not depend on J2EE APIs Thus it is possible create a test application context enabling tests to be run against application objects without the need for aJ2EE server For example, a JUnit test case could create an XmlApplicationContext from the application context definition documents in a web application, and use it to get beans by name and test their behavior 408 Brought to you by ownSky Infrastructure and Application Implementation Summary of Application Configuration Infrastructure The following UML diagram illustrates the relationship between the framework classes and interfaces we've described: I've omitted some of the implementation classes; the aim is to show the public interfaces Note that application code doesn't need to work with these classes; it's possible to write an application that is "wired up" by an application context but doesn't depend on any framework classes The version of the framework used in the sample application doesn't support dynamic reconfiguration I concluded that the value of such functionality did not justify the complexity it would introduce However, it could be added if necessary; this approach is much more flexible than using singletons and other "hard-coded" approaches to configuration Later in this chapter we'll look in more detail at how the sample application's code is simplified by use of this framework 409 Brought to you by ownSky The infrastructure described above enables us to code to interfaces and never concrete classes, relying on a central configuration manager (an "application context") to "wire up" the application on initialization This completely removes configuration data and plumbing code from application objects, which merely need to expose JavaBean properties This also has the advantage that the application code is not dependent on the supporting infrastructure - a problem that affects many frameworks Managing API Complexity Now that we have a strong infrastructure for pulling our application together, let's move on to how infrastructure can be used to tackle the problem of J2EE API complexity In the following section we'll look at how several of the keyJ2EE APIs can be made much easier to use and their use less error-prone Implementing EJBs So long as we avoid using infrastructure components that violate the EJB specification (for example, by obtaining the current classloader, which rules out dynamic proxy solutions), there's nothing special about EJB code However, by deriving application EJB implementation classes from generic superclasses we can greatly simplify EJB code Abstract Superclasses for EJBs We should derive our EJBs from common superclasses that achieve the following goals: o Implement lifecycle methods that are irrelevant to bean implementation classes o Where possible, force EJBs to implement methods from the home interface, which aren't required by the interfaces EJBs must implement o Provide a consistent logging solution o Avoid the need for EJBs to use JNDI to look up environment variables, simplifying configuration management The following UML class diagram shows the hierarchy of EJB superclasses offered by our generic infrastructure, and how they relate to the interfaces required by the EJB specification All infrastructu classes are in the com interface21.ejb.support package We'll look at a complete code listing for each below Note that we don't attempt to provide superclasses for entity beans Entity beans should use CMP, and shouldn 't normally contain business logic, placing the onus on the container to provide infrastructure code 410 Brought to you by ownSky Infrastructure and Application Implementation Although there are quite a few classes here, each class is simple and the inheritance hierarchy enables us to support stateless and stateful session beans and message-driven beans without code duplication The classes above the horizontal line belong to the framework; those below the line show how application EJB implementation classes can extend these framework classes The root of the inheritance hierarchy - not used directly as a superclass by any application EJBs - is the AbstractEnterpriseBean class This implements the javax.ejb.EntityBean tag interface and provides standard logging and configuration management The following is a complete listing: 411 Brought to you by ownSky The protected logger instance variable uses the Java 1.4 logging emulation package discussed in Chapter 4, or standard Java 1.4 logging if available The getBeanFactory ( ) method lazily loads a bean factory from environment variables This is very useful, as it avoids the need to use JNDI to look up environment variables, yet allows parameterization at EJB deployment time We can instantiate and configure helper objects such as DAOs, and put configuration variables in a simple bean We've replaced a low-level API with the same high-level API we use throughout our applications By facilitating factoring functionality into helper classes (rather than EJB code) we promote good practice and improve testability We can often test helper classes used in EJBs without an EJB container For example, the following environment variables define a DAO for the sample application's BoxOffice EJB: beans.dao.class java.lang.String com.wrox.expertJ2ee.ticket.boxoff ice.support.jdbc JBossBOOracleJdbcBoxOf ficeDao < / env-entry> This particular DAO doesn't require any properties, but these can be set using our bean factory conventions Simply by editing the ejb-jar xml deployment descriptor we can change the class DAO helper to any other object that implements the com.wrox.expertJ2ee.ticket.boxoffice.support.BoxOfficeDao interface, without need to change EJB code 412 Brought to you by ownSky Infrastructure and Application Implementation The EJB specification requires all session beans to implement the javax.ejb.SessionBean interface, shown below This includes the setSessionContext ( ) method and other lifecycle methods: Thus all session beans can be derived from a simple subclass of our AbstractEnterpriseBean base class that implements the setSessionContext( ) to store the SessionContext in an instance variable, and exposes the saved context via a protected getSessionContext( ) method We also add an empty implementation of the required ejbRemove( ) lifecycle method, which can be overridden if necessary: Stateful session beans can subclass this class directly Stateful session beans often don't need to implement the ejbRemove( ) method, but they always need to ensure correct behavior on the ejbPassivate( ) and ejbActivate( ) lifecycle methods We discussed how to implement these methods correctly in Chapter 10 Hence we leave these methods unimplemented in AbstractSessionBean 413 Brought to you by ownSky By contrast, it is illegal for an EJB container to invoke the ejbPassivate() and ejbActivate() lifecycle methods on stateless session beans, although stateless session bean implementation classes are (illogically) required to implement them Thus stateless session beans can be derived from a simple subclass of AbstractSessionBean, which implements these two lifecycle methods to throw an exception There is no reason for any subclass to override this implementation, although we can't make these methods final, as it violates the EJB specification Our superclass for stateless session beans also forces subclasses to implement a no-argument ejbCreate() method - matching the required create() method on the home interface - by defining this as an abstract method This moves to compile time a check that would otherwise only take place on EJB deployment, as this method isn't required by the EJB API Here is the complete listing of the AbstractStatelessSessionBean class, which should be used as a superclass by SLSB implementation classes: This leaves SLSB subclasses to implement an ejbCreate() method and their business methods Remember that application EJBs will usually implement a "business methods" interface, ensuring that they match the methods defined on their component interface In rare situations, an SLSB may overridi the ejbRemove() method defined in AbstractSessionBean The implementation of the ejbCreate() method may use the bean factory available from the AbstractEnterpriseBean superclass to create helper objects such as DAOs These superclasses can be used by EJBs with local o remote interfaces or both Let's look at an example, showing how the business methods interface pattern can be combined will our generic superclass to ensure that the compiler, not just deployment tools, enforces that an SLsB implementation class is valid The following business methods interface defines the remote method hypothetical EJB: 414 Brought to you by ownSky Infrastructure and Application Implementation Following the business methods pattern described in the last chapter, the EJB's remote interface will contain no methods of its own, but will extend both this interface and javax.ejb.EJBObject: import javax.ejb.EJBObject; public interface CalculatorRemote extends EJBObject, Calculator { } The bean implementation class will implement the business methods interface and extend AbstractStatelessSessionBean This means that the compiler will force us to implement all the methods required for a valid EJB I've highlighted the lines in the following listing showing how the ejbCreate() method can be implemented to load a helper object from the bean factory exposed by the AbstractEnterpriseBean base class: Our superclasses have delivered real value This EJB implementation class contains no code irrelevant to its business purpose, and is easily able to load helper objects whose configuration is held entirely outside Java code Let's finish by considering message-driven beans (MDB) MDB implementation classes must also meet some requirements beyond implementing the javax.ejb.MessageDrivenBean interface: they must also implement an ejbCreate() method without arguments; and they must implement the Javax jms MessageListener interface to handle JMS messages We can extend AbstractEnterpriseBean to save the MessageDrivenContext and force subclasses to implement an ejbCreate() method as follows: package com interface21.e jb.support ; import javax.ejb.MessageDrivenBean; import javax.e jb.MessageDrivenContext ; public abstract class AbstractMessageDrivenBean extends AbstractEnterpriseBean implements MessageDrivenBean { 415 Brought to you by ownSky Although EJB 2.0 supports onlyJMS messaging, EJB 2.1 supports JAXM messaging as well, meaning that the javax jms MessageListener is only one of three alternative messaging interfaces that an EJB 2.1 MDB might implement Thus, rather than tie the AbstractMessageDrivenBean class to use of JMS, we ensure forward compatibility with EJB 2.1 by putting the requirement to implement javax jms MessageListener in aJMS-specific subclass: package com.interface21.ejb.support; import javax.jms.MessageListener; public abstract class AbstractJmsMessageDrivenBean extends AbstractMessageDrivenBean implements MessageListener { / / Empty } An application MDB that subclasses this class is guaranteed to meet the requirements of an EJB 2.0 JMS MDB A trivial subclass automatically stubbed by an IDE will look like this: public class SimpleMDB extends AbstractJmsMessageDrivenBean { public void ejbCreate() { public void onMessage(Message message) { } As with session beans, the ejbCreate() method can access the bean factory provided by the AbstractEnterpriseBean superclass 416 Brought to you by ownSky Infrastructure and Application Implementation Of course we don't need to use these (or similar) superclasses when implementing EJBs, but as they simplify application code and reduce the likelihood of errors resulting in wasted development-deploy cycles, they provide very good value There's seldom any problem in the use of concrete inheritance depriving application EJBs of their own inheritance hierarchy Due to the EJB programming restrictions, making an object an EJB by subclassing it to implement the required lifecycle methods is rarely a good idea Our solution, however, makes it easy for a new EJB to use existing code, through using existing classes as helper objects instantiated through its bean factory Custom application EJB superclasses can simply subclass one of the above framework superclasses to add additional application-specific behavior Accessing EJBs It's important to achieve loose coupling between EJBs and code that uses them There are many disadvantages in EJB client code accessing EJBs directly: o Client code must deal with JNDI lookups in multiple places Obtaining and using EJB references is cumbersome We need to a JNDI lookup for the EJB home interface and invoke a create method on the home interface o o EJB JNDI names will be held in multiple places, making it difficult to modify client code o o Clients are too closely coupled to the EJB tier, making it difficult to change the interfaces it exposes It's impossible to introduce a consistent caching strategy for application data returned by the EJB tier or server resources such as naming contexts and EJB home interface references Especially in distributed applications, caching the result of invoking EJBs may be essential to meet performance requirements Client code must deal with low-level checked exceptions from the EJB tier For both local and remote EJBs there are JNDI NamingExceptions and EJBCreate exceptions For remote EJBs every call on home or EJB may throw java.rmi.RemoteException Except for remote exceptions (which we'll consider below) this is a lot of exception handling to achieve very little In a local application, failure to look up an EJB home or create an stateless session EJB using a create () method that has no arguments that could be invalid almost certainly indicates serious application failure Most likely we'll want to log this and gracefully inform the user that their action cannot be processed at this time due to an internal error Most importantly, if we allow code to work with EJB access APIs directly, we tie code unnecessarily to a particular implementation strategy Abstract inter-tier communication with Java interfaces, not on J2EE technologies such as EJB Technologies are features of implementations Two well-known J2EE patterns - the Service Locator and Business Delegate patterns - address these problems In this section, we'll discuss their benefits and how to implement them As with implementing EJBs, generic infrastructure classes can be used to simplify application code We can integrate EJB access with our overall application infrastructure by making service locators and business delegates JavaBeans, configured by an application bean factory Local versus Remote Access There's a profound difference between accessing EJBs through local and remote interfaces 417 Brought to you by ownSky Invoking an EJB through a local interface isn't radically different from invoking an ordinary Java object The EJB container will intercept each method call, but both caller and EJB are running in the same JVM, meaning that there is no possibility of distributed failures and no serialization issues Remote invocation is a completely different problem We may encounter distributed failures, and we must consider efficiency: "chatty" calling, or exchanging an excessive amount of parameter data, will prove disastrous for performance We can choose between two fundamental strategies for remote invocation: we can try to achieve local-remote transparency, concealing from callers the issue of remote invocation; or we can use a client-side facade to provide a locally accessible point of contact for the EJB invocation I don't advocate local-remote transparency As the designers of Java RMI didn't either, it's relatively hard to achieve when invoking EJBs Secondly, it's an invitation to gross inefficiency It's much better to provide a local facade through which all communication with the EJB tier passes This is an excellent application for the Business Delegate pattern, which is discussed below The Service Locator and Business Delegate J2EE Patterns The first step in decoupling calling code from an EJB implementation is to ensure that all EJB references are obtained through a common factory This avoids the duplication of JNDI lookup code, and enables the caching of the results of JNDI lookups to boost performance (although the overhead varies between servers, obtaining a naming context and looking up a home interface are potentially expensive operations) Such an object is called a service locator (CoreJ2EE Patterns) The disadvantage is that clients will still need to work directly with the EJBs They must still handle remote exceptions in a distributed application and may need to catch exceptions thrown when creating EJBs However, a service locator can return references to SLSB EJB instances, rather than merely home interfaces (the service locator can invoke the no argument create () method as necessary) This frees clients of the need to handle the checked exceptions that may be thrown by attempting to create an EJB from a home interface When this is possible, clients needn't know that EJB is being used to implement business interfaces T is clearly desirable; unless we use EJB to implement a distributed model, there's no reason to view EJB as more than a helpful framework for particular implementations of business interfaces The Service Locator pattern works well with local EJB access Let's consider two approaches to service location where SLSBs are concerned Using a Typed Service Locator to Access EJBs In the first approach, we use a dedicated, thread-safe service locator for each SLSB Each service locator implements a factory interface that isn't EJB-specific: a method that can be implemented t return any implementation of the relevant business interface When an EJB is used to implemen interface, the object returned will be an EJB reference, and the EJB's component interface will e the business interface In my preferred implementation of this approach, the factory method throws a runtime exception checked exception, as failure to create a business object is likely to be fatal If it's possible to retry service locator can retry before throwing an exception to callers 418 Brought to you by ownSky Infrastructure and Application Implementation Each service locator implementation will need to perform a JNDI lookup to cache the home interface for the relevant EJB This suggests the use of generic infrastructure classes to conceal the use of JNDI Our framework includes convenient support for this; application service locators need merely to subclass the com.interface21.ejb.access.AbstractLocalStatelessSessionServiceLocator class and implement the abstract setEjbHome() method as well as the relevant factory interface The com.interface21.ejb.access.AbstractLocalStatelessSessionServiceLocator class extends the com.interface21.jndi.AbstractJndiLocator class, which can be used to perform any JNDI lookup and cache the result This allows the Service Locator pattern to be used not only for EJB access, but also to look up and cache any resource obtained through JNDI The AbstractJndiLocator class is designed for use as a Java bean in our application framework The following is a complete listing: package com interface21 jndi; import java14.java.util.logging.Logger; import javax naming NamingExcept ion; import com interface21.beans.factory.InitializingBean; public abstract class AbstractJndiLocator implements InitializingBean { protected final Logger logger = Logger.getLogger(getClass().getName( ) ); private static String PREFIX = "java:comp/env/"; private String jndiName; public Abstract JndiLocator() { } The JndiName property setter enables the JNDI name of the EJB or other resource to be held outside Java code: The implementation of the afterPropertiesSet( ) method, which is invoked by the bean factory after all properties have been set, performs the JNDI lookup If the object is successfully located, the afterPropertiesSet ( ) method invokes the abstract template method located ( ) with the object Subclasses must implement this to perform their own initialization, based on the type of object If the lookup fails, the resulting javax.Naming.NamingExcept ion is thrown to be handled by the application framework (this will be considered a fatal initialization exception): 419 Brought to you by ownSky ... public interface CommandExecutor { Command executeCommand(Command command) throws RemoteException, CommandException; } An SFSB remote interface might extend this (the executeCommand () method has... code Advantages and Disadvantages of the EJB Command Design Pattern The EJB Command design pattern has the following advantages: o It delivers the usual benefits of the GoF Command design pattern,... PriceBandQuery object as an instance variable in the OracleJdbcSeatingPlanDAO class, and initialize it in the constructor as shown below: private PriceBandQuery priceBandQuery; …… this.PriceBandQuery