详细分析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代码层面,通过参数校验来确保数据的安全和正确性,是防止问题发生的关键步骤

相关推荐
runepic3 分钟前
Python + PostgreSQL 批量图片分发脚本:分类、去重、断点续拷贝
服务器·数据库·python·postgresql
〝七夜5693 分钟前
JVM内存结构
java·开发语言·jvm
初级炼丹师(爱说实话版)3 分钟前
JAVA泛型作用域与静态方法泛型使用笔记
java·开发语言·笔记
盖世英雄酱581363 分钟前
Java.lang.Runtime 深度解析
java·后端
i***118618 分钟前
Django视图与URLs路由详解
数据库·django·sqlite
2***c43538 分钟前
Redis——使用 python 操作 redis 之从 hmse 迁移到 hset
数据库·redis·python
2***d88542 分钟前
redis的启动方式
数据库·redis·bootstrap
j***57681 小时前
【MySQL】mysqldump使用方法
数据库·mysql·oracle
CoderYanger1 小时前
递归、搜索与回溯-穷举vs暴搜vs深搜vs回溯vs剪枝:12.全排列
java·算法·leetcode·机器学习·深度优先·剪枝·1024程序员节
Coder-coco1 小时前
在线商城系统|基于springboot vue在线商城系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·宠物