MyBatis provides powerful mapping capabilities for converting SQL result sets into Java objects, including nested associations and collections.

Result Maps

  <resultMap id="OrderResultMap" type="Order">
    <id property="id" column="order_id"/>
    <result property="orderDate" column="order_date"/>
    <result property="totalAmount" column="total_amount"/>
    <result property="status" column="status"/>
</resultMap>

<select id="findById" resultMap="OrderResultMap">
    SELECT order_id, order_date, total_amount, status
    FROM orders WHERE order_id = #{id}
</select>
  

Association (One-to-One / Many-to-One)

  <resultMap id="OrderWithCustomerMap" type="Order">
    <id property="id" column="order_id"/>
    <result property="orderDate" column="order_date"/>
    <association property="customer" javaType="Customer">
        <id property="id" column="customer_id"/>
        <result property="name" column="customer_name"/>
        <result property="email" column="customer_email"/>
    </association>
</resultMap>

<select id="findByIdWithCustomer" resultMap="OrderWithCustomerMap">
    SELECT o.order_id, o.order_date,
           c.id AS customer_id, c.name AS customer_name, c.email AS customer_email
    FROM orders o
    JOIN customers c ON o.customer_id = c.id
    WHERE o.order_id = #{id}
</select>
  

Collection (One-to-Many)

  <resultMap id="OrderWithItemsMap" type="Order">
    <id property="id" column="order_id"/>
    <result property="orderDate" column="order_date"/>
    <collection property="items" ofType="OrderItem">
        <id property="id" column="item_id"/>
        <result property="productName" column="product_name"/>
        <result property="quantity" column="quantity"/>
        <result property="unitPrice" column="unit_price"/>
    </collection>
</resultMap>

<select id="findByIdWithItems" resultMap="OrderWithItemsMap">
    SELECT o.order_id, o.order_date,
           i.id AS item_id, i.product_name, i.quantity, i.unit_price
    FROM orders o
    JOIN order_items i ON o.order_id = i.order_id
    WHERE o.order_id = #{id}
</select>
  

Nested Select (Lazy Loading)

  <resultMap id="OrderLazyMap" type="Order">
    <id property="id" column="order_id"/>
    <association property="customer" column="customer_id"
                 select="com.example.mapper.CustomerMapper.findById"/>
    <collection property="items" column="order_id"
                select="com.example.mapper.OrderItemMapper.findByOrderId"/>
</resultMap>
  

Enable lazy loading:

  mybatis:
  configuration:
    lazy-loading-enabled: true
    aggressive-lazy-loading: false
  

Type Handlers

Custom Java ↔ JDBC type conversion:

  @MappedTypes(EmployeeStatus.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class EmployeeStatusTypeHandler extends BaseTypeHandler<EmployeeStatus> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i,
                                    EmployeeStatus status, JdbcType jdbcType) throws SQLException {
        ps.setString(i, status.name());
    }

    @Override
    public EmployeeStatus getNullableResult(ResultSet rs, String column) throws SQLException {
        return EmployeeStatus.valueOf(rs.getString(column));
    }
    // ... other getNullableResult overloads
}
  

Register globally:

  mybatis:
  type-handlers-package: com.example.handler
  

Enum Mapping

  <!-- Store as string -->
<result property="status" column="status"
        typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>

<!-- Or use MyBatis default enum ordinal/string mapping -->
  

Constructor Mapping

Map directly to constructor:

  <resultMap id="UserDtoMap" type="com.example.dto.UserDto">
    <constructor>
        <idArg column="id" javaType="long"/>
        <arg column="email" javaType="string"/>
        <arg column="name" javaType="string"/>
    </constructor>
</resultMap>
  

Best Practices

  • Use explicit resultMap for complex mappings; avoid resultType with joins
  • Prefer join queries over nested selects to avoid N+1 problems
  • Use column aliases to distinguish fields in join queries
  • Register custom type handlers for non-standard Java ↔ SQL type mappings
  • Test result maps thoroughly — silent mapping failures are common