乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 MyBatis概述与快速上手

    • 本章定位
    • MyBatis简介
    • 环境搭建
    • 第一个MyBatis程序
    • SqlSessionFactoryBuilder与openSession重载
    • SqlSessionFactory与SqlSession
    • SqlSession核心方法
    • 不使用 XML 构建 SqlSessionFactory
    • Mapper接口与映射方式
    • Java API 目录结构
  • 第2章 全局配置文件详解

    • 本章定位
    • properties
    • settings
    • typeAliases
    • typeHandlers
    • objectFactory
    • plugins
    • environments
    • transactionManager
    • dataSource
    • databaseIdProvider
    • mappers
    • 日志配置
  • 第3章 SQL映射文件基础

    • 本章定位
    • select
    • insert
    • update
    • delete
    • 参数传递与占位符
    • 主键生成策略
    • resultType
    • resultMap
    • 自动映射详解
    • sql片段
    • SQL 语句构建器
  • 第4章 动态SQL

    • 本章定位
    • if
    • choose、when、otherwise
    • where
    • set
    • foreach
    • trim
    • bind
    • script 元素:在注解映射器中启用动态 SQL
    • _databaseId 与动态 SQL 的多数据库支持
    • 动态 SQL 中插入脚本语言
  • 第5章 结果映射与关联查询

    • 本章定位
    • resultMap详解
    • association
    • collection
    • discriminator
    • N+1查询问题
    • 延迟加载
  • 第6章 MyBatis注解开发

    • 本章定位
    • @Select
    • @Insert
    • @Update
    • @Delete
    • @Param
    • @Options
    • @SelectKey
    • @Results
    • @Result
    • @One
    • @Many
    • @SelectProvider
  • 第7章 缓存与性能优化

    • 本章定位
    • 一级缓存
    • 二级缓存
    • 缓存配置详解
    • 自定义缓存
    • Executor执行器类型
    • 分页插件

if

导学

本节你将掌握 MyBatis 中最基础的动态 SQL 元素 if,理解它如何根据传入参数的条件动态拼接 SQL 片段。学习目标是:

  • 掌握 test 属性中 OGNL 表达式的写法
  • 理解 if 在动态查询中的典型用法
  • 认识单独使用 if 时存在的 AND/OR 拼接问题(为后续学习 where 做铺垫)
  • 能够独立编写基于 if 的动态条件查询

定义

if 是 MyBatis 动态 SQL 中最基础的条件判断元素。它根据 test 属性中 OGNL 表达式的求值结果,决定是否将包裹的 SQL 片段包含到最终执行的 SQL 语句中。

在传统的 JDBC 编程中,开发者需要手动拼接 SQL 字符串:

String sql = "SELECT * FROM student WHERE 1=1";
if (name != null) {
    sql += " AND name LIKE '%" + name + "%'";  // 丑陋的字符串拼接
}
if (major != null) {
    sql += " AND major = '" + major + "'";      // SQL 注入风险!
}

这种写法不仅代码丑陋、难以维护,还存在严重的 SQL 注入安全隐患。MyBatis 的 if 元素将条件判断从 Java 代码中剥离到 XML 映射文件中,配合预编译参数占位符 #{ },从根本上解决了字符串拼接和 SQL 注入的问题。


适用位置与核心属性

if 元素只能嵌套在以下动态 SQL 父元素内部使用:where、set、trim、foreach,或作为 choose 的子元素 when 的底层实现。

属性是否必填说明
test是OGNL 表达式,返回布尔值。结果为 true 时包含该 SQL 片段,为 false 时忽略

核心原理

动态 SQL if 条件判断流程

OGNL 表达式求值流程

OGNL(Object-Graph Navigation Language)是一种强大的表达式语言。MyBatis 将传入的参数对象(可以是实体类、Map 或基本类型包装对象)放入 OGNL 上下文,然后对 test 属性中的表达式进行求值。常用的 OGNL 表达式写法包括:

表达式含义
name != null属性不为 null
name != ''属性不为空字符串
name != null and name != ''同时满足不为 null 且不为空字符串
score != null数值类型不为 null
score >= 60数值比较
student != null and student.name != null对象及其嵌套属性判断

完整示例

场景说明

乐途学院的学生管理系统需要提供一个动态组合查询功能:管理员可以只按姓名模糊查询,也可以只按专业精确查询,也可以同时按姓名和专业查询,也可以两个条件都不填(查询全部)。要求使用 if 元素实现动态条件拼接。

操作前的数据库表结构及初始数据

CREATE TABLE student (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(20),
    age INT,
    major VARCHAR(20),
    score DECIMAL(5,2)
);

INSERT INTO student (name, age, major, score) VALUES
('大翔', 22, '计算机科学', 95.5),
('白歌', 21, '软件工程', 88.0),
('小崔', 20, '计算机科学', 92.0),
('黄俪', 21, '信息安全', 90.5),
('李眉', 22, '软件工程', 87.0);

当前表数据:

idnameagemajorscore
1大翔22计算机科学95.50
2白歌21软件工程88.00
3小崔20计算机科学92.00
4黄俪21信息安全90.50
5李眉22软件工程87.00

完整的映射文件片段

StudentMapper.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.flying.mapper.StudentMapper">

    <!-- 动态条件查询学生:使用 if 拼接 WHERE 条件 -->
    <select id="findByCondition" resultType="Student"
            parameterType="com.flying.entity.Student">
        SELECT id, name, age, major, score
        FROM student
        WHERE 1=1
        <if test="name != null and name != ''">
            AND name LIKE CONCAT('%', #{name}, '%')
        </if>
        <if test="major != null and major != ''">
            AND major = #{major}
        </if>
        <if test="minScore != null">
            AND score >= #{minScore}
        </if>
    </select>

</mapper>

Student.java(实体类)

package com.flying.entity;

public class Student {
    private Integer id;
    private String name;
    private Integer age;
    private String major;
    private Double score;
    private Double minScore;  // 查询条件:最低分数
    // getter 和 setter 省略
}

StudentMapper.java(接口)

package com.flying.mapper;

import com.flying.entity.Student;
import java.util.List;

public interface StudentMapper {
    List<Student> findByCondition(Student condition);
}

实际执行结果

情况一:name 和 major 都传入

测试代码:

Student condition = new Student();
condition.setName("翔");
condition.setMajor("计算机科学");
List<Student> list = mapper.findByCondition(condition);

最终生成的 SQL 语句:

SELECT id, name, age, major, score FROM student WHERE 1=1 AND name LIKE CONCAT('%', ?, '%') AND major = ?

参数值: 翔, 计算机科学

查询结果集:

idnameagemajorscore
1大翔22计算机科学95.50

情况二:只传入 name

测试代码:

Student condition = new Student();
condition.setName("白");
List<Student> list = mapper.findByCondition(condition);

最终生成的 SQL 语句:

SELECT id, name, age, major, score FROM student WHERE 1=1 AND name LIKE CONCAT('%', ?, '%')

参数值: 白

查询结果集:

idnameagemajorscore
2白歌21软件工程88.00

情况三:只传入 major

测试代码:

Student condition = new Student();
condition.setMajor("软件工程");
List<Student> list = mapper.findByCondition(condition);

最终生成的 SQL 语句:

SELECT id, name, age, major, score FROM student WHERE 1=1 AND major = ?

参数值: 软件工程

查询结果集:

idnameagemajorscore
2白歌21软件工程88.00
5李眉22软件工程87.00

情况四:什么都不传入

测试代码:

Student condition = new Student();
List<Student> list = mapper.findByCondition(condition);

最终生成的 SQL 语句:

SELECT id, name, age, major, score FROM student WHERE 1=1

查询结果集:

idnameagemajorscore
1大翔22计算机科学95.50
2白歌21软件工程88.00
3小崔20计算机科学92.00
4黄俪21信息安全90.50
5李眉22软件工程87.00

分析

从四种情况的对比可以清晰看到 if 的工作机制:

  1. 条件满足时:OGNL 表达式求值为 true,if 包裹的 SQL 片段被保留,最终 SQL 包含该条件
  2. 条件不满足时:OGNL 表达式求值为 false,if 包裹的 SQL 片段被丢弃,最终 SQL 不包含该条件
  3. 1=1 的妥协:示例中使用了 WHERE 1=1 这种技巧来避免第一个条件前没有 AND 的问题。如果没有 1=1,当只有 major 条件满足时,SQL 会变成 WHERE AND major = ?,这是语法错误的。这种写法虽然能工作,但不够优雅,下一节将学习 where 元素来彻底解决此问题。

易错场景 / 常见误区

误区错误示例后果正解
只判断 != null 忽略空字符串<if test="name != null">传入空字符串 "" 时,会拼接 name LIKE '%%',查询无意义<if test="name != null and name != ''">
在 test 中直接使用 #{ }<if test="#{name} != null">语法错误,test 中应直接写属性名<if test="name != null">
数值类型与空字符串比较<if test="age != ''">age 是 Integer 类型,与字符串比较可能触发类型转换异常数值类型只判断 age != null 即可
忘记处理第一个条件的 AND没有 WHERE 1=1当只有后面的条件满足时,SQL 变成 WHERE AND ...,语法错误使用 WHERE 1=1 过渡,或改用 where 元素
OGNL 中使用了 Java 保留字<if test="class != null">class 是 OGNL 保留字,解析失败使用 _class 或改用其他属性名

面试考点

Q1:MyBatis 的 if 元素中 test 属性使用的是什么表达式语言?

A:使用的是 OGNL(Object-Graph Navigation Language)表达式语言。MyBatis 将参数对象放入 OGNL 上下文,由 OGNL 引擎对 test 表达式进行求值,返回布尔结果决定是否包含该 SQL 片段。

Q2:为什么单独使用 if 时通常要写 WHERE 1=1?

A:因为当多个 if 条件动态拼接时,无法确定哪个条件会第一个满足。如果第一个满足的条件前带有 AND,最终 SQL 会变成 WHERE AND ...,导致语法错误。WHERE 1=1 作为一个永真条件,确保后续所有满足的条件前都可以安全地加上 AND。但这种写法不够优雅,生产环境中推荐使用 where 元素来自动处理。

Q3:if 的 test 中能否使用 && 和 ||?

A:不能直接使用 Java 的 && 和 ||。OGNL 中使用 and/&&(部分版本支持)和 or/||,但最稳妥的写法是使用英文单词 and 和 or,例如 name != null and name != ''。

Q4:以下写法有什么问题?

<if test="name != null and name != ''">
    AND name = '%#{name}%'
</if>

A:存在两个问题:① #{ } 是预编译参数占位符,不应该放在单引号内部,否则会被当作字符串字面量处理;② 模糊查询应该使用 CONCAT('%', #{name}, '%') 或数据库特定的字符串拼接函数,而不是直接拼接百分号。正确写法是 name LIKE CONCAT('%', #{name}, '%')。


小结

if 是 MyBatis 动态 SQL 的基石,它通过 OGNL 表达式实现了条件化的 SQL 片段拼接,彻底告别了 JDBC 时代丑陋的字符串拼接和 SQL 注入风险。但单独使用 if 时,开发者需要手动处理 WHERE 关键字和多余的 AND/OR,通常借助 WHERE 1=1 这种技巧来规避。这种妥协方案虽然能工作,但不够优雅。

下一节我们将学习 where 元素,它是专门为解决 if 的 WHERE 和 AND 拼接问题而生的智能包装器,让动态查询更加简洁和专业。


下一章引子

当你掌握了 if 的基础用法后,一定会对 WHERE 1=1 这种妥协写法感到不满。where 元素正是 MyBatis 为解决这个问题提供的优雅方案——它能自动插入 WHERE 关键字,并智能移除多余的 AND 或 OR。继续阅读,让你的动态查询代码更加专业。

上一页
本章定位
下一页
choose、when、otherwise