Using Immutable Objects with MyBatis

Immutable is Beautiful

I’m a fan of immutable objects. We all know the benefits; simple, no mutators, thread safe, cache word optimizable. However, I see far too many MyBatis (and iBatis) developers adding no arg constructors to their POJO’s simply so that MyBatis can reflect and set all of the values into the field setters. I’ll ask “why does this POJO need to be mutated” and they quip that it doesn’t, but these setters and protected no-arg constructors are needed by MyBatis. This violates my principle that you make your library work with your code, not the other way around.

Given the lack of good documentation on immutable objects in MyBatis, I hope the following helps folks.

Example Implementation

We need a ResultMap that will tell MyBatis how to map this to a constructor. This is because Java reflection only exposes the constructor parameter types and order, not the names of the parameters (so claim the MyBatis docs, though Spring somehow manages to do this with bean constructors…).

The mapper maps the column names returned in the query to the types on the constructor. It also lays out the order of the arguments. Make sure the constructor argument order exactly matches that of your POJO.

Note: underscores before types map to the primitive value. _long maps to the primitive long type. long maps to the wrapper type java.lang.Long.

<resultMap  id="fooViewMap" type="com.lustforge.FooView">
	<constructor>
		<arg column="id" javaType="_long"/>
		<arg column="is_dirty"	javaType="_boolean"/>
	</constructor>
</resultMap>

Now make sure your query block points to the mapper via its resultMap attribute. Again confirm that the column names returned exactly match those in the map. Note: the order does not need to match for the query.

<select id="getFooViews" resultMap="fooViewMap">
  <![CDATA[
	    SELECT 
   		foo.id
   		foo.is_dirty

		FROM foo
		-- your query here
	 ]]>
</select>

Finally make sure your POJO constructor matches. It’s also a good idea to leave a note to future developers to update MyBatis if they alter the constructor argument types or order.

public final class FooView {

	private final long id;
	private final boolean isDirty;

	// Prescient comment
	// NOTE: MyBatis depends on parameter order and type for object creation
	// If you change the constructor, update the MyBatis mapper
	public FooView(long id, boolean isDirty) {
	    super();
	    this.id = id;
	    this.isDirty = isDirty;
	}

	public long getId() {
	    return id;
	}

	public boolean isDirty() {
	    return isDirty;
	}
	
	// ... don't forget equals/hashcode and toString as well
}

That was easy and now you’re using best practices. Pat yourself on the back and get busy with your new immutable POJO.