Result Maps

The resultMap element is the most important and powerful element in MyBatis. It’s what allows you to do away with 90% of the code that JDBC requires to retrieve data from ResultSets, and in some cases allows you to do things that JDBC does not even support. In fact, to write the equivalent code for something like a join mapping for a complex statement could probably span thousands of lines of code. The design of the ResultMaps is such that simple statements don’t require explicit result mappings at all, and more complex statements require no more than is absolutely necessary to describe the relationships.

You’ve already seen examples of simple mapped statements that don’t have an explicit resultMap. For example:

  1. <select id="selectUsers" resultType="map">
  2. select id, username, hashedPassword
  3. from some_table
  4. where id = #{id}
  5. </select>

Such a statement simply results in all columns being automatically mapped to the keys of a HashMap, as specified by the resultType attribute. While useful in many cases, a HashMap doesn’t make a very good domain model. It’s more likely that your application will use JavaBeans or POJOs (Plain Old Java Objects) for the domain model. MyBatis supports both. Consider the following JavaBean:

  1. package com.someapp.model;
  2. public class User {
  3. private int id;
  4. private String username;
  5. private String hashedPassword;
  6. public int getId() {
  7. return id;
  8. }
  9. public void setId(int id) {
  10. this.id = id;
  11. }
  12. public String getUsername() {
  13. return username;
  14. }
  15. public void setUsername(String username) {
  16. this.username = username;
  17. }
  18. public String getHashedPassword() {
  19. return hashedPassword;
  20. }
  21. public void setHashedPassword(String hashedPassword) {
  22. this.hashedPassword = hashedPassword;
  23. }
  24. }

Based on the JavaBeans specification, the above class has 3 properties: id, username, and hashedPassword. These match up exactly with the column names in the select statement.

Such a JavaBean could be mapped to a ResultSet just as easily as the HashMap.

  1. <select id="selectUsers" resultType="com.someapp.model.User">
  2. select id, username, hashedPassword
  3. from some_table
  4. where id = #{id}
  5. </select>

And remember that TypeAliases are your friends. Use them so that you don’t have to keep typing the fully qualified path of your class out. For example:

  1. <!-- In Config XML file -->
  2. <typeAlias type="com.someapp.model.User" alias="User"/>
  3. <!-- In SQL Mapping XML file -->
  4. <select id="selectUsers" resultType="User">
  5. select id, username, hashedPassword
  6. from some_table
  7. where id = #{id}
  8. </select>

In these cases MyBatis is automatically creating a ResultMap behind the scenes to auto-map the columns to the JavaBean properties based on name. If the column names did not match exactly, you could employ select clause aliases (a standard SQL feature) on the column names to make the labels match. For example:

  1. <select id="selectUsers" resultType="User">
  2. select
  3. user_id as "id",
  4. user_name as "userName",
  5. hashed_password as "hashedPassword"
  6. from some_table
  7. where id = #{id}
  8. </select>

The great thing about ResultMaps is that you’ve already learned a lot about them, but you haven’t even seen one yet! These simple cases don’t require any more than you’ve seen here. Just for example sake, let’s see what this last example would look like as an external resultMap, as that is another way to solve column name mismatches.

  1. <resultMap id="userResultMap" type="User">
  2. <id property="id" column="user_id" />
  3. <result property="username" column="user_name"/>
  4. <result property="password" column="hashed_password"/>
  5. </resultMap>

And the statement that references it uses the resultMap attribute to do so (notice we removed the resultType attribute). For example:

  1. <select id="selectUsers" resultMap="userResultMap">
  2. select user_id, user_name, hashed_password
  3. from some_table
  4. where id = #{id}
  5. </select>

Now if only the world was always that simple.

Advanced Result Maps

MyBatis was created with one idea in mind: Databases aren’t always what you want or need them to be. While we’d love every database to be perfect 3rd normal form or BCNF, they aren’t. And it would be great if it was possible to have a single database map perfectly to all of the applications that use it, it’s not. Result Maps are the answer that MyBatis provides to this problem.

For example, how would we map this statement?

  1. <!-- Very Complex Statement -->
  2. <select id="selectBlogDetails" resultMap="detailedBlogResultMap">
  3. select
  4. B.id as blog_id,
  5. B.title as blog_title,
  6. B.author_id as blog_author_id,
  7. A.id as author_id,
  8. A.username as author_username,
  9. A.password as author_password,
  10. A.email as author_email,
  11. A.bio as author_bio,
  12. A.favourite_section as author_favourite_section,
  13. P.id as post_id,
  14. P.blog_id as post_blog_id,
  15. P.author_id as post_author_id,
  16. P.created_on as post_created_on,
  17. P.section as post_section,
  18. P.subject as post_subject,
  19. P.draft as draft,
  20. P.body as post_body,
  21. C.id as comment_id,
  22. C.post_id as comment_post_id,
  23. C.name as comment_name,
  24. C.comment as comment_text,
  25. T.id as tag_id,
  26. T.name as tag_name
  27. from Blog B
  28. left outer join Author A on B.author_id = A.id
  29. left outer join Post P on B.id = P.blog_id
  30. left outer join Comment C on P.id = C.post_id
  31. left outer join Post_Tag PT on PT.post_id = P.id
  32. left outer join Tag T on PT.tag_id = T.id
  33. where B.id = #{id}
  34. </select>

You’d probably want to map it to an intelligent object model consisting of a Blog that was written by an Author, and has many Posts, each of which may have zero or many Comments and Tags. The following is a complete example of a complex ResultMap (assume Author, Blog, Post, Comments and Tags are all type aliases). Have a look at it, but don’t worry, we’re going to go through each step. While it may look daunting at first, it’s actually very simple.

  1. <!-- Very Complex Result Map -->
  2. <resultMap id="detailedBlogResultMap" type="Blog">
  3. <constructor>
  4. <idArg column="blog_id" javaType="int"/>
  5. </constructor>
  6. <result property="title" column="blog_title"/>
  7. <association property="author" javaType="Author">
  8. <id property="id" column="author_id"/>
  9. <result property="username" column="author_username"/>
  10. <result property="password" column="author_password"/>
  11. <result property="email" column="author_email"/>
  12. <result property="bio" column="author_bio"/>
  13. <result property="favouriteSection" column="author_favourite_section"/>
  14. </association>
  15. <collection property="posts" ofType="Post">
  16. <id property="id" column="post_id"/>
  17. <result property="subject" column="post_subject"/>
  18. <association property="author" javaType="Author"/>
  19. <collection property="comments" ofType="Comment">
  20. <id property="id" column="comment_id"/>
  21. </collection>
  22. <collection property="tags" ofType="Tag" >
  23. <id property="id" column="tag_id"/>
  24. </collection>
  25. <discriminator javaType="int" column="draft">
  26. <case value="1" resultType="DraftPost"/>
  27. </discriminator>
  28. </collection>
  29. </resultMap>

The resultMap element has a number of sub-elements and a structure worthy of some discussion. The following is a conceptual view of the resultMap element.

resultMap

  • constructor - used for injecting results into the constructor of a class upon instantiation
    • idArg - ID argument; flagging results as ID will help improve overall performance
    • arg - a normal result injected into the constructor
  • id – an ID result; flagging results as ID will help improve overall performance
  • result – a normal result injected into a field or JavaBean property
  • association – a complex type association; many results will roll up into this type
    • nested result mappings – associations are resultMaps themselves, or can refer to one
  • collection – a collection of complex types
    • nested result mappings – collections are resultMaps themselves, or can refer to one
  • discriminator – uses a result value to determine which resultMap to use
    • case – a case is a result map based on some value
      • nested result mappings – a case is also a result map itself, and thus can contain many of these same elements, or it can refer to an external resultMap.
ResultMap Attributes
AttributeDescription
idA unique identifier in this namespace that can be used to reference this result map.
typeA fully qualified Java class name, or a type alias (see the table above for the list of built-in type aliases).
autoMappingIf present, MyBatis will enable or disable the automapping for this ResultMap. This attribute overrides the global autoMappingBehavior. Default: unset.

Best Practice Always build ResultMaps incrementally. Unit tests really help out here. If you try to build a gigantic resultMap like the one above all at once, it’s likely you’ll get it wrong and it will be hard to work with. Start simple, and evolve it a step at a time. And unit test! The downside to using frameworks is that they are sometimes a bit of a black box (open source or not). Your best bet to ensure that you’re achieving the behaviour that you intend, is to write unit tests. It also helps to have them when submitting bugs.

The next sections will walk through each of the elements in more detail.

id & result

  1. <id property="id" column="post_id"/>
  2. <result property="subject" column="post_subject"/>

These are the most basic of result mappings. Both id and result map a single column value to a single property or field of a simple data type (String, int, double, Date, etc.).

The only difference between the two is that id will flag the result as an identifier property to be used when comparing object instances. This helps to improve general performance, but especially performance of caching and nested result mapping (i.e. join mapping).

Each has a number of attributes:

Id and Result Attributes
AttributeDescription
propertyThe field or property to map the column result to. If a matching JavaBeans property exists for the given name, then that will be used. Otherwise, MyBatis will look for a field of the given name. In both cases you can use complex property navigation using the usual dot notation. For example, you can map to something simple like: username, or to something more complicated like: address.street.number.
columnThe column name from the database, or the aliased column label. This is the same string that would normally be passed to resultSet.getString(columnName).
javaTypeA fully qualified Java class name, or a type alias (see the table above for the list of built-in type aliases). MyBatis can usually figure out the type if you’re mapping to a JavaBean. However, if you are mapping to a HashMap, then you should specify the javaType explicitly to ensure the desired behaviour.
jdbcTypeThe JDBC Type from the list of supported types that follows this table. The JDBC type is only required for nullable columns upon insert, update or delete. This is a JDBC requirement, not a MyBatis one. So even if you were coding JDBC directly, you’d need to specify this type – but only for nullable values.
typeHandlerWe discussed default type handlers previously in this documentation. Using this property you can override the default type handler on a mapping-by-mapping basis. The value is either a fully qualified class name of a TypeHandler implementation, or a type alias.

Supported JDBC Types

For future reference, MyBatis supports the following JDBC Types via the included JdbcType enumeration.

BITFLOATCHARTIMESTAMPOTHERUNDEFINED
TINYINTREALVARCHARBINARYBLOBNVARCHAR
SMALLINTDOUBLELONGVARCHARVARBINARYCLOBNCHAR
INTEGERNUMERICDATELONGVARBINARYBOOLEANNCLOB
BIGINTDECIMALTIMENULLCURSORARRAY

constructor

While properties will work for most Data Transfer Object (DTO) type classes, and likely most of your domain model, there may be some cases where you want to use immutable classes. Often tables that contain reference or lookup data that rarely or never changes is suited to immutable classes. Constructor injection allows you to set values on a class upon instantiation, without exposing public methods. MyBatis also supports private properties and private JavaBeans properties to achieve this, but some people prefer Constructor injection. The constructor element enables this.

Consider the following constructor:

  1. public class User {
  2. //...
  3. public User(Integer id, String username, int age) {
  4. //...
  5. }
  6. //...
  7. }

In order to inject the results into the constructor, MyBatis needs to identify the constructor for somehow. In the following example, MyBatis searches a constructor declared with three parameters: java.lang.Integer, java.lang.String and int in this order.

  1. <constructor>
  2. <idArg column="id" javaType="int"/>
  3. <arg column="username" javaType="String"/>
  4. <arg column="age" javaType="_int"/>
  5. </constructor>

When you are dealing with a constructor with many parameters, maintaining the order of arg elements is error-prone.
Since 3.4.3, by specifying the name of each parameter, you can write arg elements in any order. To reference constructor parameters by their names, you can either add @Param annotation to them or compile the project with ‘-parameters’ compiler option and enable useActualParamName (this option is enabled by default). The following example is valid for the same constructor even though the order of the second and the third parameters does not match with the declared order.

  1. <constructor>
  2. <idArg column="id" javaType="int" name="id" />
  3. <arg column="age" javaType="_int" name="age" />
  4. <arg column="username" javaType="String" name="username" />
  5. </constructor>

javaType can be omitted if there is a writable property with the same name and type.

The rest of the attributes and rules are the same as for the regular id and result elements.

AttributeDescription
columnThe column name from the database, or the aliased column label. This is the same string that would normally be passed to resultSet.getString(columnName).
javaTypeA fully qualified Java class name, or a type alias (see the table above for the list of built-in type aliases). MyBatis can usually figure out the type if you’re mapping to a JavaBean. However, if you are mapping to a HashMap, then you should specify the javaType explicitly to ensure the desired behaviour.
jdbcTypeThe JDBC Type from the list of supported types that follows this table. The JDBC type is only required for nullable columns upon insert, update or delete. This is a JDBC requirement, not an MyBatis one. So even if you were coding JDBC directly, you’d need to specify this type – but only for nullable values.
typeHandlerWe discussed default type handlers previously in this documentation. Using this property you can override the default type handler on a mapping-by-mapping basis. The value is either a fully qualified class name of a TypeHandler implementation, or a type alias.
selectThe ID of another mapped statement that will load the complex type required by this property mapping. The values retrieved from columns specified in the column attribute will be passed to the target select statement as parameters. See the Association element for more.
resultMapThis is the ID of a ResultMap that can map the nested results of this argument into an appropriate object graph. This is an alternative to using a call to another select statement. It allows you to join multiple tables together into a single ResultSet. Such a ResultSet will contain duplicated, repeating groups of data that needs to be decomposed and mapped properly to a nested object graph. To facilitate this, MyBatis lets you “chain” result maps together, to deal with the nested results. See the Association element below for more.
nameThe name of the constructor parameter. Specifying name allows you to write arg elements in any order. See the above explanation. Since 3.4.3.

association

  1. <association property="author" javaType="Author">
  2. <id property="id" column="author_id"/>
  3. <result property="username" column="author_username"/>
  4. </association>

The association element deals with a “has-one” type relationship. For example, in our example, a Blog has one Author. An association mapping works mostly like any other result. You specify the target property, the javaType of the property (which MyBatis can figure out most of the time), the jdbcType if necessary and a typeHandler if you want to override the retrieval of the result values.

Where the association differs is that you need to tell MyBatis how to load the association. MyBatis can do so in two different ways:

  • Nested Select: By executing another mapped SQL statement that returns the complex type desired.
  • Nested Results: By using nested result mappings to deal with repeating subsets of joined results.

First, let’s examine the properties of the element. As you’ll see, it differs from a normal result mapping only by the select and resultMap attributes.

AttributeDescription
propertyThe field or property to map the column result to. If a matching JavaBeans property exists for the given name, then that will be used. Otherwise, MyBatis will look for a field of the given name. In both cases you can use complex property navigation using the usual dot notation. For example, you can map to something simple like: username, or to something more complicated like: address.street.number.
javaTypeA fully qualified Java class name, or a type alias (see the table above for the list of built- in type aliases). MyBatis can usually figure out the type if you’re mapping to a JavaBean. However, if you are mapping to a HashMap, then you should specify the javaType explicitly to ensure the desired behaviour.
jdbcTypeThe JDBC Type from the list of supported types that follows this table. The JDBC type is only required for nullable columns upon insert, update or delete. This is a JDBC requirement, not an MyBatis one. So even if you were coding JDBC directly, you’d need to specify this type – but only for nullable values.
typeHandlerWe discussed default type handlers previously in this documentation. Using this property you can override the default type handler on a mapping-by-mapping basis. The value is either a fully qualified class name of a TypeHandler implementation, or a type alias.

Nested Select for Association

AttributeDescription
columnThe column name from the database, or the aliased column label that holds the value that will be passed to the nested statement as an input parameter. This is the same string that would normally be passed to resultSet.getString(columnName). Note: To deal with composite keys, you can specify multiple column names to pass to the nested select statement by using the syntax column=”{prop1=col1,prop2=col2}”. This will cause prop1 and prop2 to be set against the parameter object for the target nested select statement.
selectThe ID of another mapped statement that will load the complex type required by this property mapping. The values retrieved from columns specified in the column attribute will be passed to the target select statement as parameters. A detailed example follows this table. Note: To deal with composite keys, you can specify multiple column names to pass to the nested select statement by using the syntax column=”{prop1=col1,prop2=col2}”. This will cause prop1 and prop2 to be set against the parameter object for the target nested select statement.
fetchTypeOptional. Valid values are lazy and eager. If present, it supersedes the global configuration parameter lazyLoadingEnabled for this mapping.

For example:

  1. <resultMap id="blogResult" type="Blog">
  2. <association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
  3. </resultMap>
  4. <select id="selectBlog" resultMap="blogResult">
  5. SELECT * FROM BLOG WHERE ID = #{id}
  6. </select>
  7. <select id="selectAuthor" resultType="Author">
  8. SELECT * FROM AUTHOR WHERE ID = #{id}
  9. </select>

That’s it. We have two select statements: one to load the Blog, the other to load the Author, and the Blog’s resultMap describes that the selectAuthor statement should be used to load its author property.

All other properties will be loaded automatically assuming their column and property names match.

While this approach is simple, it will not perform well for large data sets or lists. This problem is known as the “N+1 Selects Problem”. In a nutshell, the N+1 selects problem is caused like this:

  • You execute a single SQL statement to retrieve a list of records (the “+1”).
  • For each record returned, you execute a select statement to load details for each (the “N”).

This problem could result in hundreds or thousands of SQL statements to be executed. This is not always desirable.

The upside is that MyBatis can lazy load such queries, thus you might be spared the cost of these statements all at once. However, if you load such a list and then immediately iterate through it to access the nested data, you will invoke all of the lazy loads, and thus performance could be very bad.

And so, there is another way.

Nested Results for Association

AttributeDescription
resultMapThis is the ID of a ResultMap that can map the nested results of this association into an appropriate object graph. This is an alternative to using a call to another select statement. It allows you to join multiple tables together into a single ResultSet. Such a ResultSet will contain duplicated, repeating groups of data that needs to be decomposed and mapped properly to a nested object graph. To facilitate this, MyBatis lets you “chain” result maps together, to deal with the nested results. An example will be far easier to follow, and one follows this table.
columnPrefixWhen joining multiple tables, you would have to use column alias to avoid duplicated column names in the ResultSet. Specifying columnPrefix allows you to map such columns to an external resultMap. Please see the example explained later in this section.
notNullColumnBy default a child object is created only if at least one of the columns mapped to the child’s properties is non null. With this attribute you can change this behaviour by specifiying which columns must have a value so MyBatis will create a child object only if any of those columns is not null. Multiple column names can be specified using a comma as a separator. Default value: unset.
autoMappingIf present, MyBatis will enable or disable automapping when mapping the result to this property. This attribute overrides the global autoMappingBehavior. Note that it has no effect on an external resultMap, so it is pointless to use it with select or resultMap attribute. Default value: unset.

You’ve already seen a very complicated example of nested associations above. The following is a far simpler example to demonstrate how this works. Instead of executing a separate statement, we’ll join the Blog and Author tables together, like so:

  1. <select id="selectBlog" resultMap="blogResult">
  2. select
  3. B.id as blog_id,
  4. B.title as blog_title,
  5. B.author_id as blog_author_id,
  6. A.id as author_id,
  7. A.username as author_username,
  8. A.password as author_password,
  9. A.email as author_email,
  10. A.bio as author_bio
  11. from Blog B left outer join Author A on B.author_id = A.id
  12. where B.id = #{id}
  13. </select>

Notice the join, as well as the care taken to ensure that all results are aliased with a unique and clear name. This makes mapping far easier. Now we can map the results:

  1. <resultMap id="blogResult" type="Blog">
  2. <id property="id" column="blog_id" />
  3. <result property="title" column="blog_title"/>
  4. <association property="author" resultMap="authorResult" />
  5. </resultMap>
  6. <resultMap id="authorResult" type="Author">
  7. <id property="id" column="author_id"/>
  8. <result property="username" column="author_username"/>
  9. <result property="password" column="author_password"/>
  10. <result property="email" column="author_email"/>
  11. <result property="bio" column="author_bio"/>
  12. </resultMap>

In the example above you can see at the Blog’s “author” association delegates to the “authorResult” resultMap to load the Author instance.

Very Important: id elements play a very important role in Nested Result mapping. You should always specify one or more properties that can be used to uniquely identify the results. The truth is that MyBatis will still work if you leave it out, but at a severe performance cost. Choose as few properties as possible that can uniquely identify the result. The primary key is an obvious choice (even if composite).

Now, the above example used an external resultMap element to map the association. This makes the Author resultMap reusable. However, if you have no need to reuse it, or if you simply prefer to co-locate your result mappings into a single descriptive resultMap, you can nest the association result mappings. Here’s the same example using this approach:

  1. <resultMap id="blogResult" type="Blog">
  2. <id property="id" column="blog_id" />
  3. <result property="title" column="blog_title"/>
  4. <association property="author" javaType="Author">
  5. <id property="id" column="author_id"/>
  6. <result property="username" column="author_username"/>
  7. <result property="password" column="author_password"/>
  8. <result property="email" column="author_email"/>
  9. <result property="bio" column="author_bio"/>
  10. </association>
  11. </resultMap>

What if the blog has a co-author? The select statement would look like:

  1. <select id="selectBlog" resultMap="blogResult">
  2. select
  3. B.id as blog_id,
  4. B.title as blog_title,
  5. A.id as author_id,
  6. A.username as author_username,
  7. A.password as author_password,
  8. A.email as author_email,
  9. A.bio as author_bio,
  10. CA.id as co_author_id,
  11. CA.username as co_author_username,
  12. CA.password as co_author_password,
  13. CA.email as co_author_email,
  14. CA.bio as co_author_bio
  15. from Blog B
  16. left outer join Author A on B.author_id = A.id
  17. left outer join Author CA on B.co_author_id = CA.id
  18. where B.id = #{id}
  19. </select>

Recall that the resultMap for Author is defined as follows.

  1. <resultMap id="authorResult" type="Author">
  2. <id property="id" column="author_id"/>
  3. <result property="username" column="author_username"/>
  4. <result property="password" column="author_password"/>
  5. <result property="email" column="author_email"/>
  6. <result property="bio" column="author_bio"/>
  7. </resultMap>

Because the column names in the results differ from the columns defined in the resultMap, you need to specify columnPrefix to reuse the resultMap for mapping co-author results.

  1. <resultMap id="blogResult" type="Blog">
  2. <id property="id" column="blog_id" />
  3. <result property="title" column="blog_title"/>
  4. <association property="author"
  5. resultMap="authorResult" />
  6. <association property="coAuthor"
  7. resultMap="authorResult"
  8. columnPrefix="co_" />
  9. </resultMap>

Multiple ResultSets for Association

AttributeDescription
columnWhen using multiple resultset this attribute specifies the columns (separated by commas) that will be correlated with the foreignColumn to identify the parent and the child of a relationship.
foreignColumnIdentifies the name of the columns that contains the foreign keys which values will be matched against the values of the columns specified in the column attibute of the parent type.
resultSetIdentifies the name of the result set where this complex type will be loaded from.

Starting from version 3.2.3 MyBatis provides yet another way to solve the N+1 problem.

Some databases allow stored procedures to return more than one resultset or execute more than one statement at once and return a resultset per each one. This can be used to hit the database just once and return related data without using a join.

In the example, the stored procedure executes the following queries and returns two result sets. The first will contain Blogs and the second Authors.

  1. SELECT * FROM BLOG WHERE ID = #{id}
  2. SELECT * FROM AUTHOR WHERE ID = #{id}

A name must be given to each result set by adding a resultSets attribute to the mapped statement with a list of names separated by commas.

  1. <select id="selectBlog" resultSets="blogs,authors" resultMap="blogResult" statementType="CALLABLE">
  2. {call getBlogsAndAuthors(#{id,jdbcType=INTEGER,mode=IN})}
  3. </select>

Now we can specify that the data to fill the “author” association comes in the “authors” result set:

  1. <resultMap id="blogResult" type="Blog">
  2. <id property="id" column="id" />
  3. <result property="title" column="title"/>
  4. <association property="author" javaType="Author" resultSet="authors" column="author_id" foreignColumn="id">
  5. <id property="id" column="id"/>
  6. <result property="username" column="username"/>
  7. <result property="password" column="password"/>
  8. <result property="email" column="email"/>
  9. <result property="bio" column="bio"/>
  10. </association>
  11. </resultMap>

You’ve seen above how to deal with a “has one” type association. But what about “has many”? That’s the subject of the next section.

collection

  1. <collection property="posts" ofType="domain.blog.Post">
  2. <id property="id" column="post_id"/>
  3. <result property="subject" column="post_subject"/>
  4. <result property="body" column="post_body"/>
  5. </collection>

The collection element works almost identically to the association. In fact, it’s so similar, to document the similarities would be redundant. So let’s focus on the differences.

To continue with our example above, a Blog only had one Author. But a Blog has many Posts. On the blog class, this would be represented by something like:

  1. private List<Post> posts;

To map a set of nested results to a List like this, we use the collection element. Just like the association element, we can use a nested select, or nested results from a join.

Nested Select for Collection

First, let’s look at using a nested select to load the Posts for the Blog.

  1. <resultMap id="blogResult" type="Blog">
  2. <collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
  3. </resultMap>
  4. <select id="selectBlog" resultMap="blogResult">
  5. SELECT * FROM BLOG WHERE ID = #{id}
  6. </select>
  7. <select id="selectPostsForBlog" resultType="Post">
  8. SELECT * FROM POST WHERE BLOG_ID = #{id}
  9. </select>

There are a number things you’ll notice immediately, but for the most part it looks very similar to the association element we learned about above. First, you’ll notice that we’re using the collection element. Then you’ll notice that there’s a new “ofType” attribute. This attribute is necessary to distinguish between the JavaBean (or field) property type and the type that the collection contains. So you could read the following mapping like this:

  1. <collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>

Read as: “A collection of posts in an ArrayList of type Post.”

The javaType attribute is really unnecessary, as MyBatis will figure this out for you in most cases. So you can often shorten this down to simply:

  1. <collection property="posts" column="id" ofType="Post" select="selectPostsForBlog"/>

Nested Results for Collection

By this point, you can probably guess how nested results for a collection will work, because it’s exactly the same as an association, but with the same addition of the ofType attribute applied.

First, let’s look at the SQL:

  1. <select id="selectBlog" resultMap="blogResult">
  2. select
  3. B.id as blog_id,
  4. B.title as blog_title,
  5. B.author_id as blog_author_id,
  6. P.id as post_id,
  7. P.subject as post_subject,
  8. P.body as post_body,
  9. from Blog B
  10. left outer join Post P on B.id = P.blog_id
  11. where B.id = #{id}
  12. </select>

Again, we’ve joined the Blog and Post tables, and have taken care to ensure quality result column labels for simple mapping. Now mapping a Blog with its collection of Post mappings is as simple as:

  1. <resultMap id="blogResult" type="Blog">
  2. <id property="id" column="blog_id" />
  3. <result property="title" column="blog_title"/>
  4. <collection property="posts" ofType="Post">
  5. <id property="id" column="post_id"/>
  6. <result property="subject" column="post_subject"/>
  7. <result property="body" column="post_body"/>
  8. </collection>
  9. </resultMap>

Again, remember the importance of the id elements here, or read the association section above if you haven’t already.

Also, if you prefer the longer form that allows for more reusability of your result maps, you can use the following alternative mapping:

  1. <resultMap id="blogResult" type="Blog">
  2. <id property="id" column="blog_id" />
  3. <result property="title" column="blog_title"/>
  4. <collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/>
  5. </resultMap>
  6. <resultMap id="blogPostResult" type="Post">
  7. <id property="id" column="id"/>
  8. <result property="subject" column="subject"/>
  9. <result property="body" column="body"/>
  10. </resultMap>

Multiple ResultSets for Collection

As we did for the association, we can call a stored procedure that executes two queries and returns two result sets, one with Blogs and another with Posts:

  1. SELECT * FROM BLOG WHERE ID = #{id}
  2. SELECT * FROM POST WHERE BLOG_ID = #{id}

A name must be given to each result set by adding a resultSets attribute to the mapped statement with a list of names separated by commas.

  1. <select id="selectBlog" resultSets="blogs,posts" resultMap="blogResult">
  2. {call getBlogsAndPosts(#{id,jdbcType=INTEGER,mode=IN})}
  3. </select>

We specify that the “posts” collection will be filled out of data contained in the result set named “posts”:

  1. <resultMap id="blogResult" type="Blog">
  2. <id property="id" column="id" />
  3. <result property="title" column="title"/>
  4. <collection property="posts" ofType="Post" resultSet="posts" column="id" foreignColumn="blog_id">
  5. <id property="id" column="id"/>
  6. <result property="subject" column="subject"/>
  7. <result property="body" column="body"/>
  8. </collection>
  9. </resultMap>

NOTE There’s no limit to the depth, breadth or combinations of the associations and collections that you map. You should keep performance in mind when mapping them. Unit testing and performance testing of your application goes a long way toward discovering the best approach for your application. The nice thing is that MyBatis lets you change your mind later, with very little (if any) impact to your code.

Advanced association and collection mapping is a deep subject. Documentation can only get you so far. With a little practice, it will all become clear very quickly.

discriminator

  1. <discriminator javaType="int" column="draft">
  2. <case value="1" resultType="DraftPost"/>
  3. </discriminator>

Sometimes a single database query might return result sets of many different (but hopefully somewhat related) data types. The discriminator element was designed to deal with this situation, and others, including class inheritance hierarchies. The discriminator is pretty simple to understand, as it behaves much like a switch statement in Java.

A discriminator definition specifies column and javaType attributes. The column is where MyBatis will look for the value to compare. The javaType is required to ensure the proper kind of equality test is performed (although String would probably work for almost any situation). For example:

  1. <resultMap id="vehicleResult" type="Vehicle">
  2. <id property="id" column="id" />
  3. <result property="vin" column="vin"/>
  4. <result property="year" column="year"/>
  5. <result property="make" column="make"/>
  6. <result property="model" column="model"/>
  7. <result property="color" column="color"/>
  8. <discriminator javaType="int" column="vehicle_type">
  9. <case value="1" resultMap="carResult"/>
  10. <case value="2" resultMap="truckResult"/>
  11. <case value="3" resultMap="vanResult"/>
  12. <case value="4" resultMap="suvResult"/>
  13. </discriminator>
  14. </resultMap>

In this example, MyBatis would retrieve each record from the result set and compare its vehicle type value. If it matches any of the discriminator cases, then it will use the resultMap specified by the case. This is done exclusively, so in other words, the rest of the resultMap is ignored (unless it is extended, which we talk about in a second). If none of the cases match, then MyBatis simply uses the resultMap as defined outside of the discriminator block. So, if the carResult was declared as follows:

  1. <resultMap id="carResult" type="Car">
  2. <result property="doorCount" column="door_count" />
  3. </resultMap>

Then ONLY the doorCount property would be loaded. This is done to allow completely independent groups of discriminator cases, even ones that have no relationship to the parent resultMap. In this case we do of course know that there’s a relationship between cars and vehicles, as a Car is-a Vehicle. Therefore, we want the rest of the properties loaded too. One simple change to the resultMap and we’re set to go.

  1. <resultMap id="carResult" type="Car" extends="vehicleResult">
  2. <result property="doorCount" column="door_count" />
  3. </resultMap>

Now all of the properties from both the vehicleResult and carResult will be loaded.

Once again though, some may find this external definition of maps somewhat tedious. Therefore there’s an alternative syntax for those that prefer a more concise mapping style. For example:

  1. <resultMap id="vehicleResult" type="Vehicle">
  2. <id property="id" column="id" />
  3. <result property="vin" column="vin"/>
  4. <result property="year" column="year"/>
  5. <result property="make" column="make"/>
  6. <result property="model" column="model"/>
  7. <result property="color" column="color"/>
  8. <discriminator javaType="int" column="vehicle_type">
  9. <case value="1" resultType="carResult">
  10. <result property="doorCount" column="door_count" />
  11. </case>
  12. <case value="2" resultType="truckResult">
  13. <result property="boxSize" column="box_size" />
  14. <result property="extendedCab" column="extended_cab" />
  15. </case>
  16. <case value="3" resultType="vanResult">
  17. <result property="powerSlidingDoor" column="power_sliding_door" />
  18. </case>
  19. <case value="4" resultType="suvResult">
  20. <result property="allWheelDrive" column="all_wheel_drive" />
  21. </case>
  22. </discriminator>
  23. </resultMap>

NOTE Remember that these are all Result Maps, and if you don’t specify any results at all, then MyBatis will automatically match up columns and properties for you. So most of these examples are more verbose than they really need to be. That said, most databases are kind of complex and it’s unlikely that we’ll be able to depend on that for all cases.