17.5. Simplifying persistence with Seam

All the previous examples in this chapter use the EntityManager that was injected by the EJB 3.0 container. A member field in an EJB is annotated with @PersistenceContext, and the scope of the persistence context is always the transaction started and committed for a particular action method. In Hibernate terms, a Hibernate Session is opened, flushed, and closed for every method called on a session bean.

When a session bean method returns and the persistence context is closed, all entity instances you loaded from the database in that bean method are in detached state. You can render these instances on a JSF page by accessing their initialized properties and collections, but you get a LazyInitializationException if you try to access an uninitialized association or collection. You also have to reattach (or merge, with the Java Persistence API) a detached instance if you want to have it in persistent state again. Furthermore, you have to carefully code the equals() and hashCode() methods of your entity classes, because the guaranteed identity scope is only the transaction, the same as the (relatively short) persistence context scope.

We've discussed the consequences of the detached object state several times before in this book. Almost always, we've concluded that avoiding the detached state by extending the persistence context and identity scope beyond a transaction is a preferable solution. You've seen the Open Session in View pattern that extends the persistence context to span a whole request. Although this pattern is a pragmatic solution for applications that are built in a stateless fashion, where the most important scope is the request, you need a more powerful variation if you write a stateful Seam application with conversations.

If you let Seam inject an EntityManager into your session beans, and if you let Seam manage the persistence context, you'll get the following:

  • Automatic binding and scoping of an extended persistence context to the conversation—You have a guaranteed identity scope that spans your conversation. A particular conversation has at most one in-memory representation of a particular database row. There are no detached objects, and you can easily compare entity instances with double equals (a==b). You don't have to implement equals() and hashCode() and compare entity instances by business key.

  • No more LazyInitializationExceptions when you access an uninitalized proxy or collection in a conversation—The persistence context is active for the whole conversation, and the persistence engine can fetch data on demand at all times. Seam provides a much more powerful and convenient implementation of the Open Session in View pattern, which avoids detached objects not only during a single request but also during a whole conversation.

  • Automatic wrapping of the JSF request in several system transactions—Seam uses several transactions to encapsulate the phases in the JSF request lifecycle. We'll discuss this transaction assembly later; one of its benefits is that you have an optimized assembly that keeps database lock times as short as possible, without any coding.

Let's demonstrate this with an example by rewriting the registration procedure from the previous section as a conversation with an extended persistence context. The previous implementation was basically stateless: The RegisterBean was only scoped to a single event.

17.5.1. Implementing a conversation

Go back and read the code shown in listing 17.16. This stateful session bean is the backing bean for the account registration page in CaveatEmptor. When a user opens or submits the registration page, an instance of that bean is created and active while the event is being processed. JSF binds the form values into the bean (through verifyPassword and the Seam-injected currentUser) and calls the action listener methods when necessary.

This is a stateless design. Although you use a stateful session bean, its scope is a single request, the event context in Seam. This approach works fine because the conversation the user goes through is trivial—only a single page with a single form has to be filled in and submitted. Figure 17.13 shows a more sophisticated registration procedure.

Figure 17-13. The CaveatEmptor registration wizard

The user opens register.xhtml and enters the desired username and password. After the user clicks Next Page, a second form with the profile data (first name, email address, and so on) is presented and must be filled out. The last page shows all the account and profile data again, so the user can confirm it (or step back and correct it).

This registration procedure is a wizard-style conversation, with the usual Next Page and Previous Page buttons that allow the user to step through the conversation. Many applications need this kind of dialog. Without Seam, implementing multipage conversations is still difficult for web application developers. (Note that there are many other good use cases for conversations; the wizard dialog is common.)

Let's write the pages and Seam components for this conversation.

The registration page

The register.xhtml page looks almost like the one shown in listing 17.15. You remove the profile form fields (first name, last name, email address) and replace the Register button with a Next Page button:

...

<s:validateAll>

    <div class="entry">
        <div class="label">Username:</div>
        <div class="input">
            <s:decorate>
                <h:inputText size="16" required="true"
                             value="#{register.user.username}"/>
            </s:decorate>
        </div>

    </div>

    <div class="entry">
        <div class="label">Password:</div>
        <div class="input">
            <s:decorate>
                <h:inputSecret size="16" required="true"
                               value="#{register.user.password}"/>
            </s:decorate>
        </div>
    </div>

    <div class="entry">
        <div class="label">Repeat password:</div>
        <div class="input">
            <s:decorate>
                <h:inputSecret size="16" required="true"
                               value="#{register.verifyPassword}"/>
            </s:decorate>
        </div>
    </div>

</s:validateAll>

<div class="entry">
    <div class="label">&#160;</div>
    <div class="input">
        <h:commandButton value="Next Page"
                         styleClass="button"
                         action="#{register.enterAccount}"/>
    </div>
</div>

You're still referring to the register component to bind values and actions; you'll see that class in a moment. You bind the form values to the User object returned by register.getUser(). The currentUser is gone. You now have a conversation context and no longer need to use the HTTP session context (the previous implementation didn't work if the user tried to register two accounts in two browser windows at the same time). The register component now holds the state of the User that is bound to all form fields during the conversation.

The outcome of the enterAccount() method forwards the user to the next page, the profile form. Note that you still rely on Hibernate Validator for input validation, called by Seam (<s:validateAll/>) in the Process Validations phase of the request. If input validation fails, the page is redisplayed.

The profile page

The profile.xhtml page is almost the same as the register.xhtml page. The profile form includes the profile fields, and the buttons at the bottom of the page allow a user to step back or forward in the conversation:

...

<div class="entry">
    <div class="label">E-mail address:</div>
    <div class="input">
        <s:decorate>
            <h:inputText size="32" required="true"
                         value="#{register.user.email}"/>
        </s:decorate>
    </div>
</div>

<div class="entry">
    <div class="label">&#160;</div>
    <div class="input">
        <h:commandButton value="Previous Page"
                         styleClass="button"
                         action="register"/>

        <h:commandButton value="Next Page"
                         styleClass="button"
                         action="#{register.enterProfile}"/>
    </div>
</div>

Any form field filled out by the user is applied to the register.user model when the form is submitted. The Previous Page button skips the Invoke Application phase and results in the register outcome—the previous page is displayed. Note that there is no <s:validateAll/> around this form; you don't want to Process Validations when the user clicks the Previous Page button. Calling Hibernate Validator is now delegated to the register.enterProfile action. You should validate the form input only when the user clicks Next Page. However, you keep the decoration on the form fields to display any validation error messages.

The next page shows a summary of the account and profile.

The summary page

On confirm.xhtml, all input is presented in a summary, allowing the user to review the account and profile details before finally submitting them for registration:

...

<div class="entry">
    <div class="label">Last name:</div>
    <div class="output">#{register.user.lastname}</div>
</div>

<div class="entry">
    <div class="label">E-mail address:</div>
    <div class="output">#{register.user.email}</div>
</div>

<div class="entry">
    <div class="label">&#160;</div>

    <div class="input">
        <h:commandButton value="Previous Page"
                         styleClass="button"
                         action="profile"/>

        <h:commandButton value="Register"
                         styleClass="button"
                         action="#{register.confirm}"/>
    </div>
</div>

The Previous Page button renders the response defined by the profile outcome, which is the previous page. The register.confirm method is called when the user clicks Register. This action method ends the conversation.

Finally, you write the Seam component that backs this conversation.

Writing a conversational Seam component

The RegisterBean shown in listing 17.16 must be scoped to the conversation. First, here's the interface:

public interface Register {

    // Value binding methods
    public User getUser();
    public void setUser(User user);

    public String getVerifyPassword();
    public void setVerifyPassword(String verifyPassword);

    // Action binding methods
    public String enterAccount();
    public String enterProfile();
    public String confirm();

    // Cleanup routine
    public void destroy();
}

One of the advantages of the Seam conversation model is that you can read your interface like a story of your conversation. The user enters account data and then the profile data. Finally, the input is confirmed and stored.

The implementation of the bean is shown in listing 17.17.

Listing 17-17. A conversation-scoped Seam component
package auction.beans;

import ...

@Name("register")
@Scope(ScopeType.CONVERSATION)
							
@Stateful public class RegisterBean implements Register { @PersistenceContext
private EntityManager em; @In(create=true) private transient FacesMessages facesMessages; private User user;
public User getUser() { if (user == null) user = new User(); return user; } public void setUser(User user) { this.user = user; } private String verifyPassword;
public String getVerifyPassword() { return verifyPassword; } public void setVerifyPassword(String verifyPassword) { this.verifyPassword = verifyPassword; } @Begin(join = true)
public String enterAccount() { if ( verifyPasswordMismatch() || usernameExists() ) { return null; // Redisplay page } else { return "profile"; } } @IfInvalid(outcome = Outcome.REDISPLAY)
public String enterProfile() { return "confirm"; } @End(ifOutcome = "login")
public String confirm() { if ( usernameExists() ) return "register"; // Safety check em.persist(user); facesMessages.add("Registration successful!"); return "login"; } @Remove @Destroy public void destroy() {} private boolean usernameExists() { List existing = em.createQuery("select u.username from User u" + " where u.username = :uname") .setParameter("uname", user.getUsername()) .getResultList(); if (existing.size() != 0) { facesMessages.add("Username exists"); return true; } return false; } private boolean verifyPasswordMismatch() { if (!user.getPassword().equals(verifyPassword)) { facesMessages.add("Passwords do not match"); verifyPassword = null; return true; } return false; } }

❶ When Seam instantiates this component, an instance is bound into the conversation context under the variable name register.

❷ The EJB 3.0 container injects a transaction-scoped persistence context. You'll use Seam here later to inject a conversation-scoped persistence context.

❸ The user member variable is exposed with accessor methods so that JSF input widgets can be bound to individual User properties. The state of the user is held during the conversation by the register component.

❹ The verifyPassword member variable is also exposed with accessor methods for value binding in forms, and the state is held during the conversation.

❺ When the user clicks Next Page on the first screen, the enterAccount() method is called. The current conversation is promoted to a long-running conversation with @Begin, when this method returns, so it spans future requests until an @End marked method returns. Because users may step back to the first page and resubmit the form, you need to join an existing conversation if it's already in progress.

❻ When the user clicks Next Page on the second screen, the enterProfile() method is called. Because it's marked with @IfInvalid, Seam executes Hibernate Validator for input validation. If an error occurs, the page is redisplayed (Outcome.REDISPLAY is a convenient constant shortcut) with error messages from Hibernate Validator. If there are no errors, the outcome is the final page of the conversation.

❼ When the user clicks Register on the last screen, the confirm() method is called. When the method returns the login outcome, Seam ends the long-running conversation and destroys the component by calling the method marked with @Destroy. Meanwhile, if some other person picks the same username, you redirect the user back to the first page of the conversation; the conversation context stays intact and active.

You've seen most of the annotations earlier in this chapter. The only new annotation is @IfInvalid, which triggers Hibernate Validator when the enterProfile() method is called. The registration conversation is now complete, and everything works as expected. The persistence context is handled by the EJB container, and a fresh persistence context is assigned to each action method when the method is called.

You haven't run into any problems because the code and pages don't load data on demand by pulling data in the view from the detached domain objects. However, almost any conversation more complex than the registration process will trigger a LazyInitializationException.

17.5.2. Letting Seam manage the persistence context

Let's provoke a LazyInitializationException. When the user enters the final screen of the conversation, the confirmation dialog, you present a list of auction categories. The user can select the default category for their account: the auction category they want to browse and sell items in by default. The list of categories is loaded from the database and exposed with a getter method.

Triggering a LazyInitializationException

Edit the RegisterBean component and expose a list of auction categories, loaded from the database:

public class RegisterBean implements Register {
    ...

    private List<Category> categories;
    public List<Category> getCategories() {
        return categories;
    }
    ...

    @IfInvalid(outcome = Outcome.REDISPLAY)
    public String enterProfile() {
        categories =
                em.createQuery("select c from Category c" +
                               " where c.parentCategory is null")
                    .getResultList();
        return "confirm";
    }

}

You also add the getCategories() method to the interface of the component. In the confirm.xhtml view, you can now bind to this getter method to show the categories:

...
<div class="entry">
    <div class="label">E-mail address:</div>
    <div class="output">#{register.user.email}</div>
</div>

<div class="entry">
    <div class="label">Default category:</div>
    <div class="input">
        <tr:tree var="cat"
                 value="#{registrationCategoryAdapter.treeModel}">
            <f:facet name="nodeStamp">
                <h:outputText value="#{cat.name}"/>
            </f:facet>
        </tr:tree>
    </div>
</div>
...

To display categories, you use a different widget, which isn't in the standard JSF set. It's a visual tree data component from the Apache MyFaces Trinidad project. It also needs an adapter that converts the list of categories into a tree data model. But this isn't important (you can find the libraries and configuration for this in the CaveatEmptor download).

What is important is that if the tree of categories is rendered, the persistence context was closed already in the Render Response phase, after enterProfile() was invoked. Which categories are now fully available in detached state? Only the root categories, categories with no parent category, have been loaded from the database. If the user clicks the tree display and wants to see whether a category has any children, the application fails with a LazyInitializationException.

With Seam, you can easily extend the persistence context to span the whole conversation, not only a single method or a single event. On-demand loading of data is then possible anywhere in the conversation and in any JSF processing phase.

Injecting a Seam persistence context

First, configure a Seam managed persistence context. Edit (or create) the file components.xml in your WEB-INF directory:

<components>

    <component name="org.jboss.seam.core.init">

        <!-- Enable seam.debug page -->
        <property name="debug">false</property>

        <!-- How does Seam lookup EJBs in JNDI -->
        <property name="jndiPattern">
            caveatemptor/#{ejbName}/local
        </property>
    </component>

    <component name="org.jboss.seam.core.manager">

        <!-- 10 minute inactive conversation timeout -->
        <property name="conversationTimeout">600000</property>

    </component>

    <component
        name="caveatEmptorEM"
        class="org.jboss.seam.core.ManagedPersistenceContext">
        <property name="persistenceUnitJndiName">
            java:/EntityManagerFactories/caveatEmptorEMF
        </property>
    </component>

</components>

You also move all other Seam configuration options into this file, so seam.properties is now empty (but still required as a marker for the component scanner).

When Seam starts up, it configures the class ManagedPersistenceContext as a Seam component. This is like putting Seam annotations onto that class (there are also annotations on this Seam-bundled class). The name of the component is caveatEmptorEM, and it implements the EntityManager interface. Whenever you now need an EntityManager, let Seam inject the caveatEmptorEM.

(The ManagedPersistenceContext class needs to know how to get a real EntityManager, so you have to provide the name of the EntityManagerFactory in JNDI. How you get the EntityManagerFactory into JDNI depends on your Java Persistence provider. In Hibernate, you can configure this binding with jboss.entity.manager.factory.jndi.name in persistence.xml.)

Modify the RegisterBean again, and use the Seam persistence context:

						@Name("register")
@Scope(ScopeType.CONVERSATION)

@Stateful
public class RegisterBean implements Register {

    @In(create = true, value = "caveatEmptorEM")
    private EntityManager em;

    ...

When a method on this component is called for the first time, Seam creates an instance of ManagedPersistenceContext, binds it into the variable caveatEmptorEM in the conversation context, and injects it into the member field em right before the method is executed. When the conversation context is destroyed, Seam destroys the ManagedPersistenceContext instance, which closes the persistence context.

When is the persistence context flushed?

Integrating the persistence context lifecycle

The Seam-managed persistence context is flushed whenever a transaction commits. Instead of wrapping transactions (with annotations) around your action methods, let Seam also manage transactions. This is the job of a different Seam phase listener for JSF, replacing the basic one in faces-config.xml:

<lifecycle>
 <phase-listener>
    org.jboss.seam.jsf.TransactionalSeamPhaseListener
 </phase-listener>
</lifecycle>

This listener uses two system transactions to handle one JSF request. One transaction is started in the Restore View phase and committed after the Invoke Application phase. Any system exceptions in these phases trigger an automatic rollback of the transaction. A different response can be prepared with an exception handler (this is weak point in JSF—you have to use a servlet exception handler in web.xml to do this). By committing the first transaction after the action method execution is complete, you keep any database locks created by SQL DML in the action methods as short as possible.

A second transaction spans the Render Response phase of a JSF request. Any view that pulls data on demand (and triggers initialization of lazy loaded associations and collections) runs in this second transaction. This is a transaction in which data is only read, so no database locks (if your database isn't running in repeatable read mode, or if it has a multiversion concurrency control system) are created during that phase.

Finally, note that the persistence context spans the conversation, but that flushing and commits may occur during the conversation. Hence, the whole conversation isn't atomic. You can disable automatic flushing with @Begin(flushMode = FlushModeType.MANUAL) when a conversation is promoted to be long-running; you then have to call flush() manually when the conversation ends (usually in the method marked with @End).

The persistence context is now available through Seam injection in any component, stateless or stateful. It's always the same persistence context in a conversation; it acts as a cache and identity map for all entity objects that have been loaded from the database.

An extended persistence context that spans a whole conversation has other benefits that may not be obvious at first. For example, the persistence context is not only the identity map, but also the cache of all entity objects that have been loaded from the database during a conversation.

Imagine that you don't hold conversational state between requests, but push every piece of information either into the database or into the HTTP session (or into hidden form fields, or cookies, or request parameters...) at the end of each request. When the next request hits the server, you assemble state again by accessing the database, the HTTP session, and so on. Because you have no other useful contexts and no conversational programming model, you must reassemble and disassemble the application state for every request. This stateless application design doesn't scale—you can't hit the database (the tier that is most expensive to scale) for every client request!

Developers try to solve this problem by enabling the Hibernate second-level cache. However, scaling an application with a conversational cache is much more interesting than scaling it with a dumb second-level data cache. Especially in a cluster, a second-level cache forces an update of the caches on all cluster nodes whenever any piece of data is modified by any node. With the conversational cache, only the nodes required for load balancing or failover of this particular conversation have to participate in replication of the current conversation data (which is in this case stateful session bean replication). Replication can be significantly reduced, because no global shared cache needs to be synchronized.

We'd like to talk about Seam much more and show you other examples, but we're running out of paper.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.84.150