MyBatis 从入门到精通(第三篇)—— 动态 SQL、关联查询与查询缓存

发布于:2025-09-15 ⋅ 阅读:(17) ⋅ 点赞:(0)

        在前两篇博客中,我们掌握了 MyBatis 的基础搭建、核心架构与 Mapper 代理开发,能应对简单的单表 CRUD 场景。但实际项目中,业务往往更复杂 —— 比如 “多条件动态查询”“员工与部门的关联查询”“高频查询的性能优化” 等。本篇将聚焦 MyBatis 的三大高级特性:动态 SQL(灵活拼接 SQL)关联查询(处理多表关系)查询缓存(提升性能),结合文档中的实战案例,帮你解决复杂业务场景,真正做到 “学以致用”。

目录

一、动态 SQL:MyBatis 的 “灵活拼接神器”

1.1 动态 SQL 的核心价值

1.2 核心动态 SQL 标签实战

(1)标签:基础条件判断

(2)标签:智能处理 AND/OR

(3)--标签:二选一的条件

(4)标签:自定义前缀 / 后缀

(5)标签:动态更新的 “逗号杀手”

(6)标签:处理集合与 IN 条件

(7)片段:SQL 代码复用

(8)标签:跨数据库模糊查询

二、关联查询:处理多表关系(一对一 / 一对多 / 多表)

2.1 关联查询的核心概念

2.2 一对一关联查询:员工→部门

实现步骤:

2.3 一对多关联查询:部门→员工

实现步骤:

2.4 多表关联查询:用户→订单→订单详情→商品

实现步骤:

三、查询缓存:MyBatis 的 “性能优化利器”

3.1 缓存的核心概念

3.2 一级缓存:SqlSession 级别的本地缓存

核心特点:

实战案例:

3.3 二级缓存:Mapper 级别的全局缓存

核心特点:

配置步骤:

实战案例:

关键配置:

3.4 整合第三方缓存:Ehcache(分布式场景)

配置步骤:

四、总结:MyBatis 核心能力回顾与实践建议

实践建议:


一、动态 SQL:MyBatis 的 “灵活拼接神器”

        实际开发中,SQL 语句往往不是固定的 —— 比如 “查询员工” 时,用户可能输入姓名查询,也可能输入部门号查询,还可能两者都输入。如果为每种情况写一个 SQL,会导致代码冗余。MyBatis 的动态 SQL 通过标签判断条件,自动拼接 SQL,彻底解决这一问题。

1.1 动态 SQL 的核心价值

动态 SQL 的本质是 “根据参数是否为空或满足条件,动态生成合法的 SQL 语句”,避免手动拼接 SQL 的痛点:

  • 无需担心 “第一个条件是 AND/OR” 导致的语法错误;
  • 无需处理 “字段末尾多余逗号”(如更新操作中);
  • 支持循环遍历集合(如IN条件),简化批量操作。

MyBatis 提供 8 种常用动态 SQL 标签,我们结合文档中的实战案例,逐一讲解核心用法。

1.2 核心动态 SQL 标签实战

(1)<if>标签:基础条件判断

<if>标签是动态 SQL 的基础,用于 “满足条件则拼接 SQL 片段”,常与test属性(OGNL 表达式)配合使用。
场景:查询员工,姓名不为空则按姓名模糊查询,部门号不为空则按部门号查询。

<select id="selectEmpByCond" parameterType="emp" resultType="emp">
    select * from emp where 1=1
    <!-- 若ename不为空且非空字符串,拼接姓名条件 -->
    <if test="ename != null and ename != ''">
        and ename like concat('%', #{ename}, '%')
    </if>
    <!-- 若deptno不为空,拼接部门号条件 -->
    <if test="deptno != null">
        and deptno = #{deptno}
    </if>
</select>

关键说明

  • test="ename != null and ename != ''":判断参数ename是否有效(非空且非空字符串);
  • concat('%', #{ename}, '%'):MySQL 中拼接模糊查询的%,避免 SQL 注入(文档中强调#{}${}更安全);
  • where 1=1:临时占位,避免 “第一个条件是 AND” 导致的语法错误(后续<where>标签可替代这一写法)。
(2)<where>标签:智能处理 AND/OR

<where>标签是<if>标签的 “好搭档”,能自动处理条件拼接中的语法问题:

  • 若内部有满足条件的<if>,自动添加WHERE关键字;
  • 自动去掉第一个条件前的ANDOR
  • 若内部无满足条件的<if>,不添加WHERE,避免语法错误。

优化上述<if>案例

<select id="selectEmpByCond" parameterType="emp" resultType="emp">
    select * from emp
    <where>
        <if test="ename != null and ename != ''">
            and ename like concat('%', #{ename}, '%') <!-- 无需担心第一个条件是AND -->
        </if>
        <if test="deptno != null">
            and deptno = #{deptno}
        </if>
    </where>
</select>

文档要点<where>标签会智能忽略条件开头的AND/OR,且无需手动写where 1=1,代码更简洁。

(3)<choose>-<when>-<otherwise>标签:二选一的条件

类似 Java 的switch-case-default<choose>标签下的<when>按顺序判断,只执行第一个满足条件的<when>,都不满足则执行<otherwise>
场景:查询员工,优先按薪资查(薪资≤指定值),其次按姓名查,都不满足则查部门号 = 10。

<select id="selectEmpByChoose" parameterType="emp" resultType="emp">
    select * from emp
    <where>
        <choose>
            <when test="sal != null">
                sal &lt;= #{sal} <!-- XML中“<”需转义为“&lt;” -->
            </when>
            <when test="ename != null and ename != ''">
                ename like concat('%', #{ename}, '%')
            </when>
            <otherwise>
                deptno = 10 <!-- 所有条件不满足时执行 -->
            </otherwise>
        </choose>
    </where>
</select>

文档说明<choose>适用于 “多个条件中只选一个” 的场景,避免<if>标签的 “多条件同时生效” 问题。

(4)<trim>标签:自定义前缀 / 后缀

<trim>标签比<where>更灵活,支持自定义 “添加前缀”“添加后缀”“覆盖首尾字符”,核心属性如下:

  • prefix:给内部内容添加前缀(如WHERE);
  • suffix:给内部内容添加后缀(如));
  • prefixOverrides:去掉内部内容开头的指定字符(如AND/OR);
  • suffixOverrides:去掉内部内容末尾的指定字符(如,)。

场景 1:替代<where>标签

<trim prefix="where" prefixOverrides="and|or">
    <if test="ename != null">and ename like '%${ename}%'</if>
    <if test="deptno != null">and deptno = #{deptno}</if>
</trim>

场景 2:动态插入字段(处理末尾逗号)
插入操作中,若部分字段为空,会导致INSERT语句末尾多逗号,<trim>可自动去掉:

<insert id="insertEmp" parameterType="emp">
    insert into emp
    <!-- 动态拼接字段名,去掉末尾逗号 -->
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="ename != null">ename,</if>
        <if test="job != null">job,</if>
        <if test="sal != null">sal,</if>
    </trim>
    values
    <!-- 动态拼接字段值,去掉末尾逗号 -->
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="ename != null">#{ename},</if>
        <if test="job != null">#{job},</if>
        <if test="sal != null">#{sal},</if>
    </trim>
</insert>

文档要点<trim>标签是动态 SQL 中最灵活的标签,可应对<where><set>无法覆盖的场景。

(5)<set>标签:动态更新的 “逗号杀手”

更新操作中,若用<if>标签,可能出现 “字段末尾多逗号”(如update emp set ename=?,),<set>标签可自动去掉末尾逗号,并添加SET关键字。

场景:动态更新员工信息,字段不为空则更新该字段。

<update id="updateEmp" parameterType="emp">
    update emp
    <set>
        <if test="ename != null">ename = #{ename},</if>
        <if test="job != null">job = #{job},</if>
        <if test="sal != null">sal = #{sal},</if>
    </set>
    where empno = #{empno}
</update>

文档说明<set>标签会自动添加SET关键字,并去掉内部内容末尾的逗号,避免update语句语法错误。

(6)<foreach>标签:处理集合与 IN 条件

        当 SQL 需要IN条件(如where deptno in (10,20,30))或批量操作时,<foreach>标签可遍历集合生成对应 SQL 片段,核心属性如下:

  • collection:集合类型(list=List,array= 数组,map的key=Map 中的集合);
  • open:遍历开始符号(如();
  • close:遍历结束符号(如));
  • item:集合元素的别名(如deptno);
  • separator:元素之间的分隔符(如,)。

场景 1:遍历 List 集合,查询部门号在列表中的员工

<select id="selectEmpByDeptnos" parameterType="java.util.List" resultType="emp">
    select * from emp
    <where>
        deptno in
        <foreach collection="list" open="(" close=")" item="deptno" separator=",">
            #{deptno}
        </foreach>
    </where>
</select>

场景 2:遍历数组,查询员工编号在数组中的员工

<select id="selectEmpByEmpnosArr" parameterType="int[]" resultType="emp">
    select * from emp
    <where>
        empno in
        <foreach collection="array" open="(" close=")" item="empno" separator=",">
            #{empno}
        </foreach>
    </where>
</select>

文档要点collection属性需根据参数类型选择(List 用list,数组用array),若参数是 Map,需写 Map 中集合的key

(7)<sql>片段:SQL 代码复用

        若多个 SQL 有重复片段(如select empno, ename, job from emp),可提取为<sql>片段,避免重复书写,提升维护性。

<!-- 定义SQL片段:id为片段唯一标识 -->
<sql id="empColumns">
    empno, ename, job, sal, deptno
</sql>

<!-- 引用SQL片段:用<include refid="片段id"> -->
<select id="selectEmp" resultType="emp">
    select <include refid="empColumns"/> from emp
</select>

<select id="selectEmpByDeptno" parameterType="int" resultType="emp">
    select <include refid="empColumns"/> from emp where deptno = #{deptno}
</select>

文档说明<sql>片段适用于重复的字段列表、查询条件等,减少代码冗余。

(8)<bind>标签:跨数据库模糊查询

        不同数据库的模糊查询语法不同(MySQL 用concat,Oracle 用||),<bind>标签可定义变量统一语法,提升代码可移植性。

<select id="selectEmpByEname" parameterType="emp" resultType="emp">
    <!-- 定义变量name:值为“%+ename+%” -->
    <bind name="name" value="'%' + ename + '%'"/>
    select * from emp where ename like #{name}
</select>

文档要点<bind>标签无需关心数据库类型,统一用#{name}引用变量,避免因数据库切换修改 SQL。

二、关联查询:处理多表关系(一对一 / 一对多 / 多表)

        实际业务中,单表查询很少见,更多是 “员工 - 部门”“部门 - 员工”“用户 - 订单 - 商品” 等多表关联场景。MyBatis 通过<resultMap>标签的<association>(一对一)和<collection>(一对多)子标签,实现复杂关联映射。

2.1 关联查询的核心概念

在讲解案例前,需明确两种常见关联关系:

  • 一对一:一个对象对应一个对象(如一个员工对应一个部门);
  • 一对多:一个对象对应多个对象(如一个部门对应多个员工);
  • 多对多:需通过中间表转换为 “一对多 + 多对一”(如用户 - 订单 - 商品,用户与商品是多对多,通过订单表关联)。

MyBatis 通过<resultMap>定义关联规则,无需手动遍历多表结果集,自动映射为实体类对象。

2.2 一对一关联查询:员工→部门

场景:查询员工信息时,同时查询员工所属的部门信息(一个员工只属于一个部门)。

实现步骤:

定义实体类:在Emp类中添加Dept属性,存储关联的部门信息。

public class Emp {
    private int empno;
    private String ename;
    private int deptno;
    private Dept dept; // 一对一关联:员工所属部门

    // getter/setter、toString()
}

public class Dept {
    private int deptno;
    private String dname;
    private String loc;

    // getter/setter、toString()
}

编写 Mapper 接口:定义查询方法。

public interface EmpMapper {
    List<Emp> selectEmpWithDept(); // 查询员工及所属部门
}

配置 Mapper.xml:用<resultMap>+<association>定义关联映射。

<mapper namespace="com.jr.mapper.EmpMapper">
    <!-- 定义resultMap:映射Emp与Dept的一对一关联 -->
    <resultMap id="empWithDeptMap" type="emp">
        <!-- 映射Emp的基本字段:id标签对应主键 -->
        <id column="empno" property="empno"/>
        <result column="ename" property="ename"/>
        <result column="deptno" property="deptno"/>

        <!-- 一对一关联Dept:用<association> -->
        <association property="dept" javaType="dept"> 
            <!-- javaType:关联实体类的类型(Dept) -->
            <id column="d_deptno" property="deptno"/> <!-- 用别名避免字段名冲突 -->
            <result column="dname" property="dname"/>
            <result column="loc" property="loc"/>
        </association>
    </resultMap>

    <!-- 关联查询SQL:多表连接,用别名区分字段 -->
    <select id="selectEmpWithDept" resultMap="empWithDeptMap">
        select e.empno, e.ename, e.deptno, 
               d.deptno as d_deptno, d.dname, d.loc
        from emp e
        inner join dept d on e.deptno = d.deptno
    </select>
</mapper>
  1. 测试代码
@Test
public void testSelectEmpWithDept() {
    SqlSession session = factory.openSession();
    EmpMapper empMapper = session.getMapper(EmpMapper.class);
    List<Emp> emps = empMapper.selectEmpWithDept();
    
    for (Emp emp : emps) {
        System.out.println("员工:" + emp.getEname() + ",部门:" + emp.getDept().getDname());
    }
    session.close();
}

文档要点<association>标签用于一对一关联,javaType属性指定关联实体类的类型,需用别名避免多表字段名冲突(如d.deptno as d_deptno)。

2.3 一对多关联查询:部门→员工

场景:查询部门信息时,同时查询部门下的所有员工(一个部门有多个员工)。

实现步骤:

定义实体类:在Dept类中添加List<Emp>属性,存储关联的员工列表。

public class Dept {
    private int deptno;
    private String dname;
    private String loc;
    private List<Emp> emps; // 一对多关联:部门下的员工列表

    // getter/setter、toString()
}

编写 Mapper 接口

public interface DeptMapper {
    Dept selectDeptWithEmp(int deptno); // 查询部门及下属员工
}

配置 Mapper.xml:用<resultMap>+<collection>定义一对多关联。

<mapper namespace="com.jr.mapper.DeptMapper">
    <!-- 定义resultMap:映射Dept与Emp的一对多关联 -->
    <resultMap id="deptWithEmpMap" type="dept">
        <!-- 映射Dept的基本字段 -->
        <id column="deptno" property="deptno"/>
        <result column="dname" property="dname"/>
        <result column="loc" property="loc"/>

        <!-- 一对多关联Emp列表:用<collection> -->
        <collection property="emps" ofType="emp"> 
            <!-- ofType:集合中元素的类型(Emp) -->
            <id column="e_empno" property="empno"/> <!-- 别名避免冲突 -->
            <result column="e_ename" property="ename"/>
            <result column="e_sal" property="sal"/>
        </collection>
    </resultMap>

    <!-- 关联查询SQL:左连接查询部门与员工 -->
    <select id="selectDeptWithEmp" parameterType="int" resultMap="deptWithEmpMap">
        select d.deptno, d.dname, d.loc,
               e.empno as e_empno, e.ename as e_ename, e.sal as e_sal
        from dept d
        left join emp e on d.deptno = e.deptno
        where d.deptno = #{deptno}
    </select>
</mapper>
  1. 测试代码
@Test
public void testSelectDeptWithEmp() {
    SqlSession session = factory.openSession();
    DeptMapper deptMapper = session.getMapper(DeptMapper.class);
    Dept dept = deptMapper.selectDeptWithEmp(10);
    
    System.out.println("部门:" + dept.getDname());
    for (Emp emp : dept.getEmps()) {
        System.out.println("  员工:" + emp.getEname() + ",薪资:" + emp.getSal());
    }
    session.close();
}

文档要点<collection>标签用于一对多关联,ofType属性指定集合元素的类型(区别于javaTypejavaType用于指定属性类型,如List)。

2.4 多表关联查询:用户→订单→订单详情→商品

场景:查询用户信息时,同时查询用户的所有订单、每个订单的详情、每个详情对应的商品(多表关联:用户 1:N 订单 1:N 订单详情 1:1 商品)。

实现步骤:

定义实体类:逐层关联(Users→Orders→OrderDetail→Items)。

// 用户类:1个用户对应多个订单
public class Users {
    private int uid;
    private String uname;
    private List<Orders> orders; // 一对多关联订单
    // getter/setter
}

// 订单类:1个订单对应多个详情
public class Orders {
    private int oid;
    private String orderid;
    private List<OrderDetail> orderdetails; // 一对多关联详情
    // getter/setter
}

// 订单详情类:1个详情对应1个商品
public class OrderDetail {
    private int odid;
    private int itemsnum;
    private Items item; // 一对一关联商品
    // getter/setter
}

// 商品类
public class Items {
    private int iid;
    private String name;
    private double price;
    // getter/setter
}

配置 Mapper.xml:嵌套<collection><association>实现多表映射。

<mapper namespace="com.jr.mapper.UserMapper">
    <!-- 多表关联resultMap:用户→订单→详情→商品 -->
    <resultMap id="userOrderDetailItemMap" type="users">
        <id column="uid" property="uid"/>
        <result column="uname" property="uname"/>

        <!-- 1:N:用户→订单 -->
        <collection property="orders" ofType="orders">
            <id column="oid" property="oid"/>
            <result column="orderid" property="orderid"/>

            <!-- 1:N:订单→订单详情 -->
            <collection property="orderdetails" ofType="orderdetail">
                <id column="odid" property="odid"/>
                <result column="itemsnum" property="itemsnum"/>

                <!-- 1:1:订单详情→商品 -->
                <association property="item" javaType="items">
                    <id column="iid" property="iid"/>
                    <result column="name" property="name"/>
                    <result column="price" property="price"/>
                </association>
            </collection>
        </collection>
    </resultMap>

    <!-- 多表关联SQL:四表连接 -->
    <select id="selectUserWithAll" resultMap="userOrderDetailItemMap">
        select u.uid, u.uname,
               o.oid, o.orderid,
               od.odid, od.itemsnum,
               i.iid, i.name, i.price
        from users u
        inner join orders o on u.uid = o.userid
        inner join orderdetail od on o.orderid = od.orderid
        inner join items i on od.itemid = i.iid
    </select>
</mapper>

文档要点:多表关联需嵌套使用<collection>(一对多)和<association>(一对一),确保每层映射的columnproperty对应。

三、查询缓存:MyBatis 的 “性能优化利器”

缓存是 “以空间换时间” 的优化手段,MyBatis 提供两级缓存,减少数据库访问次数,提升高频查询的性能。

3.1 缓存的核心概念

MyBatis 的缓存分为两级,作用域和生命周期不同:

  • 一级缓存SqlSession级别(本地缓存),默认开启,无需配置;
  • 二级缓存Mapper(namespace)级别(全局缓存),默认关闭,需手动配置;
  • 第三方缓存:如 Ehcache、Redis,用于分布式场景(多服务共享缓存)。

3.2 一级缓存:SqlSession 级别的本地缓存

核心特点:
  • 作用域:同一个SqlSession(从openSession()close());
  • 实现:基于PerpetualCache(HashMap)存储;
  • 失效场景
    1. 调用SqlSession.close()
    2. 调用SqlSession.commit()/rollback()(事务提交 / 回滚会清空缓存);
    3. 执行相同 ID 的insert/update/delete(修改数据会清空缓存,避免脏读);
    4. 调用SqlSession.clearCache()(手动清空缓存)。
实战案例:
@Test
public void testFirstLevelCache() {
    SqlSession session = factory.openSession();
    EmpMapper empMapper = session.getMapper(EmpMapper.class);

    // 第一次查询:执行SQL,结果存入一级缓存
    Emp emp1 = empMapper.selectEmpByNo(7369);
    System.out.println(emp1);

    // 第二次查询:同一SqlSession,相同SQL,从缓存获取(不执行SQL)
    Emp emp2 = empMapper.selectEmpByNo(7369);
    System.out.println(emp2);

    session.close();
}

日志输出:仅第一次查询执行 SQL,第二次从缓存获取。

3.3 二级缓存:Mapper 级别的全局缓存

核心特点:
  • 作用域:同一个Mapper(namespace),跨SqlSession共享;
  • 存储:默认存储序列化后的 Java 对象(需实体类实现Serializable接口);
  • 配置步骤:需开启全局开关 + Mapper 单独配置。
配置步骤:

开启全局二级缓存SqlMapConfig.xml):

<settings>
    <setting name="cacheEnabled" value="true"/> <!-- 全局开关,默认true可省略 -->
</settings>

在 Mapper.xml 中开启二级缓存

<mapper namespace="com.jr.mapper.EmpMapper">
    <!-- 开启二级缓存:默认使用PerpetualCache -->
    <cache/>
    
    <!-- 或配置缓存参数(如过期时间、最大容量) -->
    <!--
    <cache
        eviction="LRU"        // 淘汰策略(LRU:最近最少使用)
        flushInterval="60000" // 60秒刷新一次缓存
        size="1024"           // 最多缓存1024个对象
        readOnly="true"/>     // 只读模式(返回对象引用,性能高)
    -->

    <select id="selectEmpByNo" parameterType="int" resultType="emp">
        select * from emp where empno = #{empno}
    </select>
</mapper>

实体类实现 Serializable 接口

public class Emp implements Serializable { // 二级缓存需序列化
    private static final long serialVersionUID = 1L; // 序列化ID
    // 字段、getter/setter
}
实战案例:
@Test
public void testSecondLevelCache() {
    // 第一个SqlSession:查询后关闭,将数据刷入二级缓存
    SqlSession session1 = factory.openSession();
    EmpMapper empMapper1 = session1.getMapper(EmpMapper.class);
    Emp emp1 = empMapper1.selectEmpByNo(7369);
    System.out.println(emp1);
    session1.close(); // 关闭SqlSession,一级缓存数据刷入二级缓存

    // 第二个SqlSession:从二级缓存获取数据(不执行SQL)
    SqlSession session2 = factory.openSession();
    EmpMapper empMapper2 = session2.getMapper(EmpMapper.class);
    Emp emp2 = empMapper2.selectEmpByNo(7369);
    System.out.println(emp2);
    session2.close();
}

文档要点:二级缓存需通过session.close()session.commit()将一级缓存数据刷入,否则无法共享。

关键配置:
  • 禁用二级缓存:对实时性要求高的查询(如秒杀商品库存),添加useCache="false"
<select id="selectEmpByNo" parameterType="int" resultType="emp" useCache="false">
    select * from emp where empno = #{empno}
</select>
  • 刷新二级缓存:执行insert/update/delete后,默认清空二级缓存(避免脏读),可通过flushCache="false"关闭(不推荐):
<update id="updateEmp" parameterType="emp" flushCache="true"> <!-- 默认true,可省略 -->
    update emp set ename = #{ename} where empno = #{empno}
</update>

3.4 整合第三方缓存:Ehcache(分布式场景)

默认二级缓存是 “本地缓存”,分布式部署时(多台服务器)缓存不共享,需整合分布式缓存框架(如 Ehcache)。

配置步骤:

添加 Maven 依赖

<!-- MyBatis-Ehcache整合包 -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.0.2</version>
</dependency>

<!-- Ehcache核心包 -->
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.1</version>
</dependency>

添加 Ehcache 配置文件(ehcache.xml)

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
    <!-- 缓存数据存储路径(磁盘) -->
    <diskStore path="D:/mybatis-ehcache"/>

    <!-- 默认缓存配置 -->
    <defaultCache
            maxElementsInMemory="1000"  <!-- 内存最大缓存对象数 -->
            eternal="false"            <!-- 不永久缓存 -->
            timeToIdleSeconds="120"     <!-- 120秒未访问则过期 -->
            timeToLiveSeconds="120"     <!-- 120秒后过期 -->
            overflowToDisk="true"/>     <!-- 内存满时写入磁盘 -->
</ehcache>

在 Mapper.xml 中指定 Ehcache 缓存

<mapper namespace="com.jr.mapper.EmpMapper">
    <!-- 启用Ehcache缓存 -->
    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
    <!-- SQL语句... -->
</mapper>

文档要点:Ehcache 支持内存 + 磁盘存储,分布式部署时可配置集群,实现缓存共享。

四、总结:MyBatis 核心能力回顾与实践建议

至此,MyBatis 从入门到精通系列三篇博客已全部完成,我们系统覆盖了 MyBatis 的核心能力:

  1. 基础层:框架概念、环境搭建(普通项目 + Maven);
  2. 核心层:三层架构、全局配置、Mapper 代理开发;
  3. 高级层:动态 SQL、关联查询、查询缓存。
实践建议:
  1. 动态 SQL:优先用<where>``<set>标签简化条件拼接,复杂场景用<trim>,避免手动写where 1=1
  2. 关联查询:一对一用<association>,一对多用<collection>,多表关联需注意字段别名冲突;
  3. 缓存优化:一级缓存默认开启,二级缓存按需开启(适合查询多、修改少的场景),分布式项目整合 Ehcache/Redis;
  4. 开发规范:坚持 “Mapper 代理开发”,SQL 集中在 XML 中,通过<sql>片段复用代码,提升维护性。

        MyBatis 的核心优势在于 “轻量、灵活、解耦”—— 既保留了 SQL 的灵活性,又简化了数据映射与连接管理。掌握这些核心能力后,你不仅能应对企业级项目的持久层开发,更能在面试中从容应对 MyBatis 的高频考点(如动态 SQL、缓存机制、关联查询)。后续可进一步学习 MyBatis-Plus(MyBatis 的增强工具,简化 CRUD),但建议先夯实 MyBatis 基础,再逐步拓展。