深入解析 MyBatis 动态 SQL使用:高效构建灵活查询的指南(超详细)

一、前言

在企业级应用开发的复杂场景中,持久层作为数据交互的关键枢纽,其设计面临着数据查询灵活性与 SQL 可维护性的双重核心挑战。当业务逻辑日益复杂,传统方式下的 SQL 拼接往往变得繁琐且容易出错,开发者常常需要花费大量精力处理条件判断、空格添加、逗号去除等细节问题,不仅效率低下,还极大地影响了代码的可读性和可维护性。​

MyBatis 作为 Java 生态中主流的持久层框架,其动态 SQL (code that is executed dynamically)功能犹如一把利刃,精准地切入了这一痛点。通过 XML 标签与 OGNL 表达式的巧妙结合,MyBatis 为复杂业务场景下的 SQL 拼接难题提供了优雅的解决方案。相较于 JDBC 等传统框架在 SQL 拼接时的种种不便 —— 开发者需时刻留意空格的添加、列表末尾逗号的处理等细节,稍有不慎便可能导致 SQL 语法错误 ——MyBatis 的动态 SQL 功能让开发者彻底摆脱了这些琐碎的困扰,将更多精力聚焦于业务逻辑的实现。​

当然,掌握动态 SQL 并非轻而易举之事。然而,MyBatis 凭借其强大的动态 SQL 语言,显著提升了这一特性的易用性。对于熟悉 JSTL 或其他基于类 XML 语言文本处理器的开发者来说,MyBatis 的动态 SQL 元素并不会显得陌生,反而有一种似曾相识的亲切感。值得一提的是,在 MyBatis 3 版本中,基于 OGNL 的表达式发挥了重要作用,它替换了之前版本中大量的元素,使需要学习的元素种类大幅精简,较以往减少了一半以上,极大地降低了开发者的学习成本。​

接下来,就让我们一同深看看MyBatis 动态 SQL 的标签是怎么使用的吧~

二、动态 SQL 核心机制解析

2.1动态 SQL 解决的核心问题

传统 JDBC 开发中,复杂条件查询依赖手动拼接 SQL,易引发三大痛点:

  • 注入风险:字符串拼接未预编译,如sql += "AND username = " + username直接拼接用户输入
  • 语法错误:条件为空时导致WHERE ANDSET ,等格式错误
  • 维护成本:多条件组合需编写大量相似 SQL 语句

MyBatis 通过标签化方案实现安全优雅的动态拼接,例如:

  

 这样自动处理条件前缀 / 后缀,可以避免硬编码风险。

2.2 OGNL 表达式引擎底层原理

MyBatis 动态 SQL 的核心逻辑控制依赖于 OGNL(Object-Graph Navigation Language)表达式引擎,其强大的对象图导航能力为动态 SQL 的条件判断提供了底层支持。OGNL 的执行流程可拆解为三个核心技术环节:

Step1:表达式解析与 AST 构建 

OGNL 引擎首先对test属性中的表达式进行词法分析和语法解析,生成抽象语法树(AST)。以典型条件username != null and age > 18为例:​

  1. 词法分析:将字符串拆解为IDENTIFIER(username)、NOT_EQUAL(!=)、NULL(null)等 token 序列​
  2. 语法解析:根据 OGNL 语法规则构建树状结构,根节点为逻辑与(AND)表达式,左子树为比较表达式(username != null),右子树为数值比较(age > 18)​
  3. 语义校验:验证表达式合法性,如属性是否存在、操作符是否匹配类型​

MyBatis 通过OGNLExpressionParser类完成解析,最终生成Expression对象。该过程支持复杂表达式,包括:​

  • 数学运算:price * 0.9 >= cost​
  • 集合操作:roles.contains('ADMIN')​
  • 方法调用:StringUtils.isNotBlank(address)

Step2:上下文访问与参数解析 

OGNL 表达式的求值依赖于表达式上下文(Expression Context),MyBatis 在此过程中做了两层参数封装:​

1,单参数场景:​

  • 当方法只有一个参数时,OGNL 上下文的root节点直接指向该参数对象​
  • 支持深度属性导航,如user.address.city会通过反射依次访问user.getAddress().getCity()​

2,多参数场景:​MyBatis 将参数封装为ParamMap对象(实现Map),包含:​

  • param1/param2等按顺序的参数别名​
  • _parameter指向原始参数对象(若为 POJO 则直接使用)​

比如一个方法selectUser(String username, Integer age)中,OGNL 可通过param1或username(若参数使用@Param注解)访问第一个参数​。

对与集合数据,MyBatis 针对collection、list等集合参数做了特殊处理,在foreach标签中会自动将集合参数包装为Map,包含collection、list、array等键,确保 OGNL 表达式正确解析集合对象。

 Step3:类型转换与参数绑定

OGNL 引擎在表达式求值后,MyBatis 会进行严格的类型转换以确保 SQL 参数安全,主要有两种方式。当Java 类型到 JDBC 类型映射时,MyBatis 会通过TypeHandlerRegistry查找对应的TypeHandler,例如Integer类型转换为 JDBC 类型 4(java.sql.Types.INTEGER)​,而String类型转换为 JDBC 类型 12(java.sql.Types.VARCHAR)​,也可以由我们支持自定义类型处理器,处理枚举、日期等特殊类型​。

第二种是#{} 占位符安全处理,所有通过#{property}引用的参数,最终会生成?占位符,并通过PreparedStatement进行预编译​,此时类型转换发生在数据库驱动层,避免 SQL 注入的同时保证类型安全​。

对于特殊场景的处理方式:​

  • 当参数为null时,根据jdbcType配置(如jdbcType=NULL)生成对应的数据库 NULL 值​
  • 集合类型参数自动拆分为多个占位符,如IN (#{id1}, #{id2})

2.3 MyBatis的执行性能与安全平衡策略

在实际应用中,MyBatis 提供了完善的机制,以帮助我们在使用动态 SQL 时,达成性能优化与安全保障的平衡。

MyBatis 通过启用cacheEnabled="true"开启二级缓存机制,缓存相同 SQL 的查询结果,并支持EhcacheRedis等第三方缓存,减少数据库重复访问,但需根据业务场景设计缓存失效策略,防止脏读。批量执行器优化则通过defaultExecutorType="BATCH"配置,适用于批量插入 / 更新场景,它将多条 SQL 缓存在内存中,使用addBatch()executeBatch()一次性提交。

在 MySQL 8.0 和 InnoDB 引擎的测试环境下,批量插入 1000 条数据时,相比简单执行器,性能提升约 35%。此外,MyBatis 还能通过fetchType="lazy"实现关联对象的延迟加载,避免加载过多数据,并利用resultMap精确映射字段,减少不必要的列查询。

在安全编码规范方面,MyBatis 要求所有用户输入参数必须通过#{property}占位符引用,利用预编译机制防止 SQL 注入,同时限制${}仅用于动态字段名,并进行白名单校验。

例如,在排序字段使用时,需通过校验。同时,应禁止通过动态 SQL 生成DROP TABLETRUNCATE等敏感操作语句。服务层也需对入参进行严格校验,如限制分页参数范围,并使用@Param注解明确参数名称,避免 OGNL 解析歧义。此外,开启logImpl(如LOG4J2)记录执行的 SQL 语句,并对包含动态 SQL 的方法添加访问控制,限制敏感操作的调用频率。

三、核心标签深度解析

MyBatis 动态 SQL 标签一共有如下几种:

标签分类

标签名称

核心功能

语法结构

核心属性

使用场景

示例代码

条件控制

单条件判断,根据表达式结果决定是否包含 SQL 片段

SQL片段

test:OGNL 表达式(如username != null)

单条件过滤(如非空校验)

xml
title = #{title}

条件控制

多条件分支选择(类似 Java 的 switch)

......

互斥条件场景(如按 ID 或用户名查询)

xml
id=#{id}username=#{username}

条件控制

子标签,定义具体分支条件

SQL片段

test:OGNL 表达式

同上

同上

条件控制

子标签,定义默认分支

SQL片段

同上

同上

结构优化

自动处理 WHERE 子句的冗余连接符(去除首个 AND/OR)

条件片段

复杂查询条件拼接(避免1=1硬编码)

xml
name=#{name}AND age>#{age}

结构优化

自动处理 UPDATE 语句的冗余逗号,添加 SET 关键字

字段赋值片段

动态更新场景(如部分字段修改)

xml
name=#{name},age=#{age},update_time=NOW()

结构优化

灵活去除 SQL 片段的前后缀字符(支持自定义前缀 / 后缀 / 覆盖规则)

内容

prefix/suffix:添加前后缀prefixOverrides/suffixOverrides:去除指定字符

复杂格式控制(如批量插入去逗号、自定义 WHERE/SET 逻辑)

xml
条件1 AND 条件2 AND

集合处理

遍历集合生成批量 SQL(如 IN 条件、批量插入)

SQL模板

collection:集合对象item:元素别名separator:元素分隔符open/close:包裹符号

批量操作(如 IN 查询、批量插入 / 更新)

xml
IN #{id}

代码复用

定义可复用的 SQL 片段

SQL内容

id:片段唯一标识

公共字段、条件或表名复用(减少重复代码)

xml
id,name,email

代码复用

引用已定义的片段

refid:目标片段 ID

同上

xml
SELECT FROM user

高级控制

定义临时变量(MyBatis 3.2.4 + 新增)

name:变量名value:OGNL 表达式

复杂表达式拆分(如预处理 LIKE 参数)

xml
name LIKE #{likeName}

下面我们逐一来看看:

3.1 条件控制标签族:精准逻辑筛选

 基础条件判断

使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。比如:

这条语句提供了可选的查找文本功能。如果不传入 “title”,那么所有处于 “ACTIVE” 状态的 BLOG 都会返回;如果传入了 “title” 参数,那么就会对 “title” 一列进行模糊查找并返回对应的 BLOG 结果(细心的读者可能会发现,“title” 的参数值需要包含查找掩码或通配符字符)。

如果希望通过 “title” 和 “author” 两个参数进行可选搜索该怎么办呢?首先,我想先将语句名称修改成更名副其实的名称;接下来,只需要加入另一个条件即可。

注意事项:先判空再取值,避免NullPointerException,如test="user != null and user.age > 18"

 标签

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了这三个标签组合起来用于实现类似 Java 中 switch - case 的多路分支选择逻辑。

MyBatis 会按照顺序依次判断  标签中的条件,当某个条件满足时,就会使用该  标签内的 SQL 片段,若所有  标签的条件都不满足,则使用  标签内的 SQL 片段。

3.2 结构化标签:智能格式处理 

动态 WHERE 生成 标签 

如果我们在条件语句查询中直接使用where而不是标签,就会出现这种情况:

这时候如果没有匹配的条件最终这条 SQL 会变成这样: 

SELECT * FROM BLOG
WHERE

这会导致查询失败。如果匹配的只是第二个条件,那么就会变成这样:

SELECT * FROM BLOG
WHERE
AND title like ‘my script’

这个查询也会失败。这个问题不能简单地用条件元素来解决。此时就可以使用标签来解决,标签用于简化 SQL 语句中 WHERE 子句的编写,它只会在子元素返回任何内容的情况下才插入 “WHERE” ,若子句的开头为 “AND” 或 “OR”关键字,where 元素也会将它们去除。避免因条件为空而产生的 SQL 语法错误。

这样如果第一个  条件不满足,而第二个  条件满足, 标签会自动去掉 AND 关键字,避免产生语法错误。

安全更新 标签 

主要用于 UPDATE 语句中,自动处理 SET 子句,会自动去除多余的逗号,避免 SQL 语法错误。适用于部分字段更新(如用户资料修改、商品库存调整)。

  
    UPDATE sys_user  
      
        username = #{username},  
        email = #{email},  
        update_time = NOW()  
      
    WHERE id = #{id} AND version = #{version}  
  

如果只有一个条件满足, 标签会自动去掉多余的逗号,无有效更新字段时不生成 SET 关键字。

灵活修剪标签 

trim标记是一个格式化的标记,它可以自定义前后缀过滤完成set或者是where标记的功能,比如我们这里通过trim代替where操作:

  • prefix:在当前位置添加一个前缀where

  • prefixoverride:去掉第一个and或者是or

prefix="("添加前缀,suffix=")"添加后缀,使用suffixOverrides=","实现移除末尾逗号,相当于trim与prefix、suffix就能实现动态的标签或者元素符号添加。

3.3 集合处理

foreach是用来对集合的遍历,这个和Java中的功能很类似。通常处理SQL中的in语句。

foreach 元素的功能非常强大,它允许我们指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符

我们可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

我们可以通过下面的方式实现批量查询:

  

也可以实现批量的数据插入:

  
    INSERT INTO sys_user (username, email)  
    VALUES  
      
        (#{user.username}, #{user.email})  
      
  

但是要注意在mapper中,集合参数需通过@Param("idList")显式命名,避免Available parameters are [list]错误。

3.4 代码复用 

在实际开发中我们会遇到许多相同的SQL,比如根据某个条件筛选,这个筛选很多地方都能用到,我们可以将其抽取出来成为一个公用的部分,这样修改也方便,一旦出现了错误,只需要改这一处便能处处生效了,此时就用到了这个标签了。

当多种类型的查询语句的查询字段或者查询条件相同时,可以将其定义为常量,方便调用。

  
    id, username, email, create_time, last_login_time  
  

 也可以通过查询来找到字段信息:


    select  *  from  student

 而则是与用标签配合使用,用于sql标签定义的常量。因为有些查询中可能会引用sql中定义的字段,因此在出现跨Mapper查询的时候这个标签就很方便了。 

比如我在比如你在com.yy.dao.firstMapper这个Mapper的XML中定义了一个SQL片段如下:

  
    id, username, email, create_time, last_login_time  
  

当我子另一个com.yy.dao.secondMapper查询中引用时,就可以这样实现:

也可以引用来实现字段查询:

  

但需要注意:refid这个属性就是指定标签中的id值(唯一标识)。

四、MyBatis的动态SQL多表关联查询 

在日常业务中除了单个数据的查询外,偶尔也会有部分需求可能需要我们采用多表关联查询的方式来进行,为此MyBatis也有对应的标签和集合标签一起来实现我们的动态SQL关联查询。

4.1一对多关联查询 

以常见的学生-老师这个关系为例,在学校中一个学生会有多个课程的老师,因此这样就形成了典型的一对多关系,那么我们可以通过下面这样的动态SQL实现查询一个学生关联了多少个老师的结果:


    
        
        
        
        
        
            
            
            
        
    

    

这里我们会发现一个新的标签这个元素元素是 MyBatis 中最重要最强大的元素。它可以让我们从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许我们进行一些 JDBC 不支持的操作。 我们通过一个简单例子来说明它的作用:

一般情况下我们在设计POJO或者Entity等实体类时,数据库中表的字段往往是与实体类(以User类为例)的属性名称一致,我们就可以使用resultType来返回。

当然,这是理想状态下,属性和字段名都完全一致的情况。但事实上,不一致的情况是有的,这时候我们的resultMap就要登场了。如果User类保持不变,但SQL语句发生了变化,将id改成了uid

那么,在结果集中,我们将会丢失id数据。这时候我们就可以定义一个resultMap,来映射不一样的字段。


	

然后,我们把上面的select语句中的resultType修改为resultMap="getUserByIdMap"。这里面column对应的是数据库的列名或别名;property对应的是结果集的字段或属性。这就是resultMap最简单,也最基础的用法:字段映射。

这样我们上面那个关联查询的结果集的意义就很清楚了:

 标签

用于定义数据库查询结果到 Java 对象的映射关系。在一对多关联查询中,它可以将查询到的多表数据正确地映射到嵌套的 Java 对象结构中。

核心属性

  • id:为这个 resultMap 定义一个唯一的标识符,方便后续的 select 标签引用。
  • type:指定映射的 Java 对象类型,这里是 student1 类。

子标签

  • :用于映射数据库表的主键字段到 Java 对象的属性,确保数据的唯一性标识。例如,property="sid" 表示 Java 对象的 sid 属性,column="sid" 表示数据库表中的 sid 字段。
  • :用于映射普通字段到 Java 对象的属性。
  • :用于处理一对多的关联关系。
    • property:指定 student1 类中用于存储关联对象集合的属性名,这里是 list
    • ofType:指定集合中元素的 Java 类型,这里是 teacher 类。

这样就能通过一个mapper实现通过一个学生查询其对应的所有老师了。

import java.util.List;

public interface StudentMapper {
    List findTeachersByStudentId(int studentId);
}

测试如下:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.junit.jupiter.api.Test;

import java.util.List;

@SpringBootTest
public class StudentMapperTest {

    @Autowired
    private StudentMapper studentMapper;

    @Test
    public void testFindTeachersByStudentId() {
        int studentId = 1;
        List students = studentMapper.findTeachersByStudentId(studentId);
        for (student1 student : students) {
            System.out.println("Student ID: " + student.getSid());
            System.out.println("Student Name: " + student.getSname());
            for (teacher teacher : student.getList()) {
                System.out.println("  Teacher ID: " + teacher.getTid());
                System.out.println("  Teacher Name: " + teacher.getTname());
            }
        }
    }
}

4.2 多对一关联查询

多对一关联查询在实际业务中也十分常见。例如,在一个教育系统里,多个学生可能会对应同一位老师;在一个电商系统中,多个订单可能会关联到同一个用户。下面以学生和班级的关系为例,多个学生属于同一个班级,这就是典型的多对一关联场景。这种场景下,我们常常需要查询学生信息的同时获取其所属班级的信息。

假设我们有两个表:student 表和 class 表。student 表结构:

字段名 类型 描述
id int 学生 ID
name varchar 学生姓名
class_id int 所属班级 ID

class 表结构:

字段名 类型 描述
id int 班级 ID
class_name varchar 班级名称

然后我们确定一下实体类:

// Student.java
public class Student {
    private int id;
    private String name;
    private Class clazz; // 注意这里使用 clazz 避免与 Java 关键字冲突

    // 省略 getter 和 setter 方法
}

// Class.java
public class Class {
    private int id;
    private String class_name;

    // 省略 getter 和 setter 方法
}

 接着定义需要的mapper接口:

import java.util.List;
@Mapper
public interface StudentMapper {
    List getAllStudents();
}

完成后我们通过xml来实现多对一查询:



    
    
    
        
        
        
        
        
        
        
        
            
            
            
            
        
    

    
    
    

这里我们要关注的核心点是标签,在 MyBatis 里, 标签主要用于处理多对一的关联映射。在 MyBatis 的映射配置里, 标签能够把查询结果中的关联表数据映射到 Java 对象里的一个关联对象属性上。借助  标签,我们能够在查询学生信息时,同时把该学生所属班级的信息映射到学生对象的 class 属性中。

  • property="clazz":指定 Student 类中用于存储关联对象(班级)的属性名。
  • javaType="com.example.entity.Class":指定关联对象的 Java 类型,也就是 Class 类。
  • 子标签  和 :用于定义关联对象(班级)的主键和普通字段的映射关系。

这样我们就实现查询所有学生信息,其中也会查询到每个学生关联的班级信息:

import com.example.entity.Student;
import com.example.mapper.StudentMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class StudentMapperTest {

    @Autowired
    private StudentMapper studentMapper;

    @Test
    public void testGetAllStudents() {
        List students = studentMapper.getAllStudents();
        for (Student student : students) {
            System.out.println("Student ID: " + student.getId());
            System.out.println("Student Name: " + student.getName());
            if (student.getClazz() != null) {
                System.out.println("Class ID: " + student.getClazz().getId());
                System.out.println("Class Name: " + student.getClazz().getClass_name());
            } else {
                System.out.println("No associated class.");
            }
            System.out.println("----------------------");
        }
    }
}

除了这种多对一的查询方式外,还可以用于嵌套查询(Select 方式),除了通过 JOIN 语句在一个查询中获取关联数据,还可以使用嵌套查询的方式,即通过多个 SQL 查询来获取关联数据。这种方式适用于关联数据需要单独查询,或者关联数据比较复杂的场景。


    
        
        
        
        
        
    

    



    
  • select 属性:指定要调用的另一个 SQL 查询的 id,这里是 com.example.mapper.ClassMapper.getClassById
  • column 属性:指定传递给嵌套查询的参数,即 student 表中的 class_id

 另外还有一中特别少用的场景-自定义映射规则, 标签内可以使用更复杂的映射规则,例如使用  标签进行鉴别器映射,根据查询结果的某个字段值来决定使用不同的映射规则。不过这种用法相对较少,适用于关联对象有多种类型的情况。这里制作简单说明。


    
    
    
        
            
            
        
    

4.3 多对多关联查询 

在一个教育系统中,一个学生可以选修多门课程,而一门课程也可以被多个学生选修。这就是典型的多对多场景,在这种场景下,我们通常需要查询某个主体关联的多个对象信息,或者通过某个对象查询与之关联的多个主体信息。

以学生和课程的多对多关系为例,需要三张表:student 表、course 表和中间表 student_coursestudent 表结构:

字段名 类型 描述
id int 学生 ID
name varchar 学生姓名

course 表结构:

字段名 类型 描述
id int 课程 ID
course_name varchar 课程名称

student_course 表结构:

字段名 类型 描述
student_id int 学生 ID,关联 student 表的 id
course_id int 课程 ID,关联 course 表的 id

然后定义实体,方式与上面差不多:

import java.util.List;

// Student.java
public class Student {
    private int id;
    private String name;
    private List courses;

    // Getters and Setters
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List getCourses() {
        return courses;
    }

    public void setCourses(List courses) {
        this.courses = courses;
    }
}

// Course.java
public class Course {
    private int id;
    private String course_name;

    // Getters and Setters
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getCourse_name() {
        return course_name;
    }

    public void setCourse_name(String course_name) {
        this.course_name = course_name;
    }
}

完成Mapper 接口定义:

import java.util.List;

public interface StudentMapper {
    List getAllStudentsWithCourses();
}

 最后完成我们的多对多xml定义:


    
        
        
        
            
            
        
    

    

可以看到这是咱们还是用来处理,在多对多场景中,也是用于将一个主体关联的多个对象映射到 Java 对象的集合属性中。

  • property:指定 Java 对象中用于存储关联对象集合的属性名。
  • ofType:指定集合中元素的 Java 类型。

最后即可通过测试验证我们的xml实现结果了:

import com.example.entity.Student;
import com.example.mapper.StudentMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class StudentMapperTest {

    @Autowired
    private StudentMapper studentMapper;

    @Test
    public void testGetAllStudentsWithCourses() {
        List students = studentMapper.getAllStudentsWithCourses();
        for (Student student : students) {
            System.out.println("Student ID: " + student.getId());
            System.out.println("Student Name: " + student.getName());
            System.out.println("Courses:");
            if (student.getCourses() != null) {
                for (Course course : student.getCourses()) {
                    System.out.println("  Course ID: " + course.getId());
                    System.out.println("  Course Name: " + course.getCourse_name());
                }
            } else {
                System.out.println("  No courses selected.");
            }
            System.out.println("----------------------");
        }
    }
}

当然了这种情况下,也可像 实现嵌套查询便于我们理解。比如在一个权限管理系统中,一个用户可能有多个权限和角色信息,那么我们可以通过嵌套查询来实现用户的权限角色多对多处理:





    
            
            
            
            
            
            
            
            
            
            
    

    
        id,username,password,
        real_name,email,phone,
        status,create_time,update_time,
        deleted
    
    
    
        
        
        
        
    
    
    

这种情况下除了要在userMapper中定义查询外,还需要在权限表和角色表定义mapper查询。

package com.yy.satokenapplication.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yy.satokenapplication.entity.SysUser;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
* @author young
* @description 针对表【sys_user(系统用户表)】的数据库操作Mapper
* @createDate 2025-03-16 16:30:47
* @Entity com.yy.satokenapplication.entity.SysUser
*/
@Mapper
public interface SysUserMapper extends BaseMapper {

    SysUser findUserWithRolesAndPermissionsById(@Param("userId") Long userId);
}
package com.yy.satokenapplication.mapper;


import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yy.satokenapplication.entity.SysPermission;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * @author young
 * @description 针对表【sys_permission(系统权限表)】的数据库操作Mapper
 * @createDate 2025-03-16 16:30:47
 * @Entity com/yy/satokenapplication.entity.SysPermission
 */
@Mapper
public interface SysPermissionMapper extends BaseMapper {
    List findPermissionsByUserId(@Param("userId")Long id);
}
package com.yy.satokenapplication.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yy.satokenapplication.entity.SysRole;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
* @author young
* @description 针对表【sys_role(系统角色表)】的数据库操作Mapper
* @createDate 2025-03-16 16:30:47
* @Entity com.yy.satokenapplication.entity.SysRole
*/
@Mapper
public interface SysRoleMapper extends BaseMapper {

    List findRolesByUserId(@Param("userId") Long userId);
}

最后让这个用户的xml进行多方关联从而在嵌套中实现多对多查询处理。

五、安全编码规范与陷阱规避

虽然动态sql使用起来让定制化业务很容易实现,但是在使用动态sql时,大家一定要注意一些编码规范,避免出现SQL注入的情况,以下有一些使用建议希望大家注意:

5.1 SQL 注入防御策略

  1. 禁止裸拼接:永远使用#{param}代替${param},前者生成预编译语句,后者直接字符串替换

    xml

      
    ORDER BY ${sortField} ${sortOrder}  
      
      
        ORDER BY id ${sortOrder}  
        ORDER BY name ${sortOrder}  
      
    
  2. 特殊字符转义:使用= ]]>处理 XML 敏感符号,避免解析错误
  3. 参数类型校验:接口层强制校验参数合法性,如@NotNull(message="查询条件不能为空")

5.2 动态参数校验实践

Java 层校验

public List searchUsers(@Valid UserQuery query) {  
    // 业务逻辑校验,如分页参数合法性  
    if (query.getPageSize() > 100) {  
        throw new IllegalArgumentException("单次查询最大页数为100");  
    }  
    return userMapper.searchUsers(query);  
}  

XML 层防御

  

你可能感兴趣的:(SpringBoot,数据库,mybatis,sql,数据库)