牧瀬クリスだ头像
关注
MyBatis进阶:动态SQL与高级映射全解析封面图

MyBatis进阶:动态SQL与高级映射全解析

目录

 

一、引言

二、动态 SQL

2.1 if 标签:条件拼接

2.2 where 标签:自动处理 AND/OR

2.3 choose/when/otherwise:多选一

2.4 set 标签:动态更新

2.5 foreach 标签:批量操作

2.6 SQL 片段:抽取复用

三、resultMap 高级映射

3.1 列名与属性名不一致

3.2 一对一关联(association)

3.3 一对多关联(collection)

四、注解开发

五、Mybatis 缓存机制

5.1 一级缓存(默认开启,SqlSession 级别)

5.2 二级缓存(SqlSessionFactory 级别,需手动开启)

六、总结


 

一、引言

在上一篇文章《Mybatis 入门》里,我们掌握了 Mybatis 最基础的用法:通过 SqlSessionFactory 加载配置、用 Mapper 接口和 XML 映射文件完成对数据库的增删改查。但是,实际业务中我们很快会碰到三个麻烦:

  1. 查询条件不固定——有时按姓名查,有时按年龄查,有时两者都要,总不能为每种组合都写一条 SQL;
  2. 表与表之间存在关联关系——用户和订单一对多、订单和商品多对多,简单的 resultType 已经不够用了;
  3. 每次查询都去数据库,性能上能不能优化?

这篇文章就围绕这几个痛点,把 Mybatis 的进阶能力讲透:动态 SQL、resultMap 高级映射、注解开发、以及一级/二级缓存。

建议先看完前面的《Mybatis 入门》,本文示例都基于同一套 t_user、t_order 表结构。


二、动态 SQL

动态 SQL 是 Mybatis 最强大的特性之一,它让我们可以根据传入参数的条件,动态拼接 SQL 语句,避免大量冗余的硬编码。

2.1 if 标签:条件拼接

最常见的场景:按条件查询用户,哪个字段有值就用哪个字段。

xml

<select id="findUserByCondition" parameterType="map" resultType="User">
    SELECT id, name, age, email
    FROM t_user
    WHERE 1 = 1
    <if test="name != null and name != ''">
        AND name = #{name}
    </if>
    <if test="age != null">
        AND age = #{age}
    </if>
    <if test="email != null and email != ''">
        AND email = #{email}
    </if>
</select>

注意:WHERE 1 = 1 是为了方便拼 AND,但这样写不够优雅,所以有了下面的 where 标签。

2.2 where 标签:自动处理 AND/OR

<where> 标签会自动去掉开头的 AND 或 OR,并且当内部条件都不成立时,不会生成 WHERE 关键字。

xml

<select id="findUserByCondition" parameterType="map" resultType="User">
    SELECT id, name, age, email
    FROM t_user
    <where>
        <if test="name != null and name != ''">
            AND name = #{name}
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
    </where>
</select>

2.3 choose/when/otherwise:多选一

相当于 Java 里的 switch-case,只取第一个满足的条件。

xml

<select id="findUser" parameterType="map" resultType="User">
    SELECT id, name, age, email FROM t_user
    <where>
        <choose>
            <when test="name != null and name != ''">
                AND name = #{name}
            </when>
            <when test="age != null">
                AND age = #{age}
            </when>
            <otherwise>
                AND age &gt; 18
            </otherwise>
        </choose>
    </where>
</select>

2.4 set 标签:动态更新

<set> 用于 UPDATE 语句,会自动去掉末尾多余的逗号。

xml

<update id="updateUser" parameterType="User">
    UPDATE t_user
    <set>
        <if test="name != null">name = #{name},</if>
        <if test="age != null">age = #{age},</if>
        <if test="email != null">email = #{email},</if>
    </set>
    WHERE id = #{id}
</update>

2.5 foreach 标签:批量操作

这是最常用的,比如批量删除、批量插入。

xml

<!-- 批量删除:WHERE id IN (1,2,3) -->
<delete id="deleteByIds" parameterType="list">
    DELETE FROM t_user WHERE id IN
    <foreach collection="list" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</delete>

<!-- 批量插入 -->
<insert id="batchInsert" parameterType="list">
    INSERT INTO t_user (name, age, email) VALUES
    <foreach collection="list" item="user" separator=",">
        (#{user.name}, #{user.age}, #{user.email})
    </foreach>
</insert>

2.6 SQL 片段:抽取复用

把重复的 SQL 抽成 <sql> 片段,用 <include> 引入。

xml

<sql id="userColumns">
    id, name, age, email
</sql>

<select id="findAll" resultType="User">
    SELECT <include refid="userColumns"/> FROM t_user
</select>

三、resultMap 高级映射

当查询结果列名和实体属性名不一致,或者涉及关联查询(一对一、一对多)时,就需要用 resultMap 手动指定映射关系。

3.1 列名与属性名不一致

数据库里是 user_name,实体里是 userName,直接 resultType 会映射不上(值为 null)。用 resultMap 解决:

xml

<resultMap id="userMap" type="com.example.entity.User">
    <id property="id" column="id"/>
    <result property="userName" column="user_name"/>
    <result property="age" column="age"/>
    <result property="email" column="email"/>
</resultMap>

<select id="findAll" resultMap="userMap">
    SELECT id, user_name, age, email FROM t_user
</select>

小技巧:如果只是命名风格差异(下划线转驼峰),也可以在全局配置里开启 mapUnderscoreToCamelCase,就不用每条都写 resultMap 了。

3.2 一对一关联(association)

场景:订单属于某个用户。查询订单时,把对应的用户对象也一起查出来。

xml

<resultMap id="orderMap" type="com.example.entity.Order">
    <id property="id" column="id"/>
    <result property="orderNo" column="order_no"/>
    <result property="amount" column="amount"/>
    <!-- 一对一:一个订单对应一个用户 -->
    <association property="user" javaType="com.example.entity.User">
        <id property="id" column="user_id"/>
        <result property="name" column="user_name"/>
        <result property="email" column="email"/>
    </association>
</resultMap>

<select id="findOrderWithUser" resultMap="orderMap">
    SELECT o.id, o.order_no, o.amount, u.id AS user_id, u.name AS user_name, u.email
    FROM t_order o
    LEFT JOIN t_user u ON o.user_id = u.id
    WHERE o.id = #{id}
</select>

3.3 一对多关联(collection)

场景:一个用户有多个订单。查询用户时,把该用户的所有订单查出来。

xml

<resultMap id="userWithOrdersMap" type="com.example.entity.User">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <result property="email" column="email"/>
    <!-- 一对多:一个用户对应多个订单 -->
    <collection property="orders" ofType="com.example.entity.Order">
        <id property="id" column="order_id"/>
        <result property="orderNo" column="order_no"/>
        <result property="amount" column="amount"/>
    </collection>
</resultMap>

<select id="findUserWithOrders" resultMap="userWithOrdersMap">
    SELECT u.id, u.name, u.email, o.id AS order_id, o.order_no, o.amount
    FROM t_user u
    LEFT JOIN t_order o ON u.id = o.user_id
    WHERE u.id = #{id}
</select>

重点区分:一对一用 association,一对多用 collection。


四、注解开发

对于简单的 SQL,写 XML 映射文件略显繁琐,Mybatis 提供了注解方式,直接在 Mapper 接口上写 SQL。

java

public interface UserMapper {

    @Select("SELECT id, name, age, email FROM t_user WHERE id = #{id}")
    User findById(Integer id);

    @Insert("INSERT INTO t_user(name, age, email) VALUES(#{name}, #{age}, #{email})")
    @Options(useGeneratedKeys = true, keyProperty = "id")  // 回填自增主键
    int insert(User user);

    @Update("UPDATE t_user SET name = #{name}, age = #{age} WHERE id = #{id}")
    int update(User user);

    @Delete("DELETE FROM t_user WHERE id = #{id}")
    int delete(Integer id);

    // 多参数必须用 @Param 指定名字
    @Select("SELECT id, name FROM t_user WHERE name = #{name} AND age = #{age}")
    List<User> findByNameAndAge(@Param("name") String name, @Param("age") Integer age);
}

注解开发还支持动态 SQL,用 <script> 包裹即可,但复杂场景仍然建议用 XML 更清晰:

java

@Select("<script>" +
        "SELECT * FROM t_user" +
        "<where>" +
        "<if test='name != null'>AND name = #{name}</if>" +
        "</where>" +
        "</script>")
List<User> findByCondition(User user);

一句话原则:简单 SQL 用注解,复杂/动态 SQL 用 XML,两者可以混用。


五、Mybatis 缓存机制

Mybatis 内置了两级缓存,理解它们对性能调优很有帮助。

5.1 一级缓存(默认开启,SqlSession 级别)

一级缓存作用于同一个 SqlSession 内。同一个会话中执行相同的查询,第二次会直接返回缓存,不再查库。

java

SqlSession session = factory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);

User u1 = mapper.findById(1);  // 第一次:查数据库
User u2 = mapper.findById(1);  // 第二次:命中一级缓存,不查库
System.out.println(u1 == u2);  // true,同一个对象

一级缓存会在 SqlSession 执行 增删改 操作、或调用 commit()、close() 时被清空。

5.2 二级缓存(SqlSessionFactory 级别,需手动开启)

二级缓存是跨 SqlSession 共享的。开启步骤:

第一步:全局配置开启二级缓存

xml

<!-- mybatis-config.xml -->
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

第二步:在 Mapper 映射文件里加 <cache/> 标签

xml

<mapper namespace="com.example.mapper.UserMapper">
    <!-- 开启二级缓存 -->
    <cache/>
    ...
</mapper>

第三步:实体类实现 Serializable 接口

java

public class User implements Serializable {
    private Integer id;
    private String name;
    // getter/setter 省略
}

注意:二级缓存默认对所有查询都生效,但多表关联、数据频繁变动的场景不建议用,容易造成脏数据。需要精确控制时可以用 <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/> 配置淘汰策略。


六、总结

这篇文章我们把 Mybatis 从「会用」提升到了「用好」的层次,核心内容可以浓缩成四句话:

  1. 动态 SQL 解决「条件不固定」的问题:if、where、set、choose、foreach、sql 片段各司其职;
  2. resultMap 解决「列名不一致」和「关联查询」的问题:一对一用 association,一对多用 collection;
  3. 注解开发 让简单 CRUD 更简洁,复杂动态 SQL 仍建议回归 XML;
  4. 一级缓存默认开启、作用在单个 SqlSession;二级缓存需手动开启、跨会话共享,但要警惕脏数据。

掌握了这些,应对绝大多数企业级项目的持久层开发已经绰绰有余。下一篇(也是本系列的最后一篇),我会带大家从零搭建一个基于 SpringBoot 的图书管理系统,把前面六篇文章的知识——SpringBoot 自动装配、SpringMVC 的 Controller、IoC/DI、yml 配置、Mybatis 的入门与进阶——全部串联起来,敬请期待。

 

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2501_93705143/article/details/166601573

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--