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:
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:
<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.
<insert id="insert"
parameterType="com.example.Task"
useGeneratedKeys="true"
keyProperty="id">
INSERT INTO task (name) VALUES (#{name})
</insert>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 #
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:
<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:
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.
Comments
Read or comment on GitHub (sign-in required to comment)