文档站点
Skip to content

MyBatis XML Mapper ​

上一篇是 MyBatis 的 CRUD 基础。这篇讲 XML Mapper 的三个高级武器:动态 SQL(对应 Eloquent 的条件拼接)、结果映射(对应模型关联)、关联查询。

动态 SQL:<if>、<where>、<foreach> ​

Laravel 里你写:

php
$query = Post::query();
if ($keyword) $query->where('title', 'like', "%$keyword%");
if ($status !== null) $query->where('status', $status);
return $query->get();

MyBatis 的动态 SQL 写在 XML 里,效果一样但更「声明式」:

xml
<select id="search" resultType="Post">
    SELECT * FROM posts
    <where>                                    <!-- 自动处理 AND/WHERE -->
        <if test="keyword != null and keyword != ''">
            AND title LIKE CONCAT('%', #{keyword}, '%')
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
        <if test="userId != null">
            AND user_id = #{userId}
        </if>
    </where>
    ORDER BY id DESC
</select>

<where> 标签的妙处:子句都为空时它输出 WHERE 1=1 都不需要,首个子句开头的 AND 会自动去掉。

<foreach>:批量操作 ​

对应 Eloquent 的 whereIn:

xml
<select id="findByIds" resultType="Post">
    SELECT * FROM posts WHERE id IN
    <foreach collection="ids" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>
java
List<Post> findByIds(@Param("ids") List<Long> ids);

批量插入:

xml
<insert id="batchInsert">
    INSERT INTO posts (title, content, user_id) VALUES
    <foreach collection="list" item="p" separator=",">
        (#{p.title}, #{p.content}, #{p.userId})
    </foreach>
</insert>

<choose>:if / else ​

xml
<choose>
    <when test="status != null">
        AND status = #{status}
    </when>
    <otherwise>
        AND status = 1
    </otherwise>
</choose>

动态 SQL 是 MyBatis 的灵魂

它是「Java 代码拼 SQL」的替代品,把条件逻辑放回 SQL 层,可读性和维护性都更好。<if> + <where> + <foreach> + <choose> 四个标签覆盖 95% 场景。

结果映射:<resultMap> ​

resultType="Post" 靠「列名 = 字段名」自动映射。复杂情况用 <resultMap> 精确定义:

xml
<resultMap id="postMap" type="Post">
    <id property="id" column="id"/>
    <result property="title" column="title"/>
    <result property="content" column="content"/>
    <result property="userId" column="user_id"/>
    <result property="createdAt" column="created_at"/>
</resultMap>

<select id="findById" resultMap="postMap">
    SELECT * FROM posts WHERE id = #{id}
</select>

TIP

开了 map-underscore-to-camel-case 后,简单映射都不用写 resultMap。只有列名和字段名对不上、或做关联映射时才需要。

关联查询:JOIN + association / collection ​

对应 Eloquent 的 with('user')(取帖子的同时带上作者):

xml
<resultMap id="postWithUser" type="Post">
    <id property="id" column="id"/>
    <result property="title" column="title"/>
    <result property="content" column="content"/>
    <association property="user" javaType="User">
        <id property="id" column="user_id"/>
        <result property="username" column="username"/>
    </association>
</resultMap>

<select id="findAllWithUser" resultMap="postWithUser">
    SELECT p.*, u.username
    FROM posts p
    JOIN users u ON u.id = p.user_id
    ORDER BY p.id DESC
</select>
  • <association>:一对一(belongsTo)
  • <collection>:一对多(hasMany)

TIP

JOIN 是 SQL 层自己写,比 JPA 的懒加载直观得多。这是很多人选 MyBatis 的原因:关联查询完全可见、可优化,没有 N+1 的黑魔法。

一对多:collection ​

xml
<resultMap id="userWithPosts" type="User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <collection property="posts" ofType="Post">
        <id property="id" column="post_id"/>
        <result property="title" column="post_title"/>
    </collection>
</resultMap>
xml
<select id="findUserWithPosts" resultMap="userWithPosts">
    SELECT u.id, u.username, p.id AS post_id, p.title AS post_title
    FROM users u
    LEFT JOIN posts p ON p.user_id = u.id
    WHERE u.id = #{id}
</select>

分页 ​

手动 LIMIT(配合 分页 里的 PageHelper 更省事):

xml
<select id="findPage" resultType="Post">
    SELECT * FROM posts ORDER BY id DESC
</select>
java
PageHelper.startPage(page, size);
List<Post> list = postMapper.findPage();
PageInfo<Post> info = new PageInfo<>(list);

代码生成:MyBatis Generator / MyBatis-Plus ​

国内很多项目直接用 MyBatis-Plus,它给 MyBatis 补上了「自动 CRUD + 条件构造器」(类似 Eloquent 的 Query Builder):

java
// MyBatis-Plus:继承 BaseMapper 就有 CRUD
public interface PostMapper extends BaseMapper<Post> {
}

// 条件构造器(≈ Eloquent query)
List<Post> list = postMapper.selectList(
        new LambdaQueryWrapper<Post>()
                .eq(Post::getStatus, 1)
                .like(Post::getTitle, "Spring"));

TIP

  • 原生 MyBatis:SQL 全手写,掌控力最强
  • MyBatis-Plus:国内主流,自带 CRUD 模板 + 分页插件 + 条件构造器,开发效率高 新手建议先学原生 MyBatis(本篇),再按团队技术栈决定要不要上 MyBatis-Plus。

XML Mapper 最佳实践清单 ​

  1. XML 放 src/main/resources/mapper/,命名与 Mapper 接口一致
  2. namespace 写 Mapper 接口全限定名,id 写方法名
  3. 动态 SQL 用 <where>/<if>/<foreach>,别在 Java 代码拼字符串
  4. 简单映射靠 map-underscore-to-camel-case,复杂关联用 <resultMap>
  5. #{xxx} 预编译防注入,永远别用 ${} 拼用户输入(${} 直接拼接,有注入风险)

面向 PHP / Laravel 开发者的 Spring Boot 中文文档