详细分析Mybatis中的动态Sql(附Demo)

目录

  • 前言
  • [1. 基本知识](#1. 基本知识)
  • [2. 注意事项](#2. 注意事项)
  • [3. 拓展](#3. 拓展)

前言

以往的Java基本知识推荐阅读:

  1. java框架 零基础从入门到精通的学习路线 附开源项目面经等(超全)
  2. 【Java项目】实战CRUD的功能整理(持续更新)
  3. Mybatis从入门到精通(全)
  4. MyBatis-plus从入门到精通(全)

1. 基本知识

在MyBatis中,动态SQL是一种强大的功能,允许在<select>, <insert>, <update>, <delete> 等标签中根据条件动态构建SQL语句

这种动态性可以简化数据库查询的复杂度,避免硬编码查询逻辑

MyBatis 提供了几种常用的动态 SQL 标签,用来处理条件判断、循环和字符串拼接等操作:

  • <if>标签:根据给定的条件动态拼接SQL
xml 复制代码
<select id="findUsers" resultType="User">
    SELECT * FROM users
    <where>
        <if test="name != null">
            AND name = #{name}
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
    </where>
</select>
  • <choose>, <when>, <otherwise>标签:类似于Java中的switch语句,允许你在多个条件中选择一个来执行
xml 复制代码
<select id="findUsersByStatus" resultType="User">
    SELECT * FROM users
    <where>
        <choose>
            <when test="status != null">
                AND status = #{status}
            </when>
            <otherwise>
                AND status = 'active'
            </otherwise>
        </choose>
    </where>
</select>
  • <foreach>标签:用于处理循环,一般用来遍历集合构建IN查询等
xml 复制代码
<select id="findUsersByIds" resultType="User">
    SELECT * FROM users
    WHERE id IN
    <foreach item="id" collection="list" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

<trim>, <where>, <set>标签:

  • <trim>:去掉拼接后多余的前后缀
  • <where>:自动在拼接后的条件前加WHERE,并去除多余的AND
  • <set>:主要用于UPDATE语句,自动去掉多余的逗号
xml 复制代码
<update id="updateUser">
    UPDATE users
    <set>
        <if test="name != null">
            name = #{name},
        </if>
        <if test="age != null">
            age = #{age},
        </if>
    </set>
    WHERE id = #{id}
</update>

2. 注意事项

使用此种方式需要注意动态SQL带来的OOM风险

在复杂动态SQL的构建中,特别是当涉及大批量数据或复杂拼接时,容易导致内存溢出(OOM)问题

尤其是在<foreach>中处理大量数据时,如果不加以控制,可能会占用大量内存

假设有一个非常大的List,例如数百万条数据

如果在MyBatis中直接使用<foreach>将整个列表插入或作为查询条件,这会导致内存问题

xml 复制代码
<select id="findLargeUsersByIds" resultType="User">
    SELECT * FROM users
    WHERE id IN
    <foreach item="id" collection="list" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

在这种情况下,MyBatis会尝试将数百万个id拼接到一个SQL语句中,导致内存占用过高,最终导致OOM

规避OOM的方法

  1. 分页处理:可以将大批量数据分段处理,例如在代码中将list切分为较小的块
java 复制代码
List<List<Integer>> partitionedIds = Lists.partition(largeIdsList, 1000);
for (List<Integer> idsBatch : partitionedIds) {
    userMapper.findLargeUsersByIds(idsBatch);
}
  1. 避免过大的IN查询:IN查询是常见的OOM来源,可以通过数据库表连接等方式优化查询。

  2. 懒加载:通过懒加载策略,避免一次性加载过多数据到内存中

3. 拓展

在Java代码中,进行参数校验是一种很好的实践,尤其是在使用动态SQL时,如果不校验传入的参数,可能会导致意外的SQL拼接,从而带来性能问题或安全隐患

  • 基本的注解校验
java 复制代码
public class User {
    @NotNull(message = "Name cannot be null")
    private String name;

    @Min(value = 18, message = "Age should not be less than 18")
    private Integer age;
}

在使用这些注解后,如果传入的参数不符合条件,就会抛出ConstraintViolationException

  • 结合Spring的@Valid注解,可以自动在控制层或服务层进行参数校验
java 复制代码
@PostMapping("/createUser")
public ResponseEntity<?> createUser(@Valid @RequestBody User user, BindingResult result) {
    if (result.hasErrors()) {
        return new ResponseEntity<>(result.getAllErrors(), HttpStatus.BAD_REQUEST);
    }
    userService.createUser(user);
    return ResponseEntity.ok("User created successfully");
}
  • 自定义校验逻辑 :有时内置的注解不够,可以自定义校验注解
    例如,限制用户名长度或格式
java 复制代码
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UsernameValidator.class)
public @interface ValidUsername {
    String message() default "Invalid username";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

public class UsernameValidator implements ConstraintValidator<ValidUsername, String> {
    public void initialize(ValidUsername constraint) {}

    public boolean isValid(String username, ConstraintValidatorContext context) {
        return username != null && username.matches("^[a-zA-Z0-9]+$");
    }
}

总的来说

MyBatis中的动态SQL功能非常灵活,但也存在潜在的性能和内存问题

在复杂查询中,务必小心处理大量数据操作,合理运用分页、懒加载等技术

在Java代码层面,通过参数校验来确保数据的安全和正确性,是防止问题发生的关键步骤

相关推荐
小周不摆烂43 分钟前
Java Web从入门到精通:全面探索与实战(一)
java
cherryc_44 分钟前
JavaSE基础——第六章 类与对象(二)
java·开发语言
快来卷java1 小时前
JVM虚拟机篇(三):JVM运行时数据区与方法区详解
java·jvm·mysql
共享家95271 小时前
Linux常用命令详解:从基础到进阶
linux·服务器·数据库
whltaoin2 小时前
Java实现N皇后问题的双路径探索:递归回溯与迭代回溯算法详解
java·算法
nlog3n4 小时前
Java策略模式详解
java·bash·策略模式
兰琛4 小时前
Compose组件转换XML布局
android·xml·kotlin
我是个假程序员5 小时前
sql server数据库可疑修复
数据库
极限实验室6 小时前
如何使用 Nginx 代理 Easysearch 服务
数据库·nginx
whn19776 小时前
selectdb修改表副本
数据库