当前位置: 首页 > news >正文

MyBatis 动态 SQL 详细指南【完整示例】

MyBatis 动态 SQL 详细指南

一、数据库表结构

创建一个名为 users 的用户表,表结构如下:

CREATE TABLE IF NOT EXISTS users
(user_id   bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '用户ID',user_name varchar(128)    NULL COMMENT '用户名',user_age  int COMMENT '用户年龄',user_status varchar(32) COMMENT '用户状态',create_time datetime DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '创建时间',PRIMARY KEY (user_id)
) COMMENT '用户表' COLLATE = utf8mb4_unicode_ci;

二、MyBatis 基础元素

1. Java Bean 的 ORM 映射对象

package com.example.entity;public class User {private Long id;private String name;private Integer age;private String status;private java.util.Date createTime;// 省略 Get、Set 方法@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +", age=" + age +", status='" + status + '\'' +", createTime=" + createTime +'}';}
}

2. <mapper> 标签

<mapper> 是 MyBatis 映射文件的根标签,通过 namespace 属性指定对应的 Mapper 接口全限定名,实现 XML 映射文件与 Java 接口的关联。示例如下:

<mapper namespace="com.example.dao.UserMapper"><!-- 具体的 SQL 语句和配置会写在这个标签内部,通过 namespace 建立与接口的联系 -->
</mapper>

3. <resultMap> 标签

用于定义数据库表字段和 Java 对象属性的映射关系,尤其在表字段名与对象属性名不一致时非常有用。对应的 resultMap 配置如下:

<resultMap id="UserResultMap" type="com.example.entity.User"><!-- 主键映射,property 对应实体类属性名,column 对应数据库表中的列名 --><id property="id" column="user_id"/> <!-- 普通字段映射,property 对应实体类属性名,column 对应数据库表中的列名 --><result property="name" column="user_name"/> <result property="age" column="user_age"/><result property="status" column="user_status"/><result property="createTime" column="create_time"/>
</resultMap>

4. <sql> 标签

用于定义可复用的 SQL 片段,提高代码的可维护性和复用性。通过 <include> 标签引用这些片段,示例如下:

<sql id="Base_Column_List">user_id, user_name, user_age, user_status, create_time
</sql>
<select id="selectUserById" resultMap="UserResultMap">SELECT<include refid="Base_Column_List"/> <!-- 引入可复用的 SQL 片段 -->FROM usersWHERE user_id = #{id}
</select>

三、Mapper 接口定义

package com.example.dao;import com.example.entity.User;
import org.apache.ibatis.annotations.Param;import java.util.List;public interface UserMapper {User selectUserById(Long id);List<User> selectUsersByCondition(User user);int batchInsertUsers(List<User> userList);int updateUser(User user);
}

四、核心动态 SQL 标签详解

1. 条件控制类标签

  • <if> 标签:根据条件判断决定 SQL 片段是否包含在最终的 SQL 语句中。常用于根据不同的参数值动态生成 SQL。示例:
<select id="selectUsersByCondition" resultMap="UserResultMap">SELECT<include refid="Base_Column_List"/>FROM users<where><!-- 当 name 不为空且不为空字符串时,添加该条件 --><if test="name != null and name != ''">AND user_name = #{name}</if><!-- 当 age 不为空时,添加该条件 --><if test="age != null">AND user_age = #{age}</if><!-- 当 status 不为空且不为空字符串时,添加该条件 --><if test="status != null and status != ''">AND user_status = #{status}</if></where>
</select>
  • <choose><when><otherwise> 标签:类似于 Java 中的 switch - case 语句,根据不同的条件选择不同的 SQL 片段。只会执行第一个满足条件的 <when> 标签中的 SQL 片段,若所有 <when> 条件都不满足,则执行 <otherwise> 标签中的 SQL 片段。示例:
<select id="selectUsersByTime" resultMap="UserResultMap">SELECT<include refid="Base_Column_List"/>FROM users<where><choose><!-- 当 startTime 和 endTime 都不为空时,添加该条件到 SQL 语句中 --><when test="startTime != null and endTime != null">AND create_time BETWEEN #{startTime} AND #{endTime}</when><!-- 当 startTime 不为空且 endTime 为空时,添加该条件到 SQL 语句中 --><when test="startTime != null and endTime == null">AND create_time = #{startTime}</when><!-- 当上述条件都不满足时,添加该条件到 SQL 语句中 --><otherwise>AND YEAR(create_time) = #{year}</otherwise></choose></where>
</select>

2. 语法修正类标签

  • <where> 标签:自动处理 SQL 语句中的 WHERE 关键字,并移除多余的 ANDOR 运算符,避免拼接 SQL 时出现语法错误。示例:
<select id="selectUsersByCondition" resultMap="UserResultMap">SELECT<include refid="Base_Column_List"/>FROM users<where><!-- 当 name 不为空且不为空字符串时,添加该条件,where 标签会自动处理开头的 AND --><if test="name != null and name != ''">AND user_name = #{name}</if><!-- 当 age 不为空时,添加该条件,where 标签会自动处理与上一个条件之间的 AND --><if test="age != null">AND user_age = #{age}</if></where>
</select>
  • <trim> 标签:提供更灵活的语法修正功能,可替代 <where><set> 标签。可以自定义前缀、后缀以及需要删除的字符。示例:
<trim prefix="WHERE" prefixOverrides="AND |OR"><!-- 当 name 不为空时,添加该条件,trim 标签会根据配置处理前缀和多余字符 --><if test="name != null">AND name=#{name}</if>
</trim>
  • <set> 标签:自动处理 UPDATE 语句的 SET 子句,动态更新字段并去除末尾逗号。示例:
<update id="updateUser" parameterType="com.example.entity.User">UPDATE users<set><!-- 当 name 不为空时,添加该更新字段,set 标签会自动处理末尾逗号 --><if test="name != null">name = #{name},</if><!-- 当 age 不为空时,添加该更新字段,set 标签会自动处理与上一个字段之间的逗号 --><if test="age != null">age = #{age}</if><if test="status != null">status = #{status}</if></set>WHERE id = #{id}
</update>

3. 集合处理类标签

  • <foreach> 标签:用于遍历集合,常用于批量操作或根据集合元素动态生成 SQL 语句。例如批量插入数据:
<insert id="batchInsertUsers" parameterType="java.util.List">INSERT INTO users (user_name, user_age, user_status, create_time)VALUES<!-- collection 指向传入的集合参数,item 表示集合中的每个元素,separator 指定元素之间的分隔符 --><foreach collection="list" item="user" separator=",">(#{user.name}, #{user.age}, #{user.status}, #{user.createTime})</foreach>
</insert>
  • <bind> 标签:预定义变量,在模糊查询等场景中非常有用。示例:
<select id="selectUsersByPartialName" resultMap="UserResultMap"><!-- 绑定一个名为 namePattern 的变量,其值为拼接后的模糊查询字符串 --><bind name="namePattern" value="'%' + name + '%'"/>SELECT<include refid="Base_Column_List"/>FROM users<where>user_name LIKE #{namePattern}</where>
</select>

五、参数引用

在 SQL 语句中使用 #{} 语法引用传入参数的属性。MyBatis 会对参数进行预编译处理,有效防止 SQL 注入攻击。示例:

<select id="selectUserById" resultMap="UserResultMap">SELECT<include refid="Base_Column_List"/>FROM usersWHERE user_id = #{id} <!-- #{id} 引用传入的 id 参数,进行预编译处理 -->
</select>

六、常见查询场景处理

1. 范围查询

当需要根据某个字段的范围进行查询时,使用 BETWEEN 关键字结合动态 SQL 实现。示例:

<select id="selectUsersByTimeRange" resultMap="UserResultMap">SELECT<include refid="Base_Column_List"/>FROM users<where><!-- 当 startTime 和 endTime 都不为空时,添加范围查询条件 --><if test="startTime != null and endTime != null">AND create_time BETWEEN #{startTime} AND #{endTime}</if></where>
</select>

2. 精确查询

根据某个字段的精确值进行查询时,直接使用 = 进行比较。示例:

<select id="selectUserByName" resultMap="UserResultMap">SELECT<include refid="Base_Column_List"/>FROM users<where><!-- 当 name 不为空且不为空字符串时,添加精确查询条件 --><if test="name != null and name != ''">AND user_name = #{name}</if></where>
</select>

3. 模糊查询

使用 LIKE 关键字结合 CONCAT 函数进行模糊查询。示例:

<select id="selectUsersByPartialName" resultMap="UserResultMap">SELECT<include refid="Base_Column_List"/>FROM users<where><!-- 当 partialName 不为空且不为空字符串时,添加模糊查询条件 --><if test="partialName != null and partialName != ''">AND user_name LIKE CONCAT('%', #{partialName}, '%')</if></where>
</select>

4. 字符串列表匹配

使用 FIND_IN_SET 函数判断一个字符串是否存在于以逗号分隔的字符串列表中。示例:

<select id="selectUsersByRoleIds" resultMap="UserResultMap">SELECT<include refid="Base_Column_List"/>FROM users<where><!-- 当 roleIds 不为空且不为空字符串时,添加字符串列表匹配条件 --><if test="roleIds != null and roleIds != ''">AND FIND_IN_SET(#{roleIds}, user_role_ids)</if></where>
</select>

七、性能优化与最佳实践

1. OGNL 表达式优化

避免在 XML 中使用过于复杂的 OGNL 表达式,优先在 Java 层处理判断逻辑,以提高性能和代码可读性。例如:

<if test="@com.utils.MyBatisUtils@isValid(name)">

2. 批量操作优化

  • 使用 BATCH 模式执行 SqlSession,减少数据库连接开销。
  • 对于超大数据集,分批次处理数据,如每 1000 条提交一次,避免一次性处理大量数据导致性能问题。

3. 缓存策略

  • 二级缓存:在 Mapper 级别配置二级缓存,实现跨会话共享缓存数据。示例:
<mapper namespace="com.UserMapper"><cache eviction="LRU" flushInterval="60000"/>
</mapper>
  • 注意脏读问题:在执行更新操作后,手动清除缓存,确保数据一致性。例如:
<update id="updateUser" flushCache="true">

八、安全与规范

1. 防 SQL 注入

禁止直接拼接 ${} 参数(除非绝对可信),应使用 #{} 预编译参数化查询,防止 SQL 注入攻击。

2. 参数校验

  • 集合判空:使用 <if test="list != null and!list.isEmpty()"> 判断集合是否为空,避免空指针异常。
  • 字符串处理:使用 <if test="name != null and name.trim() != ''"> 处理字符串,确保字符串不为空且去除前后空格。

3. 类型安全

显式声明 jdbcType,避免 NULL 类型推断错误。例如:

#{age,jdbcType=INTEGER}

九、高级特性

1. 注解动态 SQL

通过注解方式实现动态 SQL,提高代码的简洁性和可读性。示例:

@SelectProvider(type = UserSqlBuilder.class, method = "buildQuery")
List<User> findUsers(UserQuery query);

2. 动态表名映射

在 SQL 语句中动态指定表名,但需注意潜在的 SQL 注入风险。示例:

<select id="selectByTable" resultType="map">SELECT * FROM ${tableName}
</select>

十、使用示例

import com.example.dao.UserMapper;
import com.example.entity.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;public class MyBatisDynamicSQLExample {public static void main(String[] args) {try {// 加载 MyBatis 配置文件InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// 获取 SqlSessionSqlSession sqlSession = sqlSessionFactory.openSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);// 根据 ID 查询用户User userById = userMapper.selectUserById(1L);System.out.println("根据 ID 查询用户: " + userById);// 根据条件查询用户User userCondition = new User();userCondition.setName("John");userCondition.setAge(30);List<User> usersByCondition = userMapper.selectUsersByCondition(userCondition);System.out.println("根据条件查询用户: " + usersByCondition);// 批量插入用户List<User> userList = new ArrayList<>();User user1 = new User();user1.setName("Alice");user1.setAge(25);user1.setStatus("active");User user2 = new User();user2.setName("Bob");user2.setAge(28);user2.setStatus("active");userList.add(user1);userList.add(user2);int batchInsertCount = userMapper.batchInsertUsers(userList);System.out.println("批量插入用户数量: " + batchInsertCount);// 更新用户User updateUser = userMapper.selectUserById(1L);updateUser.setName("Updated Name");int updateCount = userMapper.updateUser(updateUser);System.out.println("更新用户数量: " + updateCount);// 提交事务sqlSession.commit();// 关闭 SqlSessionsqlSession.close();} catch (IOException e) {e.printStackTrace();}}
}

以上示例展示了如何使用 MyBatis 动态 SQL 进行常见的数据库操作,包括查询、插入和更新。通过合理运用 MyBatis 的动态 SQL 标签,可以灵活地构建 SQL 语句,满足

http://www.xdnf.cn/news/339175.html

相关文章:

  • Python+ffmpeg 实现给视频添加字幕
  • Android ImageView 加载 Base64编码图片
  • vscode如何使用 GitHub Copilot
  • Windows ABBYY FineReader 16 Corporate 文档转换、PDF编辑和文档比较
  • 文件操作和IO(下)
  • 基础编程题目集 6-8 简单阶乘计算
  • 【Rust错误处理】Rust错误处理机制详解与应用实战
  • Go:简洁高效,构建现代应用的利器
  • 按摩椅的机芯类型和材质
  • 数字化驱动下的智慧物流与零售创新:全流程无人仓与定制开发开源AI智能名片S2B2C商城小程序的协同实践
  • 大模型(LLMs)推理面
  • android-ndk开发(10): use of undeclared identifier ‘pthread_getname_np‘
  • python读写json文件
  • 手撕基于AMQP协议的简易消息队列-7(客户端模块的编写)
  • 数字孪生技术中端渲染与流渲染的架构对比
  • linux中的常用命令(一)
  • STM32智能刷卡消费系统(uC/OS-III)
  • commonmark.js 源码阅读(一) - Block Parser
  • ComfyUI 学习笔记,案例 6 :FLUX 模型文生图
  • 【Linux系列】目录大小查看
  • 【Python 日期和时间】
  • 【redis】集群模式
  • Windows命令行软件管理器:Chocolatey
  • 多级路由器如何避免IP冲突
  • 使用JAVA对接Deepseek API实现首次访问和提问
  • Linux网络编程day7 线程池
  • 因子分析——数学原理及R语言代码
  • flinksql bug : Max aggregate function does not support type: CHAR
  • 援外培训项目冈比亚数字政府能力建设研修班莅临麒麟信安参观考察
  • Ubuntu每次开机IP都是127.0.0.1