详细分析Mybatis中的动态Sql(附Demo)
目录
- 前言
- 1. 基本知识
- 2. 注意事项
- 3. 拓展
前言
以往的Java基本知识推荐阅读:
- java框架 零基础从入门到精通的学习路线 附开源项目面经等(超全)
- 【Java项目】实战CRUD的功能整理(持续更新)
- Mybatis从入门到精通(全)
- MyBatis-plus从入门到精通(全)
1. 基本知识
在MyBatis中,动态SQL是一种强大的功能,允许在<select>
, <insert>
, <update>
, <delete>
等标签中根据条件动态构建SQL语句
这种动态性可以简化数据库查询的复杂度,避免硬编码查询逻辑
MyBatis 提供了几种常用的动态 SQL 标签,用来处理条件判断、循环和字符串拼接等操作:
<if>
标签:根据给定的条件动态拼接SQL
<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语句,允许你在多个条件中选择一个来执行
<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查询等
<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语句,自动去掉多余的逗号
<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>
将整个列表插入或作为查询条件,这会导致内存问题
<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的方法
- 分页处理:可以将大批量数据分段处理,例如在代码中将list切分为较小的块
List<List<Integer>> partitionedIds = Lists.partition(largeIdsList, 1000);
for (List<Integer> idsBatch : partitionedIds) {
userMapper.findLargeUsersByIds(idsBatch);
}
-
避免过大的IN查询:IN查询是常见的OOM来源,可以通过数据库表连接等方式优化查询。
-
懒加载:通过懒加载策略,避免一次性加载过多数据到内存中
3. 拓展
在Java代码中,进行参数校验是一种很好的实践,尤其是在使用动态SQL时,如果不校验传入的参数,可能会导致意外的SQL拼接,从而带来性能问题或安全隐患
- 基本的注解校验
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注解,可以自动在控制层或服务层进行参数校验
@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");
}
- 自定义校验逻辑:有时内置的注解不够,可以自定义校验注解
例如,限制用户名长度或格式
@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代码层面,通过参数校验来确保数据的安全和正确性,是防止问题发生的关键步骤