Fading Coder

An Old Coder’s Final Dance

Home > Tech > Content

MyBatis ResultMap and Nested Associations

Tech 2

When mapping query results in MyBatis, you can choose betwean resultType and resultMap. Only one can be used in a single select. With resultType, MyBatis relies on column-to-property name matching (and aliases) to populate a target class. With resultMap, you describe exact how each column should be mapped, and you can define object graphs (one-to-one and one-to-many) via association and collection. MyBatis internally represents every mapping as a ResultMap; resultType is a convenient shorthand for straightforward cases.

Domain model

Post.java

package org.example.mybatis.domain;

import java.util.List;

public class Post {
    private long postId;
    private String headline;
    private String body;
    private String author;
    private List<Comment> comments;

    public long getPostId() { return postId; }
    public void setPostId(long postId) { this.postId = postId; }

    public String getHeadline() { return headline; }
    public void setHeadline(String headline) { this.headline = headline; }

    public String getBody() { return body; }
    public void setBody(String body) { this.body = body; }

    public String getAuthor() { return author; }
    public void setAuthor(String author) { this.author = author; }

    public List<Comment> getComments() { return comments; }
    public void setComments(List<Comment> comments) { this.comments = comments; }
}

Comment.java

package org.example.mybatis.domain;

import java.util.Date;

public class Comment {
    private long commentId;
    private String message;
    private Date createdAt;
    private Post post; // owning Post

    public long getCommentId() { return commentId; }
    public void setCommentId(long commentId) { this.commentId = commentId; }

    public String getMessage() { return message; }
    public void setMessage(String message) { this.message = message; }

    public Date getCreatedAt() { return createdAt; }
    public void setCreatedAt(Date createdAt) { this.createdAt = createdAt; }

    public Post getPost() { return post; }
    public void setPost(Post post) { this.post = post; }
}

Assume relational tables:

  • posts(id, title, content, author)
  • comments(id, text, created_at, post_id)

Basic mapping with resultType

<!-- aliases -->
<typeAliases>
  <typeAlias alias="Post" type="org.example.mybatis.domain.Post"/>
  <typeAlias alias="Comment" type="org.example.mybatis.domain.Comment"/>
</typeAliases>

<!-- simple mapping using resultType -->
<select id="getPost" parameterType="long" resultType="Post">
  select
    p.id      as postId,
    p.title   as headline,
    p.content as body,
    p.author  as author
  from posts p
  where p.id = #{value}
</select>

With resultType, MyBatis uses the column labels (or aliases) to match bean properties. Here aliases map id→postId, title→headline, content→body.

Explicit ResultMap for the same query

<resultMap id="PostMap" type="Post">
  <id     column="id"      property="postId"/>
  <result column="title"   property="headline"/>
  <result column="content" property="body"/>
  <result column="author"  property="author"/>
</resultMap>

<select id="getPostByMap" parameterType="long" resultMap="PostMap">
  select id, title, content, author from posts where id = #{value}
</select>

Defining the id element helps MyBatis identify unique rows, which matters when assembling collections from joined or nested results.

One-to-one: Comment → Post via association

A Comment references its owning Post. Use association with a nested select.

<resultMap id="CommentMap" type="Comment">
  <id     column="id"          property="commentId"/>
  <result column="text"        property="message"/>
  <result column="created_at"  property="createdAt"/>
  <association property="post"
               column="post_id"
               select="getPostByMap"
               javaType="Post"/>
</resultMap>

<select id="getComment" parameterType="long" resultMap="CommentMap">
  select c.id, c.text, c.created_at, c.post_id
  from comments c
  where c.id = #{value}
</select>
  • property: target property on the result type (Comment.post)
  • select: the nested statement that loads the associated object
  • column: the current row’s value past to the nested select as the parameter object
  • javaType: the type of the associated property

The nested select receives a simple parameter (the post_id value). Inside getPostByMap, reference it with #{value} or #{_parameter}.

One-to-many: Post → Comments via collection

Load a Post and its Comments with a nested select. Use ofType for collection element type.

<resultMap id="PostWithComments" type="Post">
  <id     column="id"      property="postId"/>
  <result column="title"   property="headline"/>
  <result column="content" property="body"/>
  <result column="author"  property="author"/>
  <collection property="comments"
              ofType="Comment"
              column="id"
              select="findCommentsForPost"/>
</resultMap>

<select id="findPostWithComments" parameterType="long" resultMap="PostWithComments">
  select p.id, p.title, p.content, p.author from posts p where p.id = #{value}
</select>

<select id="findCommentsForPost" parameterType="long" resultMap="CommentMap">
  select id, text, created_at, post_id
  from comments
  where post_id = #{value}
</select>
  • collection maps a property that is a List/Set
  • ofType is the element type of the collection
  • column passes the outer row’s id into the nested select

Mapper interfaces and sample tests

PostMapper.java

package org.example.mybatis.mapper;

import org.example.mybatis.domain.Post;

public interface PostMapper {
    Post getPost(long id);
    Post getPostByMap(long id);
    Post findPostWithComments(long id);
}

CommentMapper.java

package org.example.mybatis.mapper;

import java.util.List;
import org.example.mybatis.domain.Comment;

public interface CommentMapper {
    Comment getComment(long id);
    List<Comment> findCommentsForPost(long postId);
}

JUnit examples

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.example.mybatis.domain.Comment;
import org.example.mybatis.domain.Post;
import org.example.mybatis.mapper.CommentMapper;
import org.example.mybatis.mapper.PostMapper;
import org.junit.Test;

import java.util.List;

public class MyBatisResultMapTest {

    private final SqlSessionFactory sqlSessionFactory = MyBatisBootstrap.build();

    @Test
    public void loadCommentsByPost() {
        try (SqlSession session = sqlSessionFactory.openSession()) {
            CommentMapper mapper = session.getMapper(CommentMapper.class);
            List<Comment> list = mapper.findCommentsForPost(10L);
            for (Comment c : list) {
                System.out.printf("%d %s%n", c.getCommentId(), c.getMessage());
            }
        }
    }

    @Test
    public void loadPostGraph() {
        try (SqlSession session = sqlSessionFactory.openSession()) {
            PostMapper mapper = session.getMapper(PostMapper.class);
            Post post = mapper.findPostWithComments(10L);
            if (post.getComments() != null) {
                System.out.println("comments=" + post.getComments().size());
            }
        }
    }
}

Notes on automatic mapping

  • With resultType, MyBatis maps columns to properties by name. Aliases help align column names to bean properties.
  • With resultMap, you can be explicit and compose graphs. MyBatis still performs automatic mapping for any properties not explicit listed if auto-mapping is enabled.
  • When using nested selects, the value from column is passed as the paramter object. For simple types, reference it with #{value} or #{_parameter}.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.