Chapter 4. Basic web forms

published book

This chapter covers

  • Building a web form
  • Externalizing strings in a view
  • Validating and saving form data

Web forms provide a means by which we can collect data from end users. As such, they’re a key aspect of any nontrivial web application. This chapter shows how to use Spring Web MVC and related technologies to build a simple user registration form with standard features such as redirect-after-post, externalized strings, form data validation, and persistence.

Our approach is hands-on and practical. See Spring in Action, 3rd edition by Craig Walls (Manning, 2011) for additional material on Spring Web MVC.

join today to enjoy all our content. all the time.
 

4.1. Displaying a web form

Prerequisites

None

Key technologies

Spring Web MVC, Spring form tag library

Background

Users establish a relationship with a website or an organization by registering. The resulting user account allows logins, order placement, community participation, and so on. The first step in supporting a user registration process is to display a registration form.

Problem

Create a web-based form.

Solution

In this recipe you’ll use Spring Web MVC to display a user registration form. You’ll build a user account form bean, a web controller, a registration form, and a confirmation page.

It won’t hurt to have a visual on the UI you’re planning to create in this recipe. Figure 4.1 shows what you’re aiming for.

Figure 4.1. The simple web-based registration form that you’ll build in this recipe

Let’s begin by creating a form bean for your user accounts.

Creating an account form bean

You use a form bean to store form data, as shown in the following listing.

Listing 4.1. AccountForm.java, a form bean for user accounts

AccountForm is a POJO.[1] It has properties for personal , marketing , and legal data. These are typical concerns when modeling user accounts. You also include a descriptive toString() method , based on the Commons Lang library, so you can observe the form-binding later in the recipe. By design, you suppress the password here to avoid accidentally revealing it.

You default the marketingOk property to true because you’d like to market to your users unless they explicitly opt out. On the other hand, you default acceptTerms to false because you want the user’s acceptance of the terms of use to be active rather than passive. Presumably this gives you a stronger legal leg to stand on in the event of a disagreement with the user.[2]

2 Disclaimer: We aren’t lawyers! Consult a qualified legal expert if necessary.

You have a form bean, but without a web controller, it’s inert. Let’s take care of that.

Creating a web controller

Your account controller, which appears in the following listing, handles form delivery and processing.

Listing 4.2. AccountController.java to handle user registrations

At the @Controller annotation tells Spring that this is a web controller. You establish a base path for request mapping using the @RequestMapping annotation . This path contextualizes paths declared at the method level. At you’re not implementing any special interfaces or extending special classes.

You serve the empty form bean at . The associated request mapping is /users/new, which you obtain by combining the class-level /users base path with the method-level new mapping. (To override a class-level mapping rather than refine it, place a slash in front of the method-level mapping.) The method itself places a new AccountForm instance on the model under the key account and returns the view name.

You process form submissions at , specifying the POST request method. The request mapping is just /users because that’s the result of combining the base path with the empty string. For now, when users post form data, you log it and redirect them to a view that thanks them for registering . We’ll discuss the redirection in more detail later in the recipe.

Let’s move on to the two view pages. First you’ll create the view for the registration form, and after that you’ll create the “thanks” page for successful form submissions.

Creating the view pages

The next listing shows how to implement the registration form from figure 4.1. Note that we’ve suppressed the layout and CSS code; see the code download (src/main/webapp/WEB-INF/jsp/users/registrationForm.jsp) for the full version.

Listing 4.3. registrationForm.jsp: view to display your registration form

The registration page uses the form tag to create an HTML form. You use action="." to post the form submission to /main/users/. The modelAttribute attribute references the model object to be used as the form-backing bean. The HTML form elements are bound to the form bean’s properties in both directions:

  • Inbound— The form bean is populated with the HTML form element values when the form is submitted and passed to the controller for validation and processing.
  • Outbound— The form elements are prepopulated with the form bean’s values. You use this, for example, to set the default value of the marketingOk check box to true and acceptTerms to false. Form elements are also prepopulated before representing a form to a user for remediating invalid form data; you’ll see this in recipe 4.3.

Figure 4.2 presents a high-level view of form binding.

Figure 4.2. Formbinding in action. Stars show where form fields and bean properties are bound together.

You use input , password , and checkbox tags from the Spring form tag library to render HTML form elements. These are essentially form-binding versions of the corresponding HTML elements. The tag library doesn’t provide anything for submit buttons (there’s nothing to bind to here), so you use standard HTML .

After the user successfully submits a registration, you need a page to let the user know that the registration succeeded. Here’s a minimalistic registrationOk.jsp file:

<html>
    <head><title>Registration Confirmed</title></head>
    <body><p>Thank you for registering.</p></body>
</html>

In this case, the page doesn’t even need to be a JSP, although you’ll leave it as is because it’s always possible that you’ll want to present dynamic information through the page.

You’re done with your form bean, controller, and views. All that remains is configuration.

Configuring the app

The key part of your web.xml configuration is the following:

<servlet>
    <servlet-name>spring</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:/spring/beans-web.xml</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>spring</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

This web.xml configuration references a single Spring configuration, called beans-web.xml, associated with the DispatcherServlet. It goes in src/main/resources/spring so it will be on the classpath when you package and deploy the app.

Listing 4.4. beans-web.xml: web tier configuration

The listing ties everything together. You use component scanning to discover the AccountController based on its @Controller annotation.

Spring 3 introduces the mvc namespace. You use <mvc:annotation-driven> at to activate annotation-based configuration inside the DispatcherServlet explicitly.

At you use <mvc:view-controller> to configure a new controller for the registration success page. Recall from listing 4.2 that you redirected the request to a success page, but you never specified a controller to display the success page. That’s what <mvc:view-controller> does. It creates a ParameterizableViewController instance whose job is to accept requests for /users/registration_ok and serve up the logical view name users/registrationOk for view resolution.

You redirect rather than forward to the success page because you want to apply the redirect-after-post pattern to your form submission. With this pattern, a successful form submission issues an HTTP redirect to avoid resubmissions if the user reloads or bookmarks the page, as illustrated by the sequence diagram in figure 4.3.

Figure 4.3. The redirect-after-post implementation for successful registrations

The figure suppresses the ViewResolver, but the DispatcherServlet uses the instance you created for both view resolutions depicted. The DispatcherServlet uses the ViewResolver to convert logical view names into views.

Why do you need <mvc:annotation-driven>?

You might wonder why you need to declare <mvc:annotation-driven> explicitly. After all, the DispatcherServlet default configuration already has an internal DefaultAnnotationHandlerMapping instance to handle @RequestMapping annotations. The reason: behind the scenes, <mvc:view-controller> creates a SimpleUrlHandlerMapping to map the ParameterizableViewController to the specified path, and this replaces the DefaultAnnotationHandlerMapping that would otherwise have been created. You use <mvc:annotation-driven> to indicate that you want the DefaultAnnotationHandlerMapping as well.

That almost wraps it up for the configuration. You’ll also need a WEB-INF/decorators.xml file for SiteMesh; see the code download for that. To run the app, run Maven with the jetty:run goal. On the command line, it looks like this:

mvn -e clean jetty:run

Then go to http://localhost:8080/sip/users/new.html. You should see a registration page that looks like the one from figure 4.1.

Discussion

What you’ve done so far isn’t tied to registration forms; this recipe is a blueprint for displaying web forms in general. As you move forward in the chapter, you’ll continue to target registration forms, but the discussion and techniques are broadly applicable.

In the next recipe, you’ll make your view pages more flexible by externalizing the strings that appear in the JSPs.

Get Spring in Practice
add to cart

4.2. Externalizing strings in the view

Prerequisite

Recipe 4.1 Displaying a web form

Key technologies

Java resource bundles, Spring tag library

Background

It’s often desirable to decouple a view from the specific bits of text rendered in the view. Reasons include internationalization and centralized management. This recipe shows how.

Problem

Externalize the strings that appear in the registration JSPs so they can be managed centrally.

Solution

The solution involves three steps:

1.  Create a resource bundle that contains the externalized strings, or messages in the Spring vernacular.

2.  Add a ReloadableResourceBundleMessageSource to the configuration.

3.  Replace the hardcoded strings in the JSPs with references to the externalized strings in the resource bundle.

First up is the resource bundle, which contains your messages.

Creating a resource bundle for the messages

The following listing shows how to create a resource bundle for your messages. This file goes in src/main/resources because you want it to appear at the root of the classpath on deployment.

Listing 4.5. messages.properties: resource bundle for externalized strings

You can organize these messages as you like. In this case, you have three sections: one for messages that are common to both pages , another for registration form messages , and a third for messages that appear on the success page . The key names reflect this organization.

Next you add a single bean to the beans-web.xml configuration.

Adding a message source to beans-web.xml

Add the following code snippet to beans-web.xml:

<bean id="messageSource"
    class="org.springframework.context.support.
            ReloadableResourceBundleMessageSource"
    p:basename="classpath:messages" />

This creates a message source, backed by the resource bundle, that you can use to drive dereferencing in the JSP. The ID messageSource is required.

The third and final step is to replace the hardcoded strings in the JSP with references.

Replacing the hardcoded strings with references

The next listing shows how to convert hardcoded strings into references using the <spring:message> tag.

Listing 4.6. Updating registrationForm.jsp to use external messages

We’ve suppressed a good chunk of the code in listing 4.6, but it should be obvious given what we’ve included how to convert the rest of listing 4.3. First you declare the spring tag library .[3] Then you use the <spring:message> tag to set a couple of variables to messages in the resource bundle so you can use them later. You use the pageTitle variable at and also inside the following <h1>, and you use the msgAllFieldsRequired variable at . At you use <spring:message> in a slightly different fashion; this time, you dump the message right into the template. This occurs because you haven’t specified a var attribute.

3 The spring and form tag libraries come from the org.springframework.web.servlet artifact, and the corresponding tag library descriptors are spring.tld and spring-form.tld, respectively. You can find these inside the JAR’s META-INF directory.

That’s it for the changes. Run the app the same way you ran it in recipe 4.1. Under the hood, you’ve externalized the strings, but you shouldn’t see any behavioral changes.

Discussion

It’s a good practice to externalize application strings. Besides paving the way for internationalization, it gives you a central place to manage text. This helps with quality control, and it helps when you decide you want to change, for example, “Technical Support Representative” to “Customer Care Specialist” across the board.

So far your form is very permissive. You can enter whatever you like into the form—including nothing—and the result is always success. In the following recipe, you’ll fix that with form validation.

Sign in for more free preview time

4.3. Validating form data

Prerequisite

Recipe 4.2 Externalizing strings in the view

Key technologies

Spring Web MVC, Spring binding and validation APIs, JSR 303 Bean Validation, JSR 223 Java Scripting, Hibernate Validator, Spring form tag library

Background

No matter how intuitive your registration form, people will accidentally or even intentionally fill it out with invalid information. You treat such errors as user errors rather than system or application exceptions, meaning you usually want to explain the error to the user in nontechnical language and help them overcome it.

Problem

When users submit form data, validate it before performing further processing. If there are errors, help the user understand what went wrong and how to address the issue.

Solution

At the highest level, this recipe addresses two types of validation:

  • Field filtering— Ensure that all submitted field names are permissible. In general, clients shouldn’t be allowed to submit fields that don’t appear on the form.
  • Field validation— Ensure that all submitted field values follow validation rules.

We’ll set the stage with an architectural overview. Spring Web MVC supports both types of validation just described using three key APIs: Spring’s form-binding API, Spring’s validation API, and JSR 303 Bean Validation. See figure 4.4.

Figure 4.4. Validation in Spring Web MVC. The form-binding API handles field filtering, JSR 303 handles bean validation, and there’s a Spring validation API for custom logic.

Here’s how it works. When users submit HTML form data, Spring Web MVC uses the form-binding API to bind the HTTP parameters to form bean properties in an automated fashion. In certain cases—for example, when a form bean is performing double duty as a persistent entity—the form bean may have properties that aren’t intended binding targets. The form-binding API allows you to filter out unwanted HTTP parameters by silently ignoring them during binding.

When Spring Web MVC invokes a form-submission request-handler method, such as postRegistrationForm(), it passes in the form data. In general, the form data is encapsulated within a form bean, and you want to validate it. This is the domain of JSR 303 Bean Validation. Spring Web MVC uses JSR 303 to validate form data encapsulated in this fashion, and developers use the Spring validation API (specifically, the BindingResult interface) from within a controller to determine whether the bean is valid.

Sometimes you need to perform a bit of custom validation logic. You’ll see an example. Spring’s validation API provides a programmatic interface for implementing such logic.

That will do for an overview. Let’s add field filtering to the AccountController.

Field filtering via @InitBinder and WebDataBinder

Recall that Spring Web MVC automatically binds HTML forms to an underlying form bean. Although this is a major convenience to application developers, it raises a security concern because it allows attackers to inject data into form bean properties that aren’t intended to be accessed via the HTML form. You’re not in that situation here, but it’s a common state of affairs in cases where a single model object performs double duty as both a form bean and a persistent entity. In such cases you need a way to guard against data injection.[4]

4 Consider the case where you use a single Account POJO to serve as both an entity and a form bean. The entity might have an enabled field that indicates whether the account is enabled. You wouldn’t want clients to be able to manipulate that field by sending a value for the field to the form processor.

Spring Web MVC supports this using @InitBinder methods. Add the following method to AccountController:

@InitBinder
public void initBinder(WebDataBinder binder) {
    binder.setAllowedFields(new String[] {
        "username", "password", "confirmPassword", "firstName",
        "lastName", "email", "marketingOk", "acceptTerms" });
}

The @InitBinder annotation tells Spring Web MVC to call this method when initializing the WebDataBinder responsible for binding HTTP parameters to form beans. The setAllowedFields() method defines a whitelist of bindable form bean fields. The binder silently ignores unlisted fields.

Whitelisting vs. blacklisting

The list of allowed fields is an example of a whitelist. The idea is that nothing gets through unless it’s on the whitelist.

There is an alternative approach called a blacklist. With a blacklist, everything gets through unless it’s on the blacklist.

Whitelists are generally more secure, because they start with an assumption of distrust rather than trust. But blacklists have their place as well. For example, you might filter out comment spammers using an IP blacklist, because it wouldn’t be practical to use a whitelist for web traffic.

Now let’s examine field validation.

Validating the form data

Several steps are involved in adding form validation to your app:

1.  Add a JSR 303 implementation to the classpath.

2.  Add validation annotations to AccountForm.

3.  Add @ModelAttribute, @Valid, BindingResult, and validation logic to AccountController.

4.  Create a ValidationMessages.properties resource bundle, and update the messages.properties resource bundle.

5.  Update registrationForm.jsp to display error messages.

6.  Confirm that beans-web.xml has <mvc:annotation-driven> (for validation) and a message source (for certain custom error messages).

There’s a lot to cover. Let’s start at the top of the list and work our way down.

Step 1. Placing a JSR 303 implementation on the classpath

Your Maven build takes care of placing Hibernate Validator 4, a JSR 303 implementation, on the classpath. Spring Web MVC will automatically pick it up. You can therefore move on to the next step, which is marking up AccountForm with validation annotations.

Step 2. Adding bean-validation annotations to the form bean

The following listing updates the AccountForm from listing 4.1 by adding validation annotations.

Listing 4.7. AccountForm.java, with validation annotations (updates listing 4.1)

The previous listing uses the Bean Validation (JSR 303) standard and Hibernate Validator to specify validation constraints. You attach the annotations either to the fields or to the getters. At you indicate that the username property can’t be null, and its size must be 1–50 characters in length. At you use the Hibernate-specific @Email annotation to ensure that the email property represents a valid e-mail address. At you require that the acceptTerms property be true for validation to succeed, and you specify a message code to use when the validation fails. (More on that shortly.)

Finally, you declare a class-level @ScriptAssert annotation at . This Hibernate annotation, which was introduced with Hibernate Validator 4.1, allows you to use a script to express validation constraints involving multiple fields. Here you use JavaScript to assert that the password and confirmation must be equal. (The Rhino JavaScript engine is automatically available if you’re using Java 6; otherwise you’ll need to place a JSR 223–compliant [Scripting for the Java Platform] script engine JAR on the classpath.) In addition to JavaScript, there are many other language options, including Groovy, Ruby, Python, FreeMarker, and Velocity.

Next you update AccountController to validate the account bean.

Step 3. Updating the controller to validate the form data

The next listing shows how to update the AccountController from listing 4.2 to support both Bean Validation via JSR 303 and custom password validation.

Listing 4.8. AccountController.java, updated to validate form data (updates listing 4.2)

You add @ModelAttribute and @Valid annotations to the AccountForm parameter . The @ModelAttribute annotation causes the account bean to be placed automatically on the Model object for display by the view, using the key "account". The @Valid annotation causes the bean to be validated on its way into the method.

Spring exposes the validation result via the BindingResult object . This is how you can tell whether bean validation turned up any errors. You can also programmatically add new errors to the BindingResult by using its various reject() and rejectValue() methods. The BindingResult method parameter must immediately follow the form bean in the method parameter list.

The logic of the postRegistrationForm() method itself is straightforward. You call convertPasswordError() , which converts the global error that @ScriptAssert generates into an error on the password field. You use the rejectValue() method to do this, as mentioned, passing in an error code "error.mismatch". This error code resolves to one of the following message codes, depending on which message codes appear in the resource bundle:

  • error.mismatch.account.password (error code + . + object name + . + field name)
  • error.mismatch.password (error code + . + field name)
  • error.mismatch.java.lang.String (error code + . + field type)
  • error.mismatch (error code)

These message codes are listed in priority order: if the resource bundle contains the first message code, then that’s the resolution, and so forth.[5] The first message code does in fact appear in messages.properties. See the Javadoc for Spring’s DefaultMessageCodesResolver for more information on the rules for converting error codes to message codes.

5 It’s probably worth emphasizing the fact that despite superficial similarities, error codes and message codes aren’t the same thing. Validation errors have associated codes, and these generally map to a set of resource bundle message codes, which in turn map to error messages. It’s pretty easy to get these mixed up.

Finally, once you’ve processed any password errors, you check to see whether there were any validation errors, and route to a success or failure page accordingly . Notice that you’re using the view name constants defined at the top of the file.

Let’s take a more detailed look at the error messages here.

Step 4. Configuring error messages

First let’s talk about the default JSR 303 and Hibernate Validator messages. Strictly speaking, you don’t have to override them at all. But the defaults aren’t particularly user-centric (one of the defaults, for example, references regular expressions), so you’ll change the messages for the constraints you’re using. JSR 303 supports this by allowing you to place a ValidationMessages.properties resource bundle at the top of the classpath. You’ll use this resource bundle not only to override the JSR 303 and Hibernate Validator defaults, but also to define an error message specific to the acceptTerms property.

Listing 4.9. ValidationMessages.properties, for JSR 303 error messages

You override the default JSR 303 @Size and default Hibernate Validator @Email error messages as shown. The message for @Size is effectively a template that generates messages with the minimum and maximum sizes substituted in. You aren’t overriding the default JSR 303 error message for @NotNull because that error shouldn’t occur if you don’t forget to implement any form fields. (And if you do, the default error message is OK because this is a programming error rather than an end user error.) Finally, you define an error message for the acceptTerms property at .

In addition to the JSR 303 error messages, you need messages for the Spring-managed errors. You’ll add these to messages.properties because ValidationMessages.properties is for JSR 303 error messages. Although it can be a little confusing to split the error messages into two resource bundles, it helps to do exactly this. The reason is that JSR 303 and Spring use different schemes for resolving error codes to message codes, and mixing error messages in a single resource bundle can make it harder to keep message codes straight.

Add the following two error messages to messages.properties:

error.global=Please fix the problems below.
error.mismatch.account.password=Your passwords do not match. Please try
        again.

Now you have an error message for the password-mismatch error code you used in the controller. You’ll use the global error message in the form JSP.

Step 5. Displaying validation errors in the view

You use the Spring form tag library to display both a global error message (“Please fix the problems below”) and error messages on the form bean, as illustrated in figure 4.5.

Figure 4.5. The revised registration form, with a global error message and field-level error messages

The text fields for properties with errors are visually distinct (they have red borders), although it’s hard to tell if you’re viewing the figure in black and white. Also, fields are prepopulated with the user’s submitted data so the user can fix mistakes instead of reentering all the data. The only exceptions are the two password fields, which for security reasons you don’t prepopulate. The user has to reenter those values.

To accomplish this design, you’ll need to revise registrationForm.jsp as shown next. (See the code download for the full version.)

Listing 4.10. registrationForm.jsp, updated with validation error messages

At you display the global error message. The tag logic here is to look for the existence of any error whatsoever—a global error or a field error—and if there is one, display the global error message. The path="*" piece is your error wildcard.

You display the username field at . By using the <form:input> tag, you get data prepopulation for free. This time around you include the CSS attributes because there’s something interesting to show off. The cssClass attribute specifies the <input> element’s CSS class when there’s no error. (The short class just sets the text-field width in the sample code.) The cssErrorClass attribute specifies the class when there is an error. This allows you to change the visual appearance of the text field when there’s an error.

In addition to the text field, you want to display the error message, and that’s what’s going on at . You select the specific form bean property with the path attribute and use htmlEscape="false" so you can include HTML in the error message if desired.

The other fields are essentially the same, so we’ve suppressed them. Again, please see the code download for the full version of the code.

The last step in the process is to configure the application for validation.

Step 6. Configuring the app for validation

Surprise—you’ve already done what you need to do here. In recipe 4.1 you included the <mvc:annotation-driven> configuration inside beans-web.xml, which among several other things activates JSR 303 Bean Validation, causing Spring Web MVC to recognize the @Valid annotation. In recipe 4.2 you added a MessageSource.

Start up your browser and give the code a spin.

Discussion

The preceding recipe handles validation in the web tier. There’s nothing wrong with that, because the constraints you’ve used so far make sense as web tier constraints. But it’s important to bear in mind that modern validation frameworks like Spring validation and JSR 303 validation abandon the traditional assumption that bean validation occurs exclusively in the web tier. In the following recipe, you’ll see what validation looks like in the service tier.

join today to enjoy all our content. all the time.
 

4.4. Saving form data

Prerequisites

Recipe 4.1 Displaying a web form

Recipe 4.3 Validating form data

Key technologies

Spring, JPA, Hibernate 3, Spring JdbcTemplate, MySQL, or other RDBMS

Background

So far you’re accepting and validating user registrations, but you aren’t saving the data to a persistent store. In this recipe, you’ll persist data using Spring, JPA, Hibernate, and JDBC. You’ll also perform service-tier validation to avoid duplicate usernames.

Problem

Save form data to a persistent store.

Solution

Although you’ll save your form data to a database, you’re not going to save the AccountForm form bean directly. The main reason is that there’s a mismatch between the form bean and what you’d want out of a domain object:

  • For security purposes, you don’t want your domain object to have a password property. (You don’t want a bunch of in-memory passwords sitting around.)
  • Your domain object will have an enabled field that the form bean doesn’t have.

Instead, you’ll create a separate Account domain object and then have the controller translate the AccountForm into an Account before saving the Account.

Why not save the form bean directly?

It’s possible to have a single POJO serve as both a form bean and a domain object, but architecturally it’s cleaner to separate the two, especially if there are material differences between them. Here the security difference seems important enough to warrant two separate classes.

Note that if you were to use a single POJO, then the @InitBinder method from recipe 4.3 would allow you to prevent users from setting the enabled property.

Having said all that, the choice is partly a matter of style. Especially with traditional designs based on anemic domain objects, it’s common to see a single POJO supporting presentational, domain, and persistence concerns. This might change, though, if domain-driven design (DDD) catches on in the Spring community. (Spring Roo promotes a DDD approach.) As domain objects get richer, they become less suitable as form beans.

You’ll use a combination of Hibernate, JPA annotations, and JDBC to persist the user registration data. Hibernate will work nicely for saving the Account domain object, but you need a way to save user passwords as well, and Hibernate won’t help there because the password isn’t part of Account. So, you’ll use straight JDBC to save the password. The POJO and password data need to be saved as part of the same transaction, and we’ll also show how to do that.

This recipe adds a lot of infrastructure to what you already have. See figure 4.6.

Figure 4.6. Bean-dependency diagram for saving user registration data. We’re including infrastructure for both Hibernate- and JDBC-based persistence.

You’ll start with the database schema, then build out the code and configuration on top of that.

Creating a database schema for storing user accounts

The following listing presents the database schema for MySQL, which involves a single table for storing user accounts.

Listing 4.11. User account table (MySQL)

Notice that you coordinate the database constraints in listing 4.11 with the validation constraints in recipe 4.3. For example, the field-size maximums are generally 50 in both locations. (The exception is that the password column in the database allows 64 characters to accommodate SHA-256 hashes, as you’ll see in recipe 6.7.) Also, you include a password column here even though the Account domain object won’t have a corresponding property.

Speaking of Account, let’s create it, because you’ll need it for what follows.

Annotating the account model for persistence

The next listing presents the Account domain object, with JPA annotations for persistence.

Listing 4.12. Account.java with JPA annotations for persistence

You use the JPA @Entity annotation to mark your domain object as a persistent entity, and @Table to associate the entity with a database table. At you use @Id, @GeneratedValue, and @Column on the getId() method to establish it as an ID property mapped to a database column called id, with GenerationType.AUTO indicating that the JPA provider (Hibernate in this case) is responsible for determining the right ID-generation strategy for the underlying database. (IDs might be generated by an autoincrement column, or perhaps by a sequence, and so on.)

For most properties, the column mapping is a matter of attaching an @Column annotation to the getter method or the field. You can see this with getUsername() .

In the case of the fullName property, it’s a convenience method rather than a persistent field, so you mark it with @Transient to prevent Hibernate from trying to persist it. You can also use JPA to define named queries supporting finder methods. At you define a named query to look up accounts by username. You’ll use this query in your data access object.

Creating the account data access object

You need both an interface and an implementation for your DAO. The interface extends the Dao interface from chapter 1 by adding a password-aware create() method (recall that the Account doesn’t have a password property) and a finder-by-username:

package com.springinpractice.ch04.dao;

import com.springinpractice.ch04.domain.Account;
import com.springinpractice.dao.Dao;

public interface AccountDao extends Dao<Account> {

    void create(Account account, String password);

    Account findByUsername(String username);

}

The DAO implementation in the following listing is more interesting. You derive it from AbstractHbnDao in chapter 1, but note that it isn’t a pure Hibernate DAO.

Listing 4.13. HbnAccountDao.java, backed by both Hibernate and JDBC

You use @Repository to tag HbnAccountDao as a DAO. This allows Spring to discover the bean during component scanning.

Now we get to the interesting part. You’re doing both Hibernate and JDBC inside this DAO. Hibernate handles everything on the Account POJO, but the password is a standalone field. So you need JDBC to update that. First you define a password-update statement at . You also inject a JdbcTemplate at to execute the update. At you have Hibernate and JDBC working together to save the user account data, including the password. A Hibernate Session sits behind the call to create(). Then you run the JDBC password update using the JdbcTemplate.

Besides saving account information, you have a finder for looking up an account by username . You’ll use this to check for duplicate usernames when the user tries to register an account. The finder uses the JPA named query you created on the Account domain object in listing 4.12.

Now let’s create an account service around the account DAO.

Creating the account service

You’ll create a service with a single method for registering valid users. Here is the service interface:

package com.springinpractice.ch04.service;

import org.springframework.validation.Errors;
import com.springinpractice.ch04.domain.Account;

public interface AccountService {
    boolean registerAccount(
        Account account, String password, Errors errors);
}

Notice that the service interface accepts an Errors object. The idea here is that the registerAccount() method does a conditional registration—it registers the account if and only if there aren’t any validation errors, either present in the Errors object, or discovered inside the registerAccount() implementation (such as a duplicate username). The controller will call registerAccount() with its BindingResult object, which works fine because BindingResult extends Errors. You use Errors in the AccountService interface, though, rather than BindingResult, because the service tier doesn’t know anything about web binding.

Why call registerAccount() if there are already known errors?

It may seem odd to call the registerAccount() method if there are already errors in the Errors container. The reason: when doing form validation, you generally want to know about all validation errors, not just the first one. So you still check for duplicate usernames even if you already know, for example, that the passwords didn’t match.

The following listing is the account service implementation.

Listing 4.14. AccountServiceImpl.java: service implementation

A good practice when writing service beans is to associate a read-only transaction definition at the class level . This provides a basic layer of safety because individual methods have to override the definition explicitly in order to write to the persistent store. Here you have only one method, so it looks a little funny, but this way you won’t forget if you decide to add more methods.

Inside registerAccount(), you validate the username and save the account to the database if the entire account is valid . The username validation uses the finder you created to determine whether the username is a duplicate. If it is, then you use the errors object to reject the username, specifying the errors.duplicate error code (we’ll define that momentarily) and the username for token substitution.

Let’s quickly take care of that error message.

Adding a new error message for duplicate usernames

All you need to do is add a single error message to messages.properties:

error.duplicate.account.username=The username {0} is already taken.

The error.duplicate.account.username message code will match the error.duplicate error code as explained in recipe 4.3. Spring will substitute the username for the {0} when displaying the error message, because the username is the 0th element of the String[] you passed into rejectValue().

There isn’t much you need to do to the controller to make it save accounts, as you’ll see now.

Updating the controller to save accounts using the service

To update the controller, you add a single line to the postRegistrationForm() method, and you add a helper method to convert the form bean into a domain object:

@RequestMapping(value = "", method = RequestMethod.POST)
public String postRegistrationForm(
        @ModelAttribute("account") @Valid AccountForm form,
        BindingResult result) {

    convertPasswordError(result);
    accountService.registerAccount(
        toAccount(form), form.getPassword(), result);
    return (result.hasErrors() ? VN_REG_FORM : VN_REG_OK);
}

private static Account toAccount(AccountForm form) {
    Account account = new Account();
    account.setUsername(form.getUsername());
    account.setFirstName(form.getFirstName());
    account.setLastName(form.getLastName());
    account.setEmail(form.getEmail());
    account.setMarketingOk(form.isMarketingOk());
    account.setAcceptTerms(form.getAcceptTerms());
    account.setEnabled(true);
    return account;
}

You’ll need to augment the existing configuration to support persistence. The main part of this effort involves adding a new Spring application context file. You’ll need to modify web.xml slightly as well. First let’s do the app context.

Creating a new application context configuration for persistence

To add persistence to your registration form, you need to add several bits.

Listing 4.15. beans-service.xml: application context configuration

You declare a DataSource reference using a JNDI lookup at . You’ll need to consult the documentation for your servlet container to see what’s involved with exposing a DataSource with JNDI using that container. The sample code includes a Jetty configuration.

At you declare the JDBC template you’re using to set the user password. The Hibernate configuration is at , and it’s set up for MySQL 5 in listing 4.15. You’ll need to modify that if you’re using a different RDBMS; see the Javadoc for the org.hibernate.dialect package for more options.

You define a Hibernate SessionFactory at , using the DataSource and configuration you just created. As its name suggests, the SessionFactory is a session source. Sometimes it creates brand-new sessions (for example, when starting a new transaction), and sometimes it returns sessions that have already been created (such as when executing DAO persistence operations).

The transaction manager provides (you guessed it) transaction management services. It knows, for example, how to start, suspend, and stop transactions. The HibernateTransactionManager implementation coordinates transaction management with Hibernate session management.

You use <context:component-scan> to discover DAOs and service beans . Component scanning interprets classes annotated with @Repository as DAOs and classes annotated with @Service as service beans.

Finally, at you use <tx:annotation-driven> to activate transactions. The specific details of that process are fairly involved, and there’s no need to dig into the details here, but the basic idea is that it causes Spring’s IOC container to wrap transaction-aware Spring AOP proxies around components marked up with the @Transactional annotation.[6] It does this using Spring AOP’s autoproxy facility.

6 In addition to Spring AOP proxies, AspectJ weaving is an option. See the reference documentation for <tx:annotation-driven> for more details.

Just one small tweak to go, and you’ll be ready to run the app.

Updating web.xml to point to the new app context configuration

All you need to do here is add a single configuration element to web.xml. This tells the Spring Web MVC DispatcherService where to find your beans-service.xml configuration:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:/spring/beans-service.xml</param-value>
</context-param>

With that, you should be ready to go. Try it out.

Discussion

In this recipe we’ve shown how to save form data to a persistent store. That’s of course a common requirement, and now you have a good feel for how to do it. You even saw how to use Hibernate and JDBC together in cases where the form data doesn’t all fit nicely inside a single domain object.

Because our topic is web forms in general rather than user-registration forms in particular, we’ve neglected some persistence- and security-related topics that a real user form would take seriously. The good news is that we’ll address them in chapter 6. They’re the following:

  • Spring Security integration— A key reason for user accounts is to support logins. Recipe 6.6 shows how to use the account data you’ve developed in chapter 4 as an authentication source.
  • Hashing and salting passwords— It’s a poor security practice to save passwords as plaintext in the database, because that makes it easier for a malicious person to see those passwords and use them on other websites. (Users often use the same password for multiple websites.) You can use password hashing and salting to mitigate this issue. We’ll show how to hash and salt passwords in recipe 6.7.
Sign in for more free preview time

4.5. Summary

In this chapter, you developed a basic registration form with several of the key features you’d expect such a form to have, including string externalization, validation, and persistence. Although we used user registration as an example, the topics we’ve treated are obviously general concerns when developing web-based forms.

In many cases, registration forms aren’t as simple as the one you developed in this chapter. Instead they carry the user through a series of steps, implemented as a web flow spanning multiple pages. In chapter 5 you’ll learn how to implement multistep flows using Spring Web Flow.

sitemap

Unable to load book!

The book could not be loaded.

(try again in a couple of minutes)

manning.com homepage