MyBatis Mapping

Last update: 2025-10-21 15:55:03 +0800 +0800
tags: Java | Mybatis | Sql
Available translations: 日本語 / 中文(简体) / 中文(繁體)

MyBatis maps Java calls to SQL statements while JDBC owns parameter binding, statement execution, and result-set access. A mapper should make that boundary visible: SQL column names belong in the statement, Java property names belong in parameter and result mappings, and a type handler performs any nonstandard conversion between them.

Parameter and Result Boundaries #

Simple parameters can use #{id} placeholders. Object and map parameters expose named properties, while multiple mapper parameters should use @Param so the names do not depend on compiler metadata:

java
List<Task> findByState(@Param("state") State state,
                       @Param("limit") int limit);

#{...} creates a JDBC parameter marker and binds a value. ${...} substitutes text into SQL before preparation, so reserve it for allow-listed identifiers or fragments that cannot be parameterized. Never place request data directly in a ${...} expression.

Automatic result mapping is concise when column labels and Java properties match. Use resultMap when names differ, nested objects require explicit identity, or a join can duplicate parent rows:

xml
<resultMap id="taskResult" type="com.example.Task">
  <id property="id" column="task_id"/>
  <result property="name" column="task_name"/>
  <result property="state" column="state"
          typeHandler="com.example.StateTypeHandler"/>
</resultMap>

The <id> mapping identifies an object within a joined result and helps MyBatis coalesce repeated parent data. It does not declare a database constraint; the schema must still enforce identity.

Generated Keys #

For a database and JDBC driver that support generated keys, enable useGeneratedKeys and identify the writable Java property. MyBatis calls the JDBC generated-key API after the insert and assigns the generated primary key to that property.

xml
<insert id="insert"
        parameterType="com.example.Task"
        useGeneratedKeys="true"
        keyProperty="id">
  INSERT INTO task (name) VALUES (#{name})
</insert>
java
Task task = new Task();
task.setName("import");
mapper.insert(task);
long id = task.getId();

The assignment fills the generated-key property only. It does not reload database defaults, trigger-generated columns, or unrelated fields. Use a follow-up SELECT, a database RETURNING clause where supported, or a selectKey statement when the database uses a sequence or a driver does not support getGeneratedKeys.

Null Parameters #

xml
UPDATE task
SET completed_at = #{completedAt, jdbcType=TIMESTAMP}
WHERE id = #{id}

Set a global jdbcTypeForNull only when the database convention is compatible with every nullable parameter. A per-parameter type is clearer for columns with distinct SQL types. Test inserts and updates against the production driver, because generated-key and null-binding behavior is driver-specific.

Dynamic SQL and Type Handlers #

Dynamic elements construct statement structure without concatenating arbitrary application text:

xml
<select id="find" resultMap="taskResult">
  SELECT id AS task_id, name AS task_name, state
  FROM task
  <where>
    <if test="state != null">
      state = #{state, typeHandler=com.example.StateTypeHandler}
    </if>
  </where>
  ORDER BY id
</select>

<where> inserts WHERE only when a child emits a condition and removes a leading AND or OR. Related elements include <choose>, <set>, <trim>, and <foreach>. A TypeHandler should define a deliberate Java/JDBC representation, such as an enum stored as a stable string; register it narrowly when different columns encode the same Java type differently.

Session and Transaction Ownership #

A mapped statement runs through a SqlSession. With direct MyBatis usage, the caller opens the session, commits or rolls back, and closes it:

java
try (SqlSession session = factory.openSession()) {
    TaskMapper mapper = session.getMapper(TaskMapper.class);
    mapper.insert(task);
    session.commit();
}

The default session is not autocommit, and SqlSession is not thread-safe. Framework integrations such as MyBatis-Spring bind sessions to framework-managed transactions; application code must not mix manual commits with that ownership model. Generated-key assignment occurs during statement execution, but the row is durable only after the owning transaction commits.

References #

Comments

Read or comment on GitHub (sign-in required to comment)