SSM从入门到实战:2.5 SQL映射文件与动态SQL

👋 大家好,我是 阿问学长!专注于分享优质开源项目解析、毕业设计项目指导支持、幼小初高教辅资料推荐等,欢迎关注交流!🚀

12-SQL映射文件与动态SQL

📖 本文概述

本文是SSM框架系列MyBatis进阶篇的第二篇,将深入探讨SQL映射文件的编写技巧和动态SQL的使用方法。通过丰富的示例和最佳实践,帮助读者掌握MyBatis中最核心的功能之一。

🎯 学习目标

  • 深入理解SQL映射文件的结构和元素
  • 掌握结果映射(ResultMap)的高级用法
  • 学会编写各种类型的动态SQL
  • 了解SQL片段的复用技巧
  • 掌握复杂查询和关联映射的实现

1. SQL映射文件概述

1.1 映射文件的基本结构

SQL映射文件是MyBatis的核心,它定义了SQL语句和Java对象之间的映射关系。

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 id="BaseResultMap" type="User">
        <!-- 主键映射 -->
        <id column="id" property="id" jdbcType="BIGINT"/>
        <!-- 普通字段映射 -->
        <result column="username" property="username" jdbcType="VARCHAR"/>
        <result column="email" property="email" jdbcType="VARCHAR"/>
    </resultMap>
    
    <!-- SQL片段 -->
    <sql id="Base_Column_List">
        id, username, email, password, age, create_time, update_time
    </sql>
    
    <!-- 查询语句 -->
    <select id="findById" parameterType="long" resultMap="BaseResultMap">
        SELECT <include refid="Base_Column_List"/>
        FROM users
        WHERE id = #{id}
    </select>
    
    <!-- 插入语句 -->
    <insert id="insert" parameterType="User">
        INSERT INTO users (username, email, password, age)
        VALUES (#{username}, #{email}, #{password}, #{age})
    </insert>
    
    <!-- 更新语句 -->
    <update id="update" parameterType="User">
        UPDATE users
        SET username = #{username}, email = #{email}
        WHERE id = #{id}
    </update>
    
    <!-- 删除语句 -->
    <delete id="deleteById" parameterType="long">
        DELETE FROM users WHERE id = #{id}
    </delete>
    
</mapper>

1.2 映射文件的主要元素

  • mapper:根元素,包含namespace属性
  • resultMap:结果映射,定义如何将查询结果映射到Java对象
  • sql:可重用的SQL片段
  • select:查询语句
  • insert:插入语句
  • update:更新语句
  • delete:删除语句

2. 结果映射(ResultMap)详解

2.1 基础结果映射

xml 复制代码
<!-- 基础结果映射 -->
<resultMap id="UserResultMap" type="com.example.entity.User">
    <!-- id元素:主键字段映射 -->
    <id column="user_id" property="id" jdbcType="BIGINT"/>
    
    <!-- result元素:普通字段映射 -->
    <result column="user_name" property="username" jdbcType="VARCHAR"/>
    <result column="user_email" property="email" jdbcType="VARCHAR"/>
    <result column="user_age" property="age" jdbcType="INTEGER"/>
    <result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
    <result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
</resultMap>

2.2 复杂类型映射

xml 复制代码
<!-- 包含复杂类型的结果映射 -->
<resultMap id="UserWithProfileResultMap" type="User">
    <id column="id" property="id"/>
    <result column="username" property="username"/>
    <result column="email" property="email"/>
    
    <!-- association:一对一关联映射 -->
    <association property="profile" javaType="UserProfile">
        <id column="profile_id" property="id"/>
        <result column="real_name" property="realName"/>
        <result column="phone" property="phone"/>
        <result column="address" property="address"/>
    </association>
    
    <!-- collection:一对多关联映射 -->
    <collection property="roles" ofType="Role">
        <id column="role_id" property="id"/>
        <result column="role_name" property="roleName"/>
        <result column="role_description" property="description"/>
    </collection>
</resultMap>

2.3 继承结果映射

xml 复制代码
<!-- 基础结果映射 -->
<resultMap id="BaseUserResultMap" type="User">
    <id column="id" property="id"/>
    <result column="username" property="username"/>
    <result column="email" property="email"/>
    <result column="create_time" property="createTime"/>
</resultMap>

<!-- 继承基础映射并扩展 -->
<resultMap id="ExtendedUserResultMap" type="User" extends="BaseUserResultMap">
    <result column="last_login_time" property="lastLoginTime"/>
    <result column="login_count" property="loginCount"/>
    
    <!-- 添加关联映射 -->
    <association property="profile" javaType="UserProfile">
        <id column="profile_id" property="id"/>
        <result column="real_name" property="realName"/>
    </association>
</resultMap>

2.4 自动映射配置

xml 复制代码
<!-- 开启自动映射 -->
<resultMap id="AutoMappingResultMap" type="User" autoMapping="true">
    <!-- 只需要配置特殊的映射关系 -->
    <id column="user_id" property="id"/>
    <!-- 其他字段会自动映射(如果字段名和属性名匹配) -->
</resultMap>

3. 动态SQL详解

3.1 if标签

if标签是最常用的动态SQL标签,用于条件判断。

xml 复制代码
<!-- 基础if用法 -->
<select id="findUsersByCondition" parameterType="User" resultMap="BaseResultMap">
    SELECT * FROM users
    WHERE 1=1
    <if test="username != null and username != ''">
        AND username = #{username}
    </if>
    <if test="email != null and email != ''">
        AND email = #{email}
    </if>
    <if test="age != null">
        AND age = #{age}
    </if>
    <if test="createTime != null">
        AND create_time >= #{createTime}
    </if>
</select>

<!-- 复杂条件判断 -->
<select id="findUsersWithComplexCondition" parameterType="map" resultMap="BaseResultMap">
    SELECT * FROM users
    WHERE 1=1
    <if test="keyword != null and keyword != ''">
        AND (username LIKE CONCAT('%', #{keyword}, '%') 
             OR email LIKE CONCAT('%', #{keyword}, '%'))
    </if>
    <if test="ageRange != null">
        <if test="ageRange.min != null">
            AND age >= #{ageRange.min}
        </if>
        <if test="ageRange.max != null">
            AND age <= #{ageRange.max}
        </if>
    </if>
    <if test="roles != null and roles.size() > 0">
        AND id IN (
            SELECT user_id FROM user_roles 
            WHERE role_id IN
            <foreach collection="roles" item="roleId" open="(" separator="," close=")">
                #{roleId}
            </foreach>
        )
    </if>
</select>

3.2 where标签

where标签可以智能地处理WHERE子句,自动去除多余的AND或OR。

xml 复制代码
<select id="findUsersByConditionWithWhere" parameterType="User" resultMap="BaseResultMap">
    SELECT * FROM users
    <where>
        <if test="username != null and username != ''">
            AND username = #{username}
        </if>
        <if test="email != null and email != ''">
            AND email = #{email}
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
    </where>
    ORDER BY create_time DESC
</select>

3.3 set标签

set标签用于动态更新语句,自动处理逗号分隔符。

xml 复制代码
<update id="updateUserSelective" parameterType="User">
    UPDATE users
    <set>
        <if test="username != null and username != ''">
            username = #{username},
        </if>
        <if test="email != null and email != ''">
            email = #{email},
        </if>
        <if test="password != null and password != ''">
            password = #{password},
        </if>
        <if test="age != null">
            age = #{age},
        </if>
        <if test="status != null">
            status = #{status},
        </if>
        update_time = CURRENT_TIMESTAMP
    </set>
    WHERE id = #{id}
</update>

3.4 choose、when、otherwise标签

类似于Java中的switch语句,用于多分支条件判断。

xml 复制代码
<select id="findUsersByType" parameterType="map" resultMap="BaseResultMap">
    SELECT * FROM users
    WHERE 1=1
    <choose>
        <when test="type == 'active'">
            AND status = 'ACTIVE' AND last_login_time > DATE_SUB(NOW(), INTERVAL 30 DAY)
        </when>
        <when test="type == 'inactive'">
            AND status = 'ACTIVE' AND last_login_time <= DATE_SUB(NOW(), INTERVAL 30 DAY)
        </when>
        <when test="type == 'disabled'">
            AND status = 'DISABLED'
        </when>
        <otherwise>
            AND status = 'ACTIVE'
        </otherwise>
    </choose>
    <if test="orderBy != null">
        <choose>
            <when test="orderBy == 'username'">
                ORDER BY username
            </when>
            <when test="orderBy == 'createTime'">
                ORDER BY create_time DESC
            </when>
            <when test="orderBy == 'lastLogin'">
                ORDER BY last_login_time DESC
            </when>
            <otherwise>
                ORDER BY id
            </otherwise>
        </choose>
    </if>
</select>

3.5 foreach标签

foreach标签用于遍历集合,常用于IN查询和批量操作。

xml 复制代码
<!-- IN查询 -->
<select id="findUsersByIds" parameterType="list" resultMap="BaseResultMap">
    SELECT * FROM users
    WHERE id IN
    <foreach collection="list" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

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

<!-- 批量更新(MySQL特有语法) -->
<update id="batchUpdateUsers" parameterType="list">
    <foreach collection="list" item="user" separator=";">
        UPDATE users
        SET username = #{user.username}, email = #{user.email}
        WHERE id = #{user.id}
    </foreach>
</update>

<!-- 复杂的foreach用法 -->
<select id="findUsersByMultipleConditions" parameterType="map" resultMap="BaseResultMap">
    SELECT * FROM users
    WHERE 1=1
    <if test="userIds != null and userIds.size() > 0">
        AND id IN
        <foreach collection="userIds" item="userId" open="(" separator="," close=")">
            #{userId}
        </foreach>
    </if>
    <if test="conditions != null and conditions.size() > 0">
        AND (
        <foreach collection="conditions" item="condition" separator=" OR ">
            (username LIKE CONCAT('%', #{condition.keyword}, '%') 
             AND age BETWEEN #{condition.minAge} AND #{condition.maxAge})
        </foreach>
        )
    </if>
</select>

3.6 trim标签

trim标签是更通用的标签,可以自定义前缀、后缀和分隔符的处理。

xml 复制代码
<!-- 使用trim实现where功能 -->
<select id="findUsersWithTrim" parameterType="User" resultMap="BaseResultMap">
    SELECT * FROM users
    <trim prefix="WHERE" prefixOverrides="AND |OR ">
        <if test="username != null">
            AND username = #{username}
        </if>
        <if test="email != null">
            AND email = #{email}
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
    </trim>
</select>

<!-- 使用trim实现set功能 -->
<update id="updateUserWithTrim" parameterType="User">
    UPDATE users
    <trim prefix="SET" suffixOverrides=",">
        <if test="username != null">
            username = #{username},
        </if>
        <if test="email != null">
            email = #{email},
        </if>
        <if test="age != null">
            age = #{age},
        </if>
    </trim>
    WHERE id = #{id}
</update>

<!-- 复杂的trim用法 -->
<insert id="insertUserWithTrim" parameterType="User">
    INSERT INTO users
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="username != null">
            username,
        </if>
        <if test="email != null">
            email,
        </if>
        <if test="password != null">
            password,
        </if>
        <if test="age != null">
            age,
        </if>
    </trim>
    <trim prefix="VALUES (" suffix=")" suffixOverrides=",">
        <if test="username != null">
            #{username},
        </if>
        <if test="email != null">
            #{email},
        </if>
        <if test="password != null">
            #{password},
        </if>
        <if test="age != null">
            #{age},
        </if>
    </trim>
</insert>

4. SQL片段复用

4.1 基础SQL片段

xml 复制代码
<!-- 定义可重用的SQL片段 -->
<sql id="Base_Column_List">
    id, username, email, password, age, create_time, update_time
</sql>

<sql id="User_Where_Clause">
    <where>
        <if test="username != null and username != ''">
            AND username = #{username}
        </if>
        <if test="email != null and email != ''">
            AND email = #{email}
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
    </where>
</sql>

<!-- 使用SQL片段 -->
<select id="findUsers" parameterType="User" resultMap="BaseResultMap">
    SELECT <include refid="Base_Column_List"/>
    FROM users
    <include refid="User_Where_Clause"/>
    ORDER BY create_time DESC
</select>

<select id="countUsers" parameterType="User" resultType="long">
    SELECT COUNT(*)
    FROM users
    <include refid="User_Where_Clause"/>
</select>

4.2 带参数的SQL片段

xml 复制代码
<!-- 带参数的SQL片段 -->
<sql id="orderByClause">
    <if test="${orderBy} != null and ${orderBy} != ''">
        ORDER BY ${orderBy}
        <if test="${orderDirection} != null and ${orderDirection} != ''">
            ${orderDirection}
        </if>
    </if>
</sql>

<!-- 使用带参数的SQL片段 -->
<select id="findUsersWithOrder" parameterType="map" resultMap="BaseResultMap">
    SELECT <include refid="Base_Column_List"/>
    FROM users
    <include refid="User_Where_Clause"/>
    <include refid="orderByClause">
        <property name="orderBy" value="orderBy"/>
        <property name="orderDirection" value="orderDirection"/>
    </include>
</select>

4.3 复杂SQL片段组合

xml 复制代码
<!-- 分页查询片段 -->
<sql id="limitClause">
    <if test="offset != null and limit != null">
        LIMIT #{offset}, #{limit}
    </if>
</sql>

<!-- 连接查询片段 -->
<sql id="userRoleJoin">
    LEFT JOIN user_roles ur ON u.id = ur.user_id
    LEFT JOIN roles r ON ur.role_id = r.id
</sql>

<!-- 复杂查询组合 -->
<select id="findUsersWithRolesAndPaging" parameterType="map" resultMap="UserWithRolesResultMap">
    SELECT 
        u.id, u.username, u.email, u.create_time,
        r.id as role_id, r.role_name, r.description as role_description
    FROM users u
    <include refid="userRoleJoin"/>
    <where>
        <if test="username != null and username != ''">
            AND u.username LIKE CONCAT('%', #{username}, '%')
        </if>
        <if test="roleName != null and roleName != ''">
            AND r.role_name = #{roleName}
        </if>
        <if test="startDate != null">
            AND u.create_time >= #{startDate}
        </if>
        <if test="endDate != null">
            AND u.create_time <= #{endDate}
        </if>
    </where>
    ORDER BY u.create_time DESC
    <include refid="limitClause"/>
</select>

5. 高级映射技巧

5.1 嵌套查询

xml 复制代码
<!-- 用户结果映射,包含延迟加载的角色信息 -->
<resultMap id="UserWithLazyRolesResultMap" type="User">
    <id column="id" property="id"/>
    <result column="username" property="username"/>
    <result column="email" property="email"/>
    
    <!-- 嵌套查询,延迟加载角色信息 -->
    <collection property="roles" 
                ofType="Role" 
                select="findRolesByUserId" 
                column="id"
                fetchType="lazy"/>
</resultMap>

<!-- 查询用户的角色 -->
<select id="findRolesByUserId" parameterType="long" resultType="Role">
    SELECT r.id, r.role_name as roleName, r.description
    FROM roles r
    INNER JOIN user_roles ur ON r.id = ur.role_id
    WHERE ur.user_id = #{userId}
</select>

<!-- 查询用户(角色信息会延迟加载) -->
<select id="findUserWithLazyRoles" parameterType="long" resultMap="UserWithLazyRolesResultMap">
    SELECT id, username, email, create_time
    FROM users
    WHERE id = #{id}
</select>

5.2 嵌套结果映射

xml 复制代码
<!-- 嵌套结果映射,一次查询获取所有数据 -->
<resultMap id="UserWithRolesNestedResultMap" type="User">
    <id column="user_id" property="id"/>
    <result column="username" property="username"/>
    <result column="email" property="email"/>
    <result column="create_time" property="createTime"/>
    
    <!-- 嵌套结果映射 -->
    <collection property="roles" ofType="Role">
        <id column="role_id" property="id"/>
        <result column="role_name" property="roleName"/>
        <result column="role_description" property="description"/>
        <result column="role_create_time" property="createTime"/>
    </collection>
</resultMap>

<!-- 一次查询获取用户和角色信息 -->
<select id="findUserWithRolesNested" parameterType="long" resultMap="UserWithRolesNestedResultMap">
    SELECT 
        u.id as user_id,
        u.username,
        u.email,
        u.create_time,
        r.id as role_id,
        r.role_name,
        r.description as role_description,
        r.create_time as role_create_time
    FROM users u
    LEFT JOIN user_roles ur ON u.id = ur.user_id
    LEFT JOIN roles r ON ur.role_id = r.id
    WHERE u.id = #{id}
</select>

6. 小结

本文深入介绍了MyBatis SQL映射文件和动态SQL的核心功能:

  1. 结果映射:基础映射、复杂类型映射、继承映射
  2. 动态SQL标签:if、where、set、choose、foreach、trim
  3. SQL片段复用:提高代码复用性和维护性
  4. 高级映射:嵌套查询和嵌套结果映射

掌握这些技能可以:

  • 编写灵活的动态SQL语句
  • 处理复杂的查询需求
  • 优化SQL性能和代码结构
  • 实现高效的对象关系映射

🔗 下一篇预告

下一篇文章将探讨MyBatis缓存机制与性能优化,学习如何使用MyBatis的一级缓存和二级缓存来提升应用性能。


相关文章:

相关推荐
WayneJoon.H3 小时前
CTFSHOW | 其他篇题解(一)web396-web416
sql·安全·web安全·网络安全·php
大新屋3 小时前
MongoDB 分片集群把非分片集合转成分片集合
数据库·mongodb
Python代狂魔4 小时前
Redis
数据库·redis·python·缓存
柠檬茶AL4 小时前
36 NoSQL 注入
数据库·nosql·postman
-XWB-4 小时前
PostgreSQL诊断系列(2/6):锁问题排查全攻略——揪出“阻塞元凶”
数据库·postgresql
XiaoMu_0015 小时前
【MongoDB与MySQL对比】
数据库
做科研的周师兄5 小时前
【机器学习入门】1.2 初识机器学习:从数据到智能的认知之旅
大数据·数据库·人工智能·python·机器学习·数据分析·机器人
qq_364371726 小时前
基于 Redis + JWT 的跨系统身份共享方案
数据库·redis
技术与健康6 小时前
LLM实践系列:利用LLM重构数据科学流程04 - 智能特征工程
数据库·人工智能·重构