MyBatis 持久层框架:SQL 映射与动态查询

FreeGuideOnline 最新 2026-06-17

MyBatis 持久层框架教程:掌握 SQL 映射与动态查询

1. 什么是 MyBatis?

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及参数设置和结果集检索的工作。它通过简单的 XML 或注解,将 Java 接口和 POJO(Plain Old Java Objects)映射成数据库中的记录。

核心优势:

  • 精准的 SQL 控制:你可以直接编写最优化性能的 SQL,不会被框架生成的低效 SQL 所束缚。
  • 动态 SQL:根据条件灵活拼接 SQL,避免了复杂的字符串拼接和容易出错的逻辑判断。
  • 简洁的映射:通过 XML 或注解配置,自动将查询结果映射到 Java 对象,支持复杂关系映射(一对一、一对多)。
  • 轻量级集成:与 Spring、Spring Boot 无缝结合,易于引入和使用。

2. 环境准备与基本配置

在开始之前,确保你的项目已引入 MyBatis 依赖(Maven 示例):

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.13</version>
</dependency>
<!-- 若与 Spring 集成,还需 mybatis-spring -->

核心配置文件 mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
                <property name="username" value="root"/>
                <property name="password" value="password"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/example/mapper/UserMapper.xml"/>
    </mappers>
</configuration>

3. SQL 映射文件详解

SQL 映射文件是 MyBatis 的核心,它将 SQL 语句与 Mapper 接口的方法绑定。

3.1 基础映射示例

定义一个 User 实体:

public class User {
    private Integer id;
    private String username;
    private String email;
    // getters & setters...
}

创建 Mapper 接口:

public interface UserMapper {
    User selectUserById(Integer id);
    void insertUser(User user);
}

编写 UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">

    <!-- 定义 resultMap 解决列名与属性名不一致的情况 -->
    <resultMap id="userResultMap" type="com.example.entity.User">
        <id property="id" column="user_id"/>
        <result property="username" column="user_name"/>
        <result property="email" column="user_email"/>
    </resultMap>

    <!-- 根据 ID 查询 -->
    <select id="selectUserById" parameterType="int" resultMap="userResultMap">
        SELECT user_id, user_name, user_email FROM users WHERE user_id = #{id}
    </select>

    <!-- 插入用户,并获取自增主键 -->
    <insert id="insertUser" parameterType="com.example.entity.User"
            useGeneratedKeys="true" keyProperty="id">
        INSERT INTO users (user_name, user_email) VALUES (#{username}, #{email})
    </insert>
</mapper>

关键点:

  • namespace 必须与接口的全限定名一致。
  • #{属性名} 会使用预编译占位符 ?,安全防止 SQL 注入。
  • 使用 resultMap 可以定制复杂的映射关系。

4. 动态 SQL:让查询更智能

动态 SQL 是 MyBatis 最强大的特性之一,允许你根据传入参数的条件,动态生成 SQL 片段。

4.1 <if> 条件判断

<select id="findUsersByCondition" resultMap="userResultMap">
    SELECT * FROM users
    WHERE 1=1
    <if test="username != null and username != ''">
        AND user_name LIKE CONCAT('%', #{username}, '%')
    </if>
    <if test="email != null and email != ''">
        AND user_email = #{email}
    </if>
</select>

test 属性内使用 OGNL 表达式,可以访问元素的属性值。

4.2 <where> 优雅处理开头 AND/OR

上面的 WHERE 1=1 并不优雅,可用 <where> 标签自动移除第一个条件开头的 ANDOR

<select id="findUsersByCondition" resultMap="userResultMap">
    SELECT * FROM users
    <where>
        <if test="username != null and username != ''">
            AND user_name LIKE CONCAT('%', #{username}, '%')
        </if>
        <if test="email != null and email != ''">
            AND user_email = #{email}
        </if>
    </where>
</select>

4.3 <choose>, <when>, <otherwise> 多条件选择

类似 Java 的 switch 语句,只选择第一个匹配的条件。

<select id="findUserByPriority" resultMap="userResultMap">
    SELECT * FROM users
    <where>
        <choose>
            <when test="id != null">
                user_id = #{id}
            </when>
            <当 test="username != null">
                user_name = #{username}
            </when>
            <otherwise>
                user_email IS NOT NULL
            </otherwise>
        </choose>
    </where>
</select>

4.4 <set> 动态更新

更新时自动处理多余的逗号。

<update id="updateUserSelective">
    UPDATE users
    <set>
        <if test="username != null">user_name = #{username},</if>
        <if test="email != null">user_email = #{email},</if>
    </set>
    WHERE user_id = #{id}
</update>

4.5 <foreach> 遍历集合

用于构建 IN 条件或批量插入。

<select id="findUsersByIds" resultMap="userResultMap">
    SELECT * FROM users WHERE user_id IN
    <foreach item="id" collection="list" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>
  • collection:指定集合参数名,若为 List 可写 list,数组写 array
  • item:每次迭代的元素。
  • open, separator, close:拼接的符号。

5. MyBatis 与 Spring Boot 快速集成

在现代开发中,几乎不再单独使用 MyBatis,而是与 Spring Boot 整合。

添加依赖:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>

application.yml 配置:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb
    username: root
    password: password
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.entity
  configuration:
    map-underscore-to-camel-case: true   # 自动驼峰命名转换
    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl

定义 Mapper 接口时,使用 @Mapper 注解或在配置类上加 @MapperScan

@Mapper
public interface UserMapper {
    User selectUserById(Integer id);
    List<User> findUsersByCondition(@Param("username") String username,
                                    @Param("email") String email);
}

6. 进阶映射:一对一、一对多

6.1 一对一关联

假设一个用户对应一个扩展信息表 user_detail

UserDetail 实体包含 address, phone 等字段。在 User 中添加 private UserDetail detail;

映射文件使用 <association> 完成一对一映射:

<resultMap id="userWithDetail" type="User">
    <id property="id" column="user_id"/>
    <result property="username" column="user_name"/>
    <association property="detail" javaType="UserDetail">
        <id property="id" column="detail_id"/>
        <result property="address" column="address"/>
        <result property="phone" column="phone"/>
    </association>
</resultMap>

<select id="selectUserWithDetail" resultMap="userWithDetail">
    SELECT u.user_id, u.user_name, d.id AS detail_id, d.address, d.phone
    FROM users u LEFT JOIN user_detail d ON u.user_id = d.user_id
    WHERE u.user_id = #{id}
</select>

6.2 一对多关联

一个用户有多条订单记录。User 中添加 private List<Order> orders;

使用 <collection> 标签:

<resultMap id="userWithOrders" type="User">
    <id property="id" column="user_id"/>
    <result property="username" column="user_name"/>
    <collection property="orders" ofType="Order">
        <id property="orderId" column="order_id"/>
        <result property="orderName" column="order_name"/>
    </collection>
</resultMap>

<select id="selectUserWithOrders" resultMap="userWithOrders">
    SELECT u.user_id, u.user_name, o.order_id, o.order_name
    FROM users u LEFT JOIN orders o ON u.user_id = o.user_id
    WHERE u.user_id = #{id}
</select>

注意避免 N+1 查询问题:尽量使用一条 SQL 联表查询所有数据,而不是先查用户再循环查订单。

7. 常用技巧与最佳实践

  • 日志输出:在 mybatis-config.xml 中配置日志实现,方便调试 SQL。
  • 字段映射驼峰命名:开启 map-underscore-to-camel-case,数据库 user_name 自动映射到 username
  • 使用 @Param 传递多参数:接口方法有多个普通参数时,用 @Param 指定名称,以在 XML 中使用 #{paramName}
  • 批量操作:利用 foreach 进行批量插入或更新,合理设置事务。
  • XML 存放路径:通常将 Mapper 文件放在 resources/mapper/ 下,与接口类包名对应。
  • 避免 SQL 注入:始终使用 #{} 语法,除非需要动态表名或列名等无法预编译的场景,才谨慎使用 ${}

8. 总结

MyBatis 通过清晰的 SQL 映射和强大的动态 SQL,让你在灵活性和效率之间取得完美平衡。它不隐藏 SQL,反而让你专注于 SQL 优化,同时通过自动化映射极大减少重复劳动。掌握 MyBatis,你将拥有构建高性能数据访问层的有力工具。


开始你的第一个 MyBatis 项目吧,从编写一个干净的 Mapper XML 和动态查询开始!