Tag Archives: data binding

Binding nested objects, complex properties with Spring’s form:select tag.

While creating a form for an Entity (say Movie), it so happens that due to some business scenarios linking this entity with some other Entity (say Actor) becomes inevitable.  Let’s see the following code snippets of the above two entities.
Movie.java

public class Movie {
 . . .
      private int movieId;
      private String movieName;
      private Actor actor;
 . . .
}

Actor.java

public class Actor {
 . . .
       private int actorId;
       private String actorName;
 . . .
}

And then during the creation of Movie you need to link it with an Actor.

How do you do that?

Using <form:select>. Yes, but there’s  little more than that 😉

So we go ahead and prepare a view for Movie entity, which will have a text field for Movie’s name and a drop down (select tag) to link it with some Actor.

In the JSP page we’ll have the following code to take care of it.

<!-- Input field for Movie's name -->
<form:input path="movieName"/>

<!-- Select Tag, for giving the actors as a Dropdown -->
<!-- From Controller we'll set a list of Actors in Model and send it for this JSP -->
<form:select path="actor">
<form:options items="${actorList}" itemValue="actorId" itemLabel="actorName"/>
</form:select>

This all looks good, but once you submit you get to see a fat, ugly error.

This happens because Spring tries to bind the Movie object with Actor. And instead of getting an object all it gets is the Id field for actor, as that’s the value going to the Controller if you select an option.

<form:options items="${actorList}" itemValue="actorId" itemLabel="actorName"/>

So for any option selected itemValue attribute will contain the Id of the Actor above and Spring will try to set this itemValue in actor property of class Movie, but here the property actor isn’t a primitive one, rather it’s complex as it’s an object in itself.

So, to overcome this we need to convert the Id property of Actor into a full fledged Actor object. Prior to Spring 3, PropertyEditors were employed, but gone are those days!

We’ll use Formatters, the new kid on the block!

To do that, first we need to write a class implementing the Formatter<T> interface.

package org.springframework.format;
public interface Formatter<T> extends Printer<T>, Parser<T> {
}

So, we’ll create a class ActorFormatter.java

/* ActorFormatter.java */

//Removed the imports for brevity

@Component
public class ActorFormatter implements Formatter<Actor> {
     @Autowired
     private ActorService actorService;
     //Some service class which can give the Actor after
     //fetching from Database

     @Override
     public String print(Actor actor, Locale arg1) {
           return actor.getActorName().toString();
     }

     @Override
      public Actor parse(String actorId, Locale arg1) throws ParseException {
           return actorService.getActor(actorId);
           //Else you can just return a new object by setting some values
           //which you deem fit.
      }
}

Now, we’ve to register this formatter with Spring. To accomplish that, write the following in your WebApplicationContext file, or the config xml file.


<mvc:annotation-driven/>

<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
      <property name="formatters">
           <set>
                 <ref bean="actorFormatter"/>
           </set>
      </property>
</bean>

Now, if you’re using Spring MVC then you don’t need to do anything, except putting an @Valid annotation in front of @ModelAttribute in your @RequestMapping.

@RequestMapping("/movie")
public String createMovieForm(@ModelAttribute("movie") @Valid Movie movie, BindingResult result, Model model) {
. . .
}

But if you’re using Spring MVC Portlet, then this @Valid won’t trigger the conversion strategies, and in which case you’ll need an @InitBinder.
So just include the below code snippet in your Controller.

@Autowired
private ConversionService conversionService;
//Autowiring the ConversionService we declared in the context file above.

@InitBinder
public void registerConversionServices(WebDataBinder dataBinder) {
       dataBinder.setConversionService(conversionService);
}

So, that’s all for this one. 🙂

Advertisements