On this page
MyBatis SQL Mapping
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
resultMapfor complex mappings; avoidresultTypewith 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