Using the Spring DataBinder to map Strings to objects

The Spring DataBinder is not only useful to bind request parameters, you can also use the DataBinder on other parameters, like command line arguments, JSON objects coming from an Ajax request and query results from a database.

Most people probably don’t know what happens when the SimpleFormController maps the input fields from the browser to the specified Command object. Input fields are passed as Strings, even if it is an Integer or a Date. In Spring there is a class called ServletRequestDataBinder which does the mapping from input fields to a Command class. This is all hidden for the developer and I also didn’t know it before yesterday.

I’m currently reading “Expert Spring MVC and Web Flow” and came across a nice subsection. In chapter 6 is explained how the SimpleFormController maps the request parameters to an object and since we’re talking about Spring there is a super class with which you can do a lot of nice things. The super super class of ServletRequestDataBinder is DataBinder.
The DataBinder class binds a list of values to an object. So let’s say we have a list of values we want to map to an Employee object.
First create an Employee class with getters and setters for id (Long), name (String) and department (Integer). Then override the toString method so we can see what happens in the following steps.

public String toString() {
    return "[" + id + ",  " + name + ", " + department + "]";
}

Now create a simple testcase with a testBind method.

 public void testBind() {
        MutablePropertyValues values = new MutablePropertyValues();
        values.addPropertyValue("id", "7839");
        values.addPropertyValue("name", "KING");
        values.addPropertyValue("department", "20");

        Employee employee = new Employee();
        DataBinder binder = new DataBinder(employee);
        binder.bind(values);

        log.debug(employee);
  }

First we put all the values on a MutablePropertyValues object. Then we create a new Employee object on which we want the values set. The last steps are creating a DataBinder object and binding the values to that object.
When we run the testcase the following line will be printed:

DEBUG nl.amis.BinderTest  - [7839,  KING, 20]

That was quite easy, no fuzz with Long.valueOf and NumberFormatExceptions. But you’re probably wondering what happens when we provide invalid parameters. Let’s change the department to X20 and run the test again.

DEBUG nl.amis.BinderTest  - [7839,  KING, null]

The test runs successfully and you won’t get any errors. This is not what you want. Add the lines

 try {
            binder.close();
        } catch (BindException bindException) {
            List<FieldError> errors = bindException.getAllErrors();
            for (FieldError error : errors) {
                log.error(error);
        log.error("cannot bind " + error.getRejectedValue() + " to the field " + error.getField());
            }
        }

Now an error is printed on the console. The FieldError object is quite useful to print out nice error messages to end users.You can invoke the getBindingResult right after invoking the bind method, but I think it’s better to close the binder and catch the BindException, but you’re free to do what you prefer.
With web applications you want to have some kind of protection. Let’s say we use our own DataBinder to save an Employee object to a database. We have a web page with an Employee form and we have an input field for name and department. Now some kind of  evil person can add an  Id field and save that field to the database.
Luckily the guys from Spring thought about this. On the DataBinder you can invoke the setAllowedFields method (or the setDisallowedFields the other way around).
So add the following line to the test just before invoking bind.

binder.setAllowedFields(new String[]{"name", "department"});

Now the id parameter is not set. No error is added to the list of errors by the way.

With the Spring PropertyEditor system you can add your own data type and bind any String to any object.

Using the DataBinder with a RowMapper

The following example wasn’t what I hoped for, but I think it can be useful for some people. I wanted to make an automatic RowMapper for the Spring JdbcTemplate. When you do a query you can  include a RowMapper. That RowMapper looks like this:

public class EmployeeRowMapper implements RowMapper {
   public Employee mapRow(ResultSet rs, int row) throws SQLException {

        MutablePropertyValues values = new MutablePropertyValues();

        ResultSetMetaData setMetaData = rs.getMetaData();
        int count = setMetaData.getColumnCount();
        for (int i = 1; i <= count; i++) {
            values.addPropertyValue(
        setMetaData.getColumnName(i).toLowerCase(), 
        rs.getObject(i));
        }

        Employee employee = new Employee();
        DataBinder binder = new DataBinder(employee);
        binder.bind(values);

        try {
            binder.close();
        } catch (BindException e) {
            throw new SQLException(e.getMessage());
        }

        return employee;
    }
}

With the getMetaData you can get all the column names and with the DataBinder you can bind those values to the object automatically. You have to do the naming of your parameters in sql. The query for all employees:
select empno id, deptno department, ename name from emp

But the big problem is that getColumnName returns uppercase characters and usually you want to have mixed case with java field names (departmentName, orderStatus etc.). When your field names have the same case it’s no problem, but you can’t rely on this. There will be a programmer in your project who forgets about is and then everything is messed up and you end up with two different methods. It’s probably better to use iBatis or Hibernate when you want ‘automatic’ binding.

Nested Properties

Let’s take it one step further (thanks for the tip qinxian). It is possible to have nested properties. Let’s say our Employee object has a Department object. This Department object has an id, name and location. When we want to set the location of the department of a certain employee we just use the d

ot-notation: values.addPropertyValue(“department.location”, “NEW YORK”);
Create a Department class with getters and setters for id, name and location. Change the department field of Employee fromk Integer to Department (and don’t forget to update the get and set method).
Then change the code in your unit test:

MutablePropertyValues values = new MutablePropertyValues();
values.addPropertyValue("id", "7839");
values.addPropertyValue("name", "KING");
values.addPropertyValue("department.id", "10");
values.addPropertyValue("department.name", "ACCOUNTING");
values.addPropertyValue("department.location", "NEW YORK");

Employee employee = new Employee();
employee.setDepartment(new Department());
DataBinder binder = new DataBinder(employee);
binder.bind(values);

Don’t forget to create a Department object, this isn’t done for you.

It’s also possible to use indexed properties, key/value properties and array properties. I’ll explain binding to a List, the key/value and array properties are done in almost exactly the same way.

Our employee now can work in multiple departments. So change the department field on Employee to private List<Department> departments (and update the get and set). Now replace the lines in your test:

MutablePropertyValues values = new MutablePropertyValues();
values.addPropertyValue("id", "7839");
values.addPropertyValue("name", "KING");
values.addPropertyValue("departments[0].id", "10");
values.addPropertyValue("departments[0].name", "ACCOUNTING");
values.addPropertyValue("departments[0].location", "NEW YORK");
values.addPropertyValue("departments[1].id", "20");
values.addPropertyValue("departments[1].name", "RESEARCH");
values.addPropertyValue("departments[1].location", "DALLAS");

Employee employee = new Employee();
employee.setDepartments(new ArrayList<Department>());
employee.getDepartments().add(new Department());
employee.getDepartments().add(new Department());
DataBinder binder = new DataBinder(employee);
binder.bind(values);

Note that we have to create the List and add two departments to it. When we run the test this is the result in the debugger:
Using the Spring DataBinder to map Strings to objects springdatabinder

Conclusion

Digging in the Spring code base can be very useful and can save you a lot of work. I always did my own binding and now I can do it the Spring-way. In our current project I do some binding to objects. We have some MultiActionControllers with different input objects. When we use the DataBinder we can clean up a lot of checks for NumberFormatExceptions and null values. And you don’t have to write documentation for the binder, because the Spring guys already did this for you 😉

8 Comments

  1. Wilson June 22, 2007
  2. Marinus Kuivenhoven June 20, 2007
  3. Geeta Vatkar June 19, 2007
  4. Warren Mayocchi February 9, 2007
  5. Jeroen van Wilgenburg February 6, 2007
  6. qinxian February 5, 2007
  7. Lucas Jellema February 5, 2007
  8. Thomas Risberg February 4, 2007