Creating and testing layered applications (Hibernate)

 

Hibernate is intended to be used in just about any architectural scenario imaginable. Hibernate may run inside a servlet container; you can use it with web application framework like Struts, WebWork, or Tapestry, or inside an EJB container, or to manage persistent data in a Java Swing application.

Even—perhaps especially—with all these options, it’s often difficult to see exactly how Hibernate should be integrated into a particular Java-based architecture. Inevitably, you’ll need to write infrastructural code to support your own application design. In this topic, we describe common Java architectures and show how Hibernate can be integrated into each scenario.

We discuss how you design and create layers in a typical request/response based web application, and how you separate code by functionality. After this, we introduce Java EE services and EJBs and show how managed components can make your life easier and reduce the infrastructure coding that would otherwise be necessary.

Finally, we assume that you’re also interested in testing your layered application, with or without managed components. Today, testing is one of the most important activities in a developer’s work, and applying the right tools and strategies is essential for quick turnaround times and productivity (not to mention the quality of the software). We’ll look at unit, functional, and integration testing with our current favorite testing framework, TestNG.

Let’s start with a typical web application example.

Hibernate in a web application

Layering helps achieve separation of concerns, making code more readable by grouping code that does similar things. Layering, however, carries a price. Each extra layer increases the amount of code it takes to implement a simple piece of functionality—and more code makes the functionality more difficult to change.

In this section, we show you how to integrate Hibernate in a typical layered application. We assume that you want to write a simple web application with Java servlets. We need a simple use case of the CaveatEmptor application to demonstrate these ideas.

Introducing the use case

When a user places a bid on an item, CaveatEmptor must perform the following tasks, all in a single request:

1 Check that the amount entered by the user is greater than the maximum amount of existing bids for the item.

2 Check that the auction hasn’t yet ended.

3 Create a bid for the item.

4 Inform the user of the outcome of the tasks.

If either checks fail, the user should be informed of the reason; if both checks are successful, the user should be informed that the bid has been placed. These checks are the business rules. If a failure occurs while accessing the database, users should be informed that the system is currently unavailable (an infrastructure concern).

Let’s see how you can implement this in a web application.

Writing a controller

Most Java web applications use some kind of Model/View/Controller (MVC) application framework; even many that use plain servlets follow the MVC pattern by using templating to implement the presentation code, separating application control logic into a servlet or multiple servlets.

You’ll now write such a controller servlet that implements the previously introduced use case. With an MVC approach, you write the code that implements the “place bid” use case in an execute() method of an action named PlaceBidAc-tion. Assuming some kind of web framework, we don’t show how to read request parameters or how to forward to the next page. The code shown may even be the implementation of a doPost() method of a plain servlet.

The first attempt at writing such a controller, shown in listing 16.1, mixes all concerns in one place—there are no layers.

Listing 16.1 Implementing a use case in one execute() method

Listing 16.1 Implementing a use case in one execute() method tmp1DC330_thumb

O You get a Session using the current persistence context and then start a database transaction. A new database transaction is started on the current Session.

C You load the Item from the database, using its identifier value.

D If the ending date of the auction is before the current date, you forward to an error page. Usually you want a more sophisticated error handling for this exception, with a qualified error message.

Q Using an HQL query, you check whether there is a higher bid for the current item in the database. If there is one, you forward to an error message.

0 If all checks are successful, you place the new bid by adding it to the item. You don’t have to save it manually—it’s saved using transitive persistence (cascading from the Item to Bid).

G The new Bid instance needs to be stored in some variable that is accessible by the following page, so you can display it to the user. You can use an attribute in the servlet request context for this.

H Committing the database transaction flushes the current state of the Session to the database and closes the current Session automatically.

1 If any RuntimeException is thrown, either by Hibernate or by other services, you roll back the transaction and rethrow the exception to be handled appropriately outside the controller.

The first thing wrong with this code is the clutter caused by all the transaction and exception-handling code. Because this code is typically identical for all actions, you would like to centralize it somewhere. One option is to place it in the execute!) method of some abstract superclass of your actions. You also have a problem with lazy initialization, if you access the new bid on the success page, pulling it out of the request context for rendering: The Hibernate persistence context is closed and you can no longer load lazy collections or proxies.

Let’s start cleaning up this design and introduce layers. The first step is to enable lazy loading on the success page by implementing the Open Session in View pattern.

The Open Session in View pattern

The motivation behind the Open Session in View (OSIV) pattern is that the view pulls information from business objects by navigating the object network beginning at some detached object—for example, the newly created Bid instance that was placed in the request context by your action. The view—that is, the page that must be rendered and displayed—accesses this detached object to get the content data for the page.

In a Hibernate application, there may be uninitialized associations (proxies or collections) that must be traversed while rendering the view. In this example, the view may list all items sold by the bidder (as part of an overview screen) by calling newBid.getBidder().getItems().iterator(). This is a rare case but certainly a valid access. Because the items collection of the User is loaded only on demand (Hibernate’s lazy association and collection default behavior), it isn’t initialized at this point. You can not load uninitialized proxies and collections of an entity instance that is in detached state.

If the Hibernate Session and therefore the persistence context is always closed at the end of the action’s execute() method, Hibernate throws a LazyIni-tializationException when this unloaded association (or collection) is accessed. The persistence context is no longer available, so Hibernate can’t load the lazy collection on access.

FAQ Why can’t Hibernate open a new Session if it has to lazy load objects? The Hibernate Session is the persistence context, the scope of object identity. Hibernate guarantees that there is at most one in-memory representation of a particular database row, in one persistence context. Opening a Session on-demand, behind the scenes, would also create a new persistence context, and all objects loaded in this identity scope would potentially conflict with objects loaded in the original persistence context. You can’t load data on-demand when an object is out of the guaranteed scope of object identity—when it’s detached. On the other hand, you can load data as long as the objects are in persistent state, managed by a Session, even when the original transaction has been committed. In such a scenario, you have to enable the autocommit mode.We recommend that you don’t use the autocommit mode in a web application; it’s much easier to extend the original Session and transaction to span the whole request. In systems where you can’t easily begin and end a transaction when objects have to be loaded on-demand inside a Session, such as Swing desktop applications that use Hibernate, the autocommit mode is useful.

A first solution would be to ensure that all needed associations and collections are fully initialized before forwarding to the view (we discuss this later), but a more convenient approach in a two-tiered architecture with a colocated presentation and persistence layer is to leave the persistence context open until the view is completely rendered.

The OSIV pattern allows you to have a single Hibernate persistence context per request, spanning the rendering of the view and potentially multiple action exe-cute() s. It can also be implemented easily—for example, with a servlet filter:

tmp1DC331_thumbtmp1DC332_thumb1

This filter acts as an interceptor for servlet requests. It runs every time a request hits the server and must be processed. It needs the SessionFactory on startup, and it gets it from the HibernateUtil helper class. When the request arrives, you start a database transaction and open a new persistence context. After the controller has executed and the view has been rendered, you commit the database transaction. Thanks to Hibernate’s auomatic Session binding and propagation, this is also automatically the scope of the persistence context.

Exception handling has also been centralized and encapsulated in this interceptor. It’s up to you what exception you’d like to catch for a rollback of the database transaction; Throwable is the catch-all variation, which means that even thrown Errors, not only Exceptions and RuntimeExceptions, trigger a rollback. Note that the actual rollback can also throw an error or exception—always make sure (for example, by printing out the stack trace) that this secondary exception doesn’t hide or swallow the original problem that led to the rollback.

The controller code is now free from transaction and exception handling and already looks much better:

tmp1DC333_thumb

The current Session returned by the SessionFactory is the same persistence context that is now scoped to the interceptor wrapping this method (and the rendering of the result page).

Refer to your web container’s documentation to see how you can enable this filter class as an interceptor for particular URLs; we recommend that you apply it only to URLs that require database access during execution. Otherwise, a database transaction and Hibernate Session is started for every HTTP request on your server. This can potentially exhaust your database connection pool, even if no SQL statements are sent to the database server.

You can implement this pattern any way you like, as long as you have the ability to intercept requests and to wrap code around your controller. Many web frameworks offer native interceptors; you should use whatever you find most appealing. The implementation shown here with a servlet filter isn’t free of problems.

Changes made to objects in the Session are flushed to the database at irregular intervals and finally when the transaction is committed. The transaction commit may occur after the view has been rendered. The problem is the buffer size of the servlet engine: If the contents of the view exceed the buffer size, the buffer may get flushed and the contents sent to the client. The buffer may be flushed many times when the content is rendered, but the first flush also sends the HTTP protocol status code. If the SQL statements on Hibernate flush/commit trigger a constraint violation in the database, the user may already have seen a successful output! You can’t change the status code (for example, use a 500 Internal Server Error); it’s already been sent to the client (as 200 OK).

There are several ways to prevent this rare exception: Adjust the buffer size of your servlets, or flush the Session before forwarding/redirecting to the view. Some web frameworks don’t immediately fill the response buffer with rendered content—they use their own buffer and flush it only with the response after the view has been completely rendered, so we consider this a problem with plain Java servlet programming.

Let’s continue with the cleanup of the controller and extract the business logic into the business layer.

Designing smart domain models

The idea behind the MVC pattern is that control logic (in the example application, this is pageflow logic), view definitions, and business logic should be cleanly separated. Currently, the controller contains some business logic—code that you may be able to reuse in the admittedly unlikely event that your application gains a new user interface—and the domain model consists of dumb data-holding objects. The persistent classes define state, but no behavior.

We suggest you migrate the business logic into the domain model, creating a business layer. The API of this layer is the domain model API. This adds a couple of lines of code, but it also increases the potential for later reuse and is more object-oriented and therefore offers various ways to extend the business logic (for example, using a strategy pattern for different bid strategies if suddenly you need to implement “lowest bid wins”). You can also test business logic independently from pageflow or any other concern.

First, add the new method placeBid() to the Item class:

tmp1DC334_thumbtmp1DC335_thumb

This code basically performs all checks that need the state of the business objects but don’t execute data-access code. The motivation is to encapsulate business logic in classes of the domain model without any dependency on persistent data access or any other infrastructure. Keep in mind that these classes should know nothing about persistence, because you may need them outside of the persistence context (for example, in the presentation tier or in a logic unit test).

You moved code from the controller to the domain model, with one noteworthy exception. This code from the old controller couldn’t be moved as is:

tmp1DC336_thumb

You’ll frequently face the same situation in real applications: Business logic is mixed with data-access code and even pageflow logic. It’s sometimes difficult to extract only the business logic without any dependencies. If you now look at the solution, the introduction of currentMaxBid and currentMinBid parameters on the Item.placeBid() method, you can see how to solve this kind of problem. Pageflow and data-access code remains in the controller but supplies the required data for the business logic:

tmp1DC337_thumbtmp1DC338_thumb

The controller is now completely unaware of any business logic—it doesn’t even know whether the new bid must be higher or lower than the last one. You have encapsulated all business logic in the domain model and can now test the business logic as an isolated unit without any dependency on actions, pageflow, persistence, or other infrastructure code (by calling the Item.placeBid() in a unit test).

You can even design a different pageflow by catching and forwarding specific exceptions. The BusinessException is a declared and checked exception, so you have to handle it in the controller in some way. It’s up to you if you want to roll back the transaction in this case, or if you have a chance to recover in some way. However, always consider the state of your persistence context when handling exceptions: There may be unflushed modifications from a previous attempt present when you reuse the same Session after an application exception. (Of course, you can never reuse a Session that has thrown a fatal runtime exception.) The safe way is to always roll back the database transaction on any exception and to retry with a fresh Session.

The action code looks good already. You should try to keep your architecture simple; isolating exception and transaction handling and extracting business logic can make a significant difference. However, the action code is now bound to Hibernate, because it uses the Session API to access the database. The MVC pattern doesn’t say much about where the P for Persistence should go.

Creating a persistence layer

Mixing data-access code with application logic violates the emphasis on separation of concerns. There are several reasons why you should consider hiding the Hibernate calls behind a facade, the so-called persistence layer:

■ The persistence layer can provide a higher level of abstraction for data-access operations. Instead of basic CRUD and query operations, you can expose higher-level operations, such as a getMaximumBid() method. This abstraction is the primary reason why you want to create a persistence layer in larger applications: to support reuse of the same non-CRUD operations.

■ The persistence layer can have a generic interface without exposing actual implementation details. In other words, you can hide the fact that you’re using Hibernate (or Java Persistence) to implement the data-access operations from any client of the persistence layer. We consider persistence layer portability an unimportant concern, because full object/relational mapping solutions like Hibernate already provide database portability. It’s highly unlikely that you’ll rewrite your persistence layer with different software in the future and still not want to change any client code. Furthermore, consider Java Persistence as a standardized and fully portable API.

■ The persistence layer can unify data-access operations. This concern is related to portability, but from a slightly different angle. Imagine that you have to deal with mixed data-access code, such as Hibernate and JDBC operations. By unifying the facade that clients see and use, you can hide this implementation detail from the client.

If you consider portability and unification to be side effects of creating a persistence layer, your primary motivation is achieving a higher level of abstraction and the improved maintainability and reuse of data-access code. These are good reasons, and we encourage you to create a persistence layer with a generic facade in all but the simplest applications. It’s again important that you don’t overengineer your system and that you first consider using Hibernate (or Java Persistence APIs) directly without any additional layering. Let’s assume you want to create a persistence layer and design a facade that clients will call.

There is more than one way to design a persistence layer facade—some small applications may use a single PersistenceManager object; some may use some kind of command-oriented design, and others mix data-access operations into domain classes (active record)—but we prefer the DAO pattern.

A generic data-access object pattern

The DAO design pattern originated in Sun’s Java Blueprints. It’s even used in the infamous Java Petstore demo application. A DAO defines an interface to persistence operations (CRUD and finder methods) relating to a particular persistent entity; it advises you to group together code that relates to persistence of that entity.

Using JDK 5.0 features such as generics and variable arguments, you can design a nice DAO persistence layer easily. The basic structure of the pattern we’re proposing here is shown in figure 16.1.

Generic DAO interfaces support arbitrary implementations

Figure 16.1

Generic DAO interfaces support arbitrary implementations

We designed the persistence layer with two parallel hierarchies: interfaces on one side, implementations on the other side. The basic object-storage and -retrieval operations are grouped in a generic superinterface and a superclass that implements these operations with a particular persistence solution (we’ll use Hibernate). The generic interface is extended by interfaces for particular entities that require additional business-related data-access operations. Again, you may have one or several implementations of an entity DAO interface.

Let’s first consider the basic CRUD operations that every entity shares and needs; you group these in the generic superinterface:

tmp1DC340_thumbtmp1DC341_thumb

The GenericDAO is an interface that requires type arguments if you want to implement it. The first parameter, T, is the entity instance for which you’re implementing a DAO. Many of the DAO methods use this argument to return objects in a type-safe manner. The second parameter defines the type of the database identifier—not all entities may use the same type for their identifier property. The second thing that is interesting here is the variable argument in the findByExample() method; you’ll soon see how that improves the API for a client.

Finally, this is clearly the foundation for a persistence layer that works state-oriented.. Methods such as makePersistent() and makeTransient() change an object’s state (or many objects at once with cascading enabled). The flush!) and clear!) operations can be used by a client to manage the persistence context. You’d write a completely different DAO interface if your persistence layer were statement-oriented; for example if you weren’t using Hibernate to implement it but only plain JDBC.

The persistence layer facade we introduced here doesn’t expose any Hibernate or Java Persistence interface to the client, so theoretically you can implement it with any software without making any changes to client code. You may not want or need persistence layer portability, as explained earlier. In that case, you should consider exposing Hibernate or Java Peristence interfaces—for example, a findByCriteria(DetachedCriteria) method that clients can use to execute arbitrary Hibernate Criteria queries. This decision is up to you; you may decide that exposing Java Persistence interfaces is a safer choice than exposing Hibernate interfaces. However, you should know that while it’s possible to change the implementation of the persistence layer from Hibernate to Java Persistence or to any other fully featured state-oriented object/relational mapping software, it’s almost impossible to rewrite a persistence layer that is state-oriented with plain JDBC statements.

Next, you implement the DAO interfaces.

Implementing the generic CRUD interface

Let’s continue with a possible implementation of the generic interface, using Hibernate APIs:

tmp1DC342_thumb

So far this is the internal plumbing of the implementation with Hibernate. In the implementation, you need access to a Hibernate Session, so you require that the client of the DAO injects the current Session it wants to use with a setter method. This is mostly useful in integration testing. If the client didn’t set a Session before using the DAO, you look up the current Session when it’s needed by the DAO code.

The DAO implementation must also know what persistent entity class it’s for; you use Java Reflection in the constructor to find the class of the T generic argument and store it in a local member.

If you write a generic DAO implementation with Java Persistence, the code looks almost the same. The only change is that an EntityManager is required by the DAO, not a Session.

You can now implement the actual CRUD operations, again with Hibernate:

tmp1DC343_thumb2

tmp1DC344_thumb

All the data-access operations use getSession() to get the Session that is assigned to this DAO. Most of these methods are straightforward, and you shouldn’t have any problem understanding them after reading the previous topics of this topic. The @SurpressWarning annotations are optional—Hibernate interfaces are written for JDKs before 5.0, so all casts are unchecked and the JDK 5.0 compiler generates a warning for each otherwise. Look at the protected find-ByCriteria() method: We consider this a convenience method that makes the implementation of other data-access operations easier. It takes zero or more Criterion arguments and adds them to a Criteria that is then executed. This is an example of JDK 5.0 variable arguments. Note that we decided not to expose this method on the public generic DAO interface; it’s an implementation detail (you may come to a different conclusion).

An implementation with Java Persistence is straightforward, although it doesn’t support a Criteria API. Instead of saveOrUpdate(), you use merge!) to make any transient or detached object persistent, and return the merged result.

You’ve now completed the basic machinery of the persistence layer and the generic interface it exposes to the upper layer of the system. In the next step, you create entity-related DAO interfaces and implement them by extending the generic interface and implementation.

Implementing entity DAOs

Let’s assume that you want to implement non-CRUD data-access operations for the Item business entity. First, write an interface:

tmp1DC345_thumb

The ItemDAO interface extends the generic super interface and parameterizes it with an Item entity type and a Long as the database identifier type. Two data-access operations are relevant for the Item entity: getMaxBid() and getMinBid().

An implementation of this interface with Hibernate extends the generic CRUD implementation:

tmp1DC346_thumb

You can see how easy this implementation was, thanks to the functionality provided by the superclass. The queries have been externalized to mapping metadata and are called by name, which avoids cluttering the code.

We recommend that you create an interface even for entities that don’t have any non-CRUD data-access operations:

tmp1DC347_thumb

The implementation is equally straightforward:

tmp1DC348_thumb

We recommend this empty interface and implementation because you can’t instantiate the generic abstract implementation. Furthermore, a client should rely on an interface that is specific for a particular entity, thus avoiding costly refactor-ing in the future if additional data-access operations are introduced. You might not follow our recommendation, however, and make GenericHibernateDAO non-abstract. This decision depends on the application you’re writing and what changes you expect in the future.

Let’s bring this all together and see how clients instantiate and use DAOs.

Using data-access objects

If a client wishes to utilize the persistence layer, it has to instantiate the DAOs it needs and then call methods on these DAOs. In the previously introduced Hibernate web application use case, the controller and action code look like this:

tmp1DC349_thumb3

You almost manage to avoid any dependency of controller code on Hibernate, except for one thing: You still need to instantiate a specific DAO implementation in the controller. One (not very sophisticated) way to avoid this dependency is the traditional abstract factory pattern.

First, create an abstract factory for data-access objects:

tmp1DC350_thumbtmp1DC351_thumb

This abstract factory can build and return any DAO. Now implement this factory for your Hibernate DAOs:

tmp1DC352_thumb

Several interesting things happen here. First, the implementation of the factory encapsulates how the DAO is instantiated. You can customize this method and set a Session manually before returning the DAO instance.

Second, you move the implementation of CommentDAOHibernate into the factory as a public static class. Remember that you need this implementation, even if it’s empty, to let clients work with interfaces related to an entity. However, nobody forces you to create dozens of empty implementation classes in separate files; you can group all the empty implementations in the factory. If in the future you have to introduce more data-access operations for the Comment entity, move the implementation from the factory to its own file. No other code needs to be changed— clients rely only on the CommentDAO interface.

With this factory pattern, you can further simplify how DAOs are used in the web application controller:

tmp1DC353_thumb 

Introducing the Command pattern

The patterns and strategies introduced in the previous sections are perfect if you have to write a small to medium sized web application with Hibernate and Java Persistence. The OSIV pattern works in any two-tiered architecture, where the presentation, business, and persistence layers are colocated on the same virtual machine.

However, as soon as you introduce a third tier and move the presentation layer to a separate virtual machine, the current persistence context can’t be held open anymore until the view has been rendered. This is typically the case in three-tiered EJB application, or in an architecture with a rich client in a separate process.

If the presentation layer runs in a different process, you need to minimize the requests between this process and the tier that runs the business and persistence layers of the application. This means that you can’t use the previous lazy approach, where the view is allowed to pull data from the domain model objects as needed. Instead, the business tier must accept responsibility for fetching all data that is needed subsequently for rendering the view.

Although certain patterns that can minimize remote communication, such as the session facade and data transfer object (DTO) patterns, have been widely used in the Java developer community, we want to discuss a slightly different approach. The Command pattern (often also called EJB Command) is a sophisticated solution that combines the advantages of other strategies.

Let’s write a three-tiered application that utilizes this pattern.

The basic interfaces

The Command pattern is based on the idea of a hierarchy of command classes, all of which implement a simple Command interface. Look at this hierarchy in figure 16.2.

A particular Command is an implementation of an action, an event, or anything that can fit a similar description. Client code creates command objects and prepares them for execution. The CommandHandler is an interface that can execute Command objects. The client passes a Command object to a handler on the server tier, and the handler executes it. The Command object is then returned to the client.

tmp1DC355_thumb

The Command interface has an execute!) method; any concrete command must implement this method. Any subinterface may add additional methods that are called before (setters) or after (getter) the Command is executed. A Command is therefore combining input, controller, and output for a particular event.

Executing Command objects—that is, calling their execute!) method—is the job of a CommandHandler implementation. Execution of commands is dispatched polymorphically.

The implementation of these interfaces (and abstract classes) can look as follows:

tmp1DC356_thumb

Commands also encapsulate exception handling, so that any exception thrown during execution is wrapped in a CommandException that can then be handled accordingly by the client.

The DataAccessCommand is an abstract class:

tmp1DC357_thumb

Any Command that needs to access the database must use a data-access object, so a DAOFactory must be set before a DataAccessCommand can be executed. This is usually the job of the CommandHandler implementation, because the persistence layer is on the server tier.

The remote interface of the command handler is equally simple:

tmp1DC358_thumb

Let’s write some concrete implementations and use commands.

Executing command objects

A client that wishes to execute a command needs to instantiate and prepare a Command object. For example, placing a bid for an auction requires a BidForAuc-tionCommand on the client:

tmp1DC359_thumb

A BidForItemCommand needs all input values for this action as constructor arguments. The client then looks up a command handler and passes the BidForItem-Command object for execution. The handler returns the instance after execution, and the client extracts any output values from the returned object. (If you work with JDK 5.0, use generics to avoid unsafe typecasts.)

How the command handler is looked up or instantiated depends on the implementation of the command handler and how remote communication occurs. You don’t even have to call a remote command handler—it can be a local object. Let’s look at the implementation of the command and the command handler.

Implementing business commands

The BidForItemCommand extends the abstract class DataAccessCommand and implements the execute!) method:

tmp1DC360_thumbtmp1DC-361_thumb

This is basically the same code you wrote in the last stage of the web application refinement earlier in this topic. However, with this approach, you have a clear contract for required input and returned output of an action.

Because Command instances are sent across the wire, you need to implement Serializable (this marker should be in the concrete class, not the superclasses or interfaces).

Let’s implement the command handler.

Implementing a command handler

The command handler can be implemented in any way you like; its responsibilities are simple. Many systems need only a single command handler, such as the following:

tmp1DC362_thumbtmp1DC-363_thumb

This is a command handler implemented as a stateless EJB 3.0 session bean. You use an EJB lookup on the client to get a reference to this (local or remote) bean and then pass Command objects to it for execution. The handler knows how to prepare a particular type of command—for example, by setting a reference to the persistence layer before execution.

Thanks to container-managed and declarative transactions, this command handler contains no Hibernate code. Of course, you can also implement this command handler as a POJO without EJB 3.0 annotations and manage transaction boundaries programmatically. One the other hand, because EJBs support remote communication out of the box, they’re the best choice for command handlers in three-tier architectures.

There are many more variations of this basic Command pattern.

Variations of the Command pattern

First, not everything is perfect with the Command pattern. Probably the most important issue with this pattern is the requirement for nonpresentation interfaces on the client classpath. Because the BidForItemCommand needs the DAOs, you have to include the persistence layer interface on the client’s classpath (even if the command is executed only on the middle tier). There is no real solution, so the severity of this problem depends on your deployment scenario and how easily you can package your application accordingly. Note that the client needs the DAO interfaces only to instantiate a DataAccessCommand, so you may be able to stabilize the interfaces before you work on the implementation of your persistence layer.

Also, because you have just one command, the Command pattern seems like more work then the traditional session facade pattern. However, as the system grows, addition of new commands is made simpler because crosscutting concerns like exception handling and authorization checking may be implemented in the command handler. Commands are easy to implement and extremely reusable. You shouldn’t feel restricted by our proposed command interface hierarchy; feel free to design more complex and sophisticated command interfaces and abstract commands. You can also group commands together using delegation—for example, a DataAccessCommand can instantiate and call a ReportCommand.

A command is a great assembler for data that is required for rendering of a particular view. Instead of having the view pull the information from lazy loaded business objects (which requires colocation of the presentation and persistence layer, so you can stay inside the same persistence context), a client can prepare and execute the commands that are needed to render a particular screen—each command transports data to the presentation layer in its output properties. In a way, a command is a kind of data-transfer object with a built-in assembling routine.

Furthermore, the Command pattern enables you to implement any Undo functionality easily. Each command can have an undo() method that can negate any permanent changes that have been made by the execute() method. Or, you can queue several command objects on the client and send them to the command handler only when a particular conversation completes.

The Command pattern is also great if you have to implement a desktop application. You can, for example, implement a command that fires events when data is changed. All dialogs that need to be refreshed listen to this event, by registering a listener on the command handler.

You can wrap the commands with EJB 3.0 interceptors. For example, you can write an interceptor for your command handler session bean that can transparently inject a particular service on command objects of a particular type. You can combine and stack these interceptors on your command handler. You can even implement a client-local command handler which, thanks to EJB interceptors, can transparently decide whether a command needs to be routed to the server (to another command handler) or if the command can be executed disconnected on the client.

The stateless session bean need not be the only command handler. It’s easy to implement a JMS-based command handler that executes commands asynchronously. You can even store a command in the database for scheduled execution. Commands may be used outside of the server environment—in a batch process or unit test case, for example.

In practice, an architecture that relies on the Command pattern works nicely. In the next section, we discuss how EJB 3.0 components can further simplify a layered application architecture.

Designing applications with EJB 3.0

We’ve focused on the Java Persistence standard in this topic and discussed only a few examples of other EJB 3.0 programming constructs. We wrote some EJB session beans, enabled container-managed transactions, and used container injection to get an EntityManager.

There is much more to be discovered in the EJB 3.0 programming model. In the following sections, we show you how to simplify some of the previous patterns with EJB 3.0 components. However, we again only look at features that are relevant for a database application, so you need to refer to other documentation if you want to know more about timers, EJB interceptors, or message-driven EJBs.

First you’ll implement an action in a web application with a stateful session bean, a conversational controller. Then you’ll simplify data-access objects by turning them into EJBs to get container-managed transactions and injection of dependencies. You’ll also switch from any Hibernate interfaces to Java Persistence, to stay fully compatible with EJB 3.0.

You start by implementing a conversation with EJB 3.0 components in a web application.

Implementing a conversation with stateful beans

A stateful session bean (SFSB) is the perfect controller for a potentially long-running conversation between the application and the user. You can write an SFSB that implements all the steps in a conversation—for example, a PlaceItem conversation:

1 User enters item information

2 User can add images for an item

3 User submits the completed form

Step 2 of this conversation can be executed repeatedly, if more than one image must be added. Let’s implement this with an SFSB that uses Java Persistence and the EntityManager directly.

A single SFSB instance is responsible for the whole conversation. First, here’s the business interface:

tmp1DC364_thumb

In the first step of the conversation, the user enters the basic item details and supplies a user identifier. From this, an Item instance is created and stored in the conversation. The user can then execute addImage() events several times. Finally, the user completes the form, and the submit() method is called to end the conversation. Note how you can read the interface like a story of your conversation.

This is a possible implementation:

tmp1DC-365_thumb

An instance of this stateful session bean is bound to a particular EJB client, so it also acts as a cache during the conversation. You use an extended persistence context that is flushed only when submit() returns, because this is the only method that executes inside a transaction. All data access in other methods runs in auto-commit mode. So em. find!User. class, userId) executes nontransactional, whereas em.persist!item) is transactional. Because the submit!) method is also marked with @Remove, the persistence context is closed automatically when this method returns, and the stateful session bean is destroyed.

A variation of this implementation doesn’t call the EntityManager directly, but data-access objects.

Writing DAOs with EJBs

A data-access object is the perfect stateless session bean. Each data-access method doesn’t require any state; it only needs an EntityManager. So, when you implement a GenericDAO with Java Persistence, you require an EntityManager to be set:

tmp1DC-366_thumb

This is really the same implementation you created earlier for Hibernate in section 16.2.2, “Implementing the generic CRUD interface.” However, you mark the setEntityManager!) method with @PersistenceContext, so you get automatic injection of the right EntityManager when this bean executes inside a container. If it’s executed outside of an EJB 3.0 runtime container, you can set the Entity-Manager manually.

We won’t show you the implementation of all CRUD operations with JPA; you should be able to implement findById(), and so on, on your own.

Next, here’s the implementation of a concrete DAO with business data-access methods:

tmp1DC-367_thumb

This concrete subclass is the stateless EJB session bean, and all methods that are called, included those inherited from the GenericDAO superclass, require a transaction context. If a client of this DAO calls a method with no active transaction, a transaction is started for this DAO method.

You no longer need any DAO factories. The conversation controller you wrote earlier is wired with the DAOs automatically through dependency injection.

Utilizing dependency injection

You now refactor the PlaceItem conversation controller and add a persistence layer. Instead of accessing JPA directly, you call DAOs that are injected into the conversation controller by the container at runtime:

tmp1DC-368_thumbtmp1DC-369_thumb

The @EJB annotation marks the itemDAO and userDAO fields for automatic dependency injection. The container looks up an implementation (which implementation is vendor-dependent, but in this case there is only one for each interface) of the given interface and sets it on the field.

You haven’t disabled transactions in this implementation, but only disabled automatic flushing with the Hibernate org.hibernate.flushmode extension property. You then flush the persistence context once, when the @Remove method of the SFSB completes and before the transaction of this method commits.

There are two reasons for this:

■ All DAO methods you’re calling require a transaction context. If you don’t start a transaction for each method in the conversation controller, the transaction boundary is a call on one of the data-access objects. However, you want the createItem!), addImages!), and submit!) methods to be the scope of the transaction, in case you execute several DAO operations.

■ You have an extended persistence context that is automatically scoped and bound to the stateful session bean. Because the DAOs are stateless session beans, this single persistence context can be propagated into all DAOs only when a transaction context is active and propagated as well. If the DAOs are stateful session beans, you can propagate the current persistence context through instantiation even when there is no transaction context for a DAO call, but that also means the conversation controller must destroy any state-ful DAOs manually.

Without the Hibernate extension property, you’d have to make your DAOs stateful session beans to allow propagation of the persistence context between nontransac-tional method calls. It would then be the responsibility of the controller to call the @Remove method of each DAO in its own @Remove method—you don’t want either. You want to disable flushing without writing any nontransactional methods.

EJB 3.0 includes many more injection features, and they extend to other Java EE 5.0 specifications. For example, you can use @EJB injection in a Java servlet container, or @Resource to get any named resource from JNDI injected automatically. However, these features are outside the scope of this topic.

Now that you’ve created application layers, you need a way to test them for correctness.

Testing

Testing is probably the single most important activity in which a Java developer engages during a day of work. Testing determines the correctness of the system from a functional standpoint as well as from a performance and scalability perspective. Successfully executing tests means that all application components and layers interact correctly and work together smoothly and as specified.

You can test and proof a software system many different ways. In the context of persistence and data management, you’re naturally most interested in automated tests. In the following sections, you create many kinds of tests that you can run repeatedly to check the correct behavior of your application.

First we look at different categories of tests. Functional, integration, and standalone unit testing all have a different goal and purpose, and you need to know when each strategy is appropriate. We then write tests and introduce the TestNG framework (http://www.testng.org). Finally, we consider stress and load testing and how you can find out whether your system will scale to a high number of concurrent transactions.

Understanding different kinds of tests

We categorize software testing as follows:

■ Acceptance testing—This kind of test isn’t necessarily automated and usually isn’t the job of the application developer and system designers. Acceptance testing is the final stage of testing of a system, conducted by the customer (or any other party) who is deciding whether the system meets the project requirements. These tests can include any metric, from functionality, to performance, to usability.

■ Performance testing—A stress or load test exercises the system with a high number of concurrent users, ideally an equal or a higher load than is expected once the software runs in production. Because this is such an important facet of testing for any application with online transactional data processing, we look at performance testing later in more detail.

■ Logic unit testing—These tests consider a single piece of functionality, often only a business method (for example, whether the highest bid really wins in the auction system). If a component is tested as a single unit, it’s tested independently from any other component. Logic unit testing doesn’t involve any subsystems like databases.

■ Integration unit testing—An integration test determines whether the interaction between software components, services, and subsystems works as expected. In the context of transaction processing and data management, this can mean that you want to test whether the application works correctly with the database (for example, whether a newly made bid for an auction item is correctly saved in the database).

■ Functional unit testing—A functional test exercises a whole use case and the public interface in all application components that are needed to complete this particular use case. A functional test can include application workflow and the user interface (for example, by simulating how a user must be logged in before placing a new bid for an auction item).

In the following sections, we focus on integration unit testing because it’s the most relevant kind of test when persistent data and transaction processing are your primary concerns. That doesn’t mean other kinds of tests aren’t equally important, and we’ll provide hints along the way. If you want to get the full picture, we recommend JUnit in Action ([Massol, 2003]).

We don’t use JUnit, but TestNG. This shouldn’t bother you too much, because the fundamentals we present are applicable with any testing framework. We think TestNG makes integration and functional unit testing easier than JUnit, and we especially like its JDK 5.0 features and annotation-based configuration of test assemblies.

Let’s write a simple isolated logic unit test first, so you can see how TestNG works.

Introducing TestNG

TestNG is a testing framework that has some unique functionality, which makes it especially useful for unit testing that involves complex test setups such as integration or functional testing. Some of TestNG’s features are JDK 5.0 annotations for the declaration of test assemblies, support for configuration parameters and flexible grouping of tests into test suites, support for a variety of plug-ins for IDEs and Ant, and the ability to execute tests in a specific order by following dependencies.

We want to approach these features step by step, so you first write a simple logic unit test without any integration of a subsystem.

A unit test in TestNG

A logic unit test validates a single piece of functionality and checks whether all business rules are followed by a particular component or method. If you followed our discussion earlier in this topic about smart domain models (section 16.1.4, “Designing ‘smart’ domain models”), you know that we prefer to encapsulate unit-testable business logic in the domain model implementation. A logic unit test executes a test of methods in the business layer and domain model:

tmp1DC-370_thumbtmp1DC371_thumb

The class AuctionLogic is an arbitrary class with so-called test methods. A test method is any method marked with the @Test annotation. Optionally, you can assign group names to test methods so that you can assemble a test suite dynamically by combining groups later on.

The test method highestBidWins!) executes part of the logic for the “Placing a bid” use case. First, an instance of User is needed for placing bids—that this, is the same user isn’t a concern for this test.

This test can fail several ways, indicating that a business rule has been violated. The first bid gets the auction started (the current maximum and minimum bids are both zero), so you don’t expect any failure here. Placing a second bid is the step that must succeed without throwing a BusinessException, because the new bid amount is higher than the previous bid amount. Finally, you assert the state of the auction with the Java assert keyword and a comparison operation.

You often want to test business logic for failure and expect an exception.

Expecting failures in a test

The auction system has a pretty serious bug. If you look at the implementation of Item.placeBid!) in section 16.1.4, “Designing ‘smart’ domain models,” you can see that you check whether the given new bid amount is higher than any existing bid amount. However, you never check it against the initial starting price of an auction. That means a user can place any bid, even if it’s lower than the initial price.

You test this by testing for failure. The following procedure expects an exception:

tmp1DC-372_thumbtmp1DC-373_thumb

Now, placing a bid with a value of 100 has to fail, because the initial starting price of the auction is 200. TestNG requires that this method throws a BusinessExcep-tion—otherwise the test fails. More fine-grained business exception types let you test failures for core parts of the business logic more accurately.

In the end, how many execution paths of your domain model are considered defines your overall business logic test coverage. You can use tools such as cenqua clover (http://www.cenqua.com/clover/), which can extract the code coverage percentage of your test suite and provide many other interesting details about the quality of your system.

Let’s execute these previous test methods with TestNG and Ant.

Creating and running a test suite

You can create a test suite dozens of ways with TestNG and start the tests. You can call test methods directly with a click of a button in your IDE (after installing the TestNG plug-in), or you can integrate unit testing in your regular build with an Ant task and an XML description of the test suite.

An XML test suite description for the unit tests from the last sections looks as follows:

tmp1DC-374_thumb

A test suite is an assembly of several logical tests—don’t confuse this with test methods. A logical test is determined at runtime by TestNG. For example, the logical test with the name BusinessLogic includes all test methods (that is, methods marked with @Test) in classes of the auction.test package. These test methods must belong to a group that starts with the name logic; note that .* is a regular expression meaning “any number of arbitrary characters.” Alternatively, you can list the test classes you’d like to consider part of this logical test explicitly, instead of the whole package (or several packages).

You can write some test classes and methods, arrange them in any way that is convenient, and then create arbitrary test assemblies by mixing and matching classes, packages, and named groups. This assembly of logical tests from arbitrary classes and packages and the separation into groups with wildcard matching make TestNG more powerful than many other testing frameworks.

Save the suite description XML file as test-logic.xml in the base directory of your project. Now, run this test suite with Ant and the following target in your build.xml:

tmp1DC-375_thumb

First, the TestNG Ant tasks are imported into the build. Then, the unit-test.logic target starts a TestNG run with the suite description file test-logic.xml in the base directory of your project. TestNG creates an HTML report in the outputDir, so you clean this directory every time before running a test.

Call this Ant target and experiment with your first TestNG assembly. Next we discuss integration testing, and how TestNG can support you with flexible configuration of the runtime environment.

Testing the persistence layer

Testing the persistence layer means several components have to be exercised and checked to see whether they interact correctly. This means:

■ Testing mappings—You want to test mappings for syntactical correctness (whether all columns and tables that are mapped match the properties and classes).

■ Testing object state transitions—You want to test whether an object transitions correctly from transient to persistent to detached state. In other words, you want to ensure that data is saved correctly in the database, that it can be loaded correctly, and that all potential cascading rules for transitive state changes work as expected.

■ Testing queries—Any nontrivial HQL, Criteria, and (possibly) SQL query should be tested for correctness of the returned data.

All these tests require that the persistence layer isn’t tested stand-alone but is integrated with a running database-management system. Furthermore, all other infrastructure, such as a Hibernate SessionFactory or a JPA EntityManagerFactory, must be available; you need a runtime environment that enables any services you want to include in the integration test.

Consider the database-management system on which you want to run these tests. Ideally, this should be the same DBMS product you’ll deploy in production for your application. On the other hand, in some cases you may run integration tests on a different system in development—for example, the lightweight HSQL DB. Note that object-state transitions can be tested transparently, thanks to Hiber-nate’s database portability features. Any sophisticated application has mappings and queries that are often tailored for a particular database-management system (with formulas and native SQL statements), so any integration test with a nonpro-duction database product won’t be meaningful. Many DBMS vendors offer free licenses or even lightweight versions of their major database products for development purposes. Consider these before switching to a different database-management system during development.

You must first prepare the test environment and enable the runtime infrastructure before you write any integration unit tests.

Writing a DBUnit superclass

An environment for integration testing of a persistence layer requires that the database-management system is installed and active—we expect that this is taken care of in your case. Next, you need to consider your integration test assembly and how you can execute configuration and tests in the right order.

First, to use your data-access objects you have to start Hibernate—building a SessionFactory is the easiest part. More difficult is defining the sequence of configuration operations that are required before and after you run a test. A common sequence is this:

1 Reset the database content to a well-known state. The easiest way to do this is through an automatic export of a database schema with the Hibernate toolset. You then start testing with an empty, clean database.

2 Create any base data for the test by importing the data into the database. This can be done in various ways, such as programmatically in Java code or with tools such as DBUnit (http://www.dbunit.org).

3 Create objects, and execute whatever state transition you want to test, such as saving or loading an object by calling your DAOs in a TestNG test method.

4 Assert the state after a transition by checking the objects in Java code and/ or by executing SQL statements and verifying the state of the database.

Consider several such integration tests. Should you always start from step 1 and export a fresh database schema after every test method, and then import all base data again? If you execute a large number of tests, this can be time consuming. On the other hand, this approach is much easier than deleting and cleaning up after every test method, which would be an additional step.

A tool that can help you with these configuration and preparation steps for each test is DBUnit. You can import and manage data sets easily—for example, a data set that must be reset into a known state for each test run.

Even though TestNG allows you to combine and assemble test suites in any way imaginable, a superclass that encapsulates all configuration and DBUnit setup operations is convenient. Look at a superclass appropriate for integration testing of Hibernate data-access objects in listing 16.2.

Listing 16.2 A superclass for Hibernate integration testing

Listing 16.2 A superclass for Hibernate integration testing

tmp1DC-377_thumb[1]

tmp1DC378_thumb

O All tests in a particular suite use the same Hibernate SessionFactory.

C A subclass can customize the DBUnit database operations that are executed before and after every test method.

D A subclass can customize which DBUnit data set should be used for all its test methods.

Q Hibernate is started before a logical test of the test assembly runs—again, note that @BeforeTest doesn’t mean before each test method.

Q For each test (sub)class, a DBUnit data set must be loaded from an XML file, and all null markers have to be replaced with real NULLs.

G Before each test method, you execute the required database operations with DBUnit.

H After each test method, you execute the required database operations with DBUnit.

O By default, you obtain a plain JDBC connection from Hibernate’s Connection-Provider and wrap it in a DBUnit DatabaseConnection. You also disable foreign key constraint checking for this connection.

Q A subclass must override this method and prepare the data-set file location and operations that are supposed to run before and after each test method.

This superclass takes care of many things at once, and writing integration tests as subclasses is easy. Each subclass can customize which DBUnit data set it wants to work with (we’ll discuss these data sets soon) and what operations on that data set (for example, INSERT and DELETE) have to run before and after a particular test method executes.

Note that this superclass assumes the database is active and a valid schema has been created. If you want to re-create and automatically export the database schema for each test suite, enable the hibernate.hbm2ddl.auto configuration option by setting it to create. Hibernate then drops the old and exports a fresh database schema when the SessionFactory is built.

Next, let’s look at the DBUnit data sets.

Preparing the data sets

With the proposed testing strategy, each test (sub)class works with a particular data set. This is merely a decision we made to simplify the superclass; you can use a data set per test method or a single data set for the whole logical test, if you like.

A data set is a collection of data that DBUnit can maintain for you. There are a great many ways to create and work with data sets in DBUnit. We’d like to introduce one of the easiest scenarios, which is often sufficient. First, write a data set into an XML file, in the syntax as required by DBUnit:

tmp1DC379_thumb

You don’t need a DTD for this file, although specifying a DTD lets you verify the syntactical correctness of the data set (it also means that you must convert part of your database schema into a DTD). Each row of data has its own element with the name of the table. For example, one <USERS> element declares the data for one row in the USERS table. Note that you use [NULL] as the token that is replaced by the integration testing superclass with a real SQL NULL. Also note that you can add an empty row for each table that you’d like DBUnit to maintain. In the data set shown here, the ITEM table is part of the data set, and DBUnit can delete any data in that table (which comes in handy later).

Let’s assume that this data set is saved in an XML file basedata.xml in the auc-tion.test.dbunit package. Next you’ll write a test class that utilizes this data set.

Writing a test class

A test class groups test methods that rely on a particular data set. Look at the following example:

tmp1DC-380_thumb

This is a subclass of HibernateIntegrationTest, and it prepares the location of the data set it requires. It also requires that a CLEAN_INSERT operation runs before any test method. This DBUnit database operation deletes all rows (effectively cleans the USERS and ITEM tables) and then inserts the rows as defined in the data set. You have a clean database state for each test method.

DBUnit includes many built-in DatabaseOperations, such as INSERT, DELETE, DELETE_ALL, and even REFRESH. Check the DBUnit reference documentation for a complete list; we won’t repeat it here. Note that you can stack operations:

tmp1DC-381_thumb

Before each test method, all content in the data set tables is deleted and then inserted. After each test method, all database content in the data set tables is deleted again. This stack guarantees a clean database state before and after each test method.

You can now write the actual test methods in this test class. The name of the class, PersistentStateTransition, hints at what you want to do:

tmp1DC382_thumbtmp1DC-383_thumb

This test method makes an Item instance persistent. Although this looks like a lot of code, there are only a few interesting parts.

A User instance is required for this state transition, so the user data you define in the data set is loaded through Hibernate. You have to provide the same identifier value (1l in the example) you wrote into the data set as the primary key.

When the unit of work commits, all state transitions are completed and the state of the Session is synchronized with the database. The final step is the real test, asserting that the database content is in the expected state.

You can test the database state many ways. Obviously, you don’t use a Hibernate query or Session operation for this purpose, because Hibernate is an additional layer between your test and the real database content. To ensure that you’re really hitting the database and that you’re seeing the state as is, we recommend that you use an SQL query.

Hibernate makes it easy to execute an SQL query and to check the returned values. In the example, you open a Hibernate StatelessSession to create this SQL query. The database connection used in this query is in autocommit mode (hibernate.connection.autocommit set to true), because you don’t start a transaction. This is the perfect use case for StatelessSession, because it deactivates any cache, any cascading, any interceptors, or anything that could interfere with your view on the database.

Let’s bring this all together in a TestNG test suite and an Ant target.

Running the integration tests

This is the XML test suite descriptor:

tmp1DC-384_thumb

The logical test PersistenceLayer includes all test classes and test methods found in the package auction.test.dbunit, if their group name starts with integration-hibernate. This is also true for any TestNG configuration methods (those marked with @BeforeClass and so on), so you need to place any classes (the superclass, too) with configuration methods in the same package and add them to the same group.

To run this test suite with Ant, replace the name of the XML suite descriptor in the Ant target you wrote in section 16.5.2, “Creating and running a test suite.”

We’ve only scratched the surface of TestNG and DBUnit in the previous examples. There are many more useful options; for example, you can parameterize test methods in TestNG with arbitrary settings in your suite descriptor. You can create a test assembly that starts an EJB 3.0 container server  and then test your EJB layers. We recommend the documentation of TestNG and DBUnit, respectively, as you start building out your testing environment from the base classes and with the strategies we’ve shown.

You may wonder how you can test mappings and queries, because we’ve only discussed testing of object-state transitions. First, you can test mappings easily by setting hibernate.hbm2ddl.auto to validate. Hibernate then verifies the mappings by checking them against database catalog metadata when the SessionFactory is built. Second, testing queries is the same as testing object state transitions: Write integration test methods, and assert the state of the returned data.

Finally, we consider load and stress testing, and which aspects you have to focus on if you want to test the performance of your system.

Considering performance benchmarks

One of the most difficult things in enterprise application development is guaranteeing performance and scalability of an application. Let’s define these terms first.

Performance is usually considered to be the reaction time of a request/response-based application. If you click a button, you expect a response in half a second. Or, depending on the use case, you expect that a particular event (or batch operation) can be executed in a reasonable time frame. Naturally, reasonable depends on the case and usage patterns of some application functionality.

Scalability is the ability of a system to perform reasonably under higher load. Imagine that instead of 1 person clicking 1 button, 5,000 people click a lot of buttons. The better the scalability of a system, the more concurrent users you can pack on it without performance degradation.

We already had much to say about performance. Creating a system that performs well is, in our opinion, synonymous to creating a Hibernate/database application that has no obvious performance bottlenecks. A performance bottleneck can be anything you consider a programming mistake or bad design—for example, the wrong fetching strategy, a wrong query, or bad handling of the Session and persistence context. Testing a system for reasonable performance is usually part of the acceptance tests. In practice, performance testing is often done by a dedicated group of end user testers in a lab environment, or with a closed user group in real-world conditions. Pure automated performance tests are rare.

You can also find performance bottlenecks with an automated scalability test; this is the ultimate goal. However, we’ve seen many stress and load tests in our careers, and most of them didn’t consider one or several of the following rules:

■ Test scalability with real-world data sets. Don’t test with a data set that can fit completely into the cache of a hard disk on the database server. Use data that already exists, or use a test data generator to produce test data (for example, TurboData: http://www.turbodata.ca/). Make sure the test data is as close as possible to the data the system will work on in production, with the same amount, distribution, and selectivity.

■ Test scalability with concurrency. An automated performance test that measures the time it takes to do a single query with a single active user doesn’t tell you anything about the scalability of the system in production. Persistence services like Hibernate are designed for high concurrency, so a test without concurrency may even show an overhead you don’t expect! As soon as you enable more concurrent units of work and transactions, you’ll see how features such as the second-level cache help you to keep up performance.

■ Test scalability with real use cases. If your application has to process complex transactions (for example, calculating stock market values based on sophisticated statistical models), you should test the scalability of the system by executing these use cases. Analyze your use cases, and pick the scenarios that are prevalent—many applications have only a handful of use cases that are most critical. Avoid writing microbenchmarks that randomly store and load a few thousand objects; the numbers from these kinds of tests are meaningless.

Creating a test environment for the automatic execution of scalability tests is an involved effort. If you follow all our rules, you need to spend some time analyzing your data, your use cases, and your expected system load first. Once you have this information, it’s time to set up automated tests.

Typically, a scalability test of a client/server application requires the simulation of concurrently running clients and the collection of statistics for each executed operation. You should consider existing testing solutions, either commercial (such as LoadRunner, http://www.mercury.com/) or open source (such as The Grinder [http://grinder.sourceforge.net/] or JMeter [http:// jakarta.apache.org/jmeter/]). Creating tests usually involves writing control scripts for the simulated clients as well as configuring the agents that run on the server processes (for example, for direct execution of particular transactions or the collection of statistics).

Finally, testing performance and (especially) scalability of a system is naturally a separate stage in the lifecycle of a software application. You shouldn’t test the scalability of system in the early stages of development. You shouldn’t enable the second-level cache of Hibernate until you have a testing environment that was built following the rules we’ve mentioned.

At a later stage in your project, you may add automated scalability tests to the nightly integration tests. You should test the scalability of your system before going into production, as part of the regular test cycle. On the other hand, we don’t recommend delaying any kind of performance and scalability testing until the last minute. Don’t try to fix your performance bottlenecks one day before you go into production by tweaking the Hibernate second-level cache. You probably won’t succeed.

Consider performance and load testing to be an essential part of your development process, with well-defined stages, metrics, and requirements.

Summary

In this topic, we looked at layered applications and some important patterns and best practices. We discussed how you can design a web application with Hibernate and implement the Open Session in View pattern. You now know how to create smart domain models and how to separate business logic from controller code. The flexible Command pattern is a major asset in your software design arsenal. We looked at EJB 3.0 components and how you can further simplify a POJO application by adding a few annotations.

Finally, we discussed the persistence layer extensively; you wrote data-access objects and integration tests with TestNG that exercise the persistence layer

Next post:

Previous post: