在Mybatis中 @Select注解中如何拼写动态sql

本文介绍如何在MyBatis Plus中使用@Select注解配合<script>标签实现动态SQL的功能,解决了多表查询时固定SQL带来的不便。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

现在随着mybatis plus的应用,越来越多的弱化了SQL语句,对于单表操作可以说几乎不需要进行自己编写SQL语句了,但对于多表查询操作目前mybatis plus还没有很好的支持,还需要自己编写SQL语句,如:

import java.util.List;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import com.baomidou.mybatisplus.mapper.BaseMapper;
import com.shield.base.model.domain.MenuDO;
import com.shield.base.model.param.MenuTreeParam;

/**
 * 基础数据操作对象
 *
 * @author xxx
 * @date 2018/5/18
 */
@Mapper
public interface MenuDAO extends BaseMapper<MenuDO> {

    /**
     * 根据菜单编码获得所有下级菜单列表(包括本级)
     * @param menuId 菜单编码
     * @return 该菜单下的所有菜单列表(包括本级)
     */
    @Select("WITH menuTree"
        + " AS"
        + "("
        + " SELECT menu1.father_rowid as id,menu1.son_rowid as parentId,menu1.system_name as menuName,"
        + "menu1.system_full_rowid as menuTreeFlat,menu1.level_value as menuLevel,menu1.homepage_status as homeStatus,"
        + "menu1.menu_status as menuType,menu1.sort as sort,menu1.duty_name as createName,"
        + "menu1.duty_datetime as createDate,menu1.update_datetime as updateDate,menu1.stop_status as status"
        + " FROM system_menu_setup menu1 WHERE menu1.father_rowid = #{menuId}"
        + " UNION ALL"
        + " SELECT menu2.father_rowid as id,menu2.son_rowid as parentId,menu2.system_name as menuName,"
        + "menu2.system_full_rowid as menuTreeFlat,menu2.level_value as menuLevel,menu2.homepage_status as homeStatus,"
        + "menu2.menu_status as menuType,menu2.sort as sort,menu2.duty_name as createName,"
        + "menu2.duty_datetime as createDate,menu2.update_datetime as updateDate,menu2.stop_status as status"
        + " FROM system_menu_setup menu2"
        + " INNER JOIN menuTree T ON menu2.son_rowid = T.id"
        + ")"
        + " SELECT id,parentId,menuName,MenuTreeFlat,menuLevel,homeStatus,menuType,sort,createName,"
        + "createDate,updateDate,status  FROM menuTree")
    List<MenuDO> selectMenuTreeList(@Param(value = "menuId") Long menuId);

}
这样整个语句基本上都是写死的,没有办法通过参数动态拼接SQL语句,在对于 相同语句不同参数来拼接SQL语句是十分不便的,而如果使用xml来配置的话可以用
<where>
	<if test="stopStatus != null">
		and menu.stop_status=#{stopStatus} and roleMenu.stop_status=#{stopStatus}
	</if>
	<if test="menuSource != null">
		and menuSource.menu_source=#{menuSource}
	</if>
	<if test="userId != null">
 		and roleUser.operator_rowid=#{userId}
	</if>
</where>

但是现在很多公司可能会采用@Select注解方式来编写SQL语句,而非通过xml 的SQL Mapper,那对于@Select这种该如何做呢?其实很简单,只是需要用<script>标签包围,然后像xml语法一样书写即可,无须任何其他类或自定义注解类来完成,具体事例如下:

package com.szss.shield.base.dao;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import com.baomidou.mybatisplus.mapper.BaseMapper;
import com.shield.base.model.domain.MenuDO;
import com.shield.base.model.param.MenuTreeParam;

/**
 * 基础数据操作对象
 *
 * @author xxxx
 * @date 2018/5/18
 */
@Mapper
public interface MenuDAO extends BaseMapper<MenuDO> {   

    /**
     * 根据当前用户权限获取所有权限内的菜单列表(不分页)
     * @param menuTreeParam 菜单参数
     * @return 当前用户权限获取所有权限内的菜单列表
     */
    @Select("<script>"
        + " WITH menuTree"
        + " AS"
        + " ("
        + " SELECT menu.father_rowid as id,menu.son_rowid as parentId,menu.system_name as menuName,\n"
        + " menu.level_value as menuLevel,menu.homepage_status as homeStatus,\n"
        + " menu.menu_status as menuType,menu.sort as sort,menu.stop_status as status,CAST(MAX(menuSource.menu_path_url) as VARCHAR) as menuUrl    \n"
        + "from system_menu_setup menu \n"
        + " LEFT JOIN system_menu_source_setup menuSource\n"
        + " ON menu.father_rowid=menuSource.system_menu_rowid \n"
        + " LEFT JOIN system_role_custom_menu_setup roleMenu \n"
        + " ON menu.father_rowid=roleMenu.system_menu_rowid \n"
        + " LEFT JOIN system_role_operator_setup roleUser \n"
        + " ON roleUser.system_role_setup_rowid=roleMenu.system_role_rowid  \n"
        + " LEFT JOIN system_role_setup role \n"
        + " ON roleUser.system_role_setup_rowid=role.rowid\n"
        + " LEFT JOIN system_department_menu_setup depMenu\n"
        + " ON menu.father_rowid=depMenu.system_menu_rowid\n"
        + "<where>"
        + "<if test='stopStatus != null'>"
        + " and menu.stop_status=#{stopStatus} and roleMenu.stop_status=#{stopStatus}\n"
        +"</if>"
        + "<if test='menuSource != null'>"
        + " and menuSource.menu_source=#{menuSource}"
        +"</if>"
        + "<if test='userId != null'>"
        + " and roleUser.operator_rowid=#{userId}\n"
        +"</if>"
        +"</where>"
        + " GROUP BY menu.father_rowid ,menu.son_rowid ,menu.system_name,menu.level_value,menu.homepage_status,\n"
        + " menu.menu_status,menu.sort,menu.duty_name,menu.duty_datetime,menu.update_datetime,menu.stop_status\n"
        + "  UNION ALL\n"
        + "  SELECT menu2.father_rowid as id,menu2.son_rowid as parentId,menu2.system_name as menuName,\n"
        + " menu2.level_value as menuLevel,menu2.homepage_status as homeStatus,\n"
        + " menu2.menu_status as menuType,menu2.sort as sort,menu2.stop_status as status,CAST('' as VARCHAR) as menuUrl\n"
        + "  FROM system_menu_setup menu2\n"
        + "  INNER JOIN menuTree T ON menu2.father_rowid= T.parentId\n"
        + " )\n"
        + "  SELECT id,parentId,menuName,menuLevel,homeStatus,menuType,sort,status,max(menuUrl) as menuUrl FROM menuTree\n"
        + " GROUP BY id,parentId,menuName,menuLevel,homeStatus,menuType,sort,status\n"
        + " ORDER BY menuLevel,sort"
        + " </script>")
    List<MenuDO> selectMenuTreeListByUserId(MenuTreeParam menuTreeParam);
}
至此我们就可以像在xml文件里面一样愉快的动态拼接你想要的SQL语句了!


注意:在@Select注解中采用<script>标签包围拼接SQL语句时不能在标签里有>大于或<小于符号出现,否则会报Caused by: org.xml.sax.SAXParseException: 元素内容必须由格式正确的字符数据或标记组成。需要对这样的标签符号进行转义即可。


<think>首先,用户的问题是关于MyBatis@Select注解中如何实现动态SQL查询。我需要基于系统级指令来构建回答。 系统级指令: 1. 所有行内数学表达式必须使用$...$格式。例如,$x^2$。 2. 独立公式必须使用$$...$$格式并单独成段。 3. LaTeX语法正确。 4. 使用中文回答。 5. 生成相关问题。 6. 回答中引用的段落末尾自然地添加引用标识,例如:[^1]。 用户可见层: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 - 参考站内引用:[^1], [^2], [^3]。 提供的引用: - [^1]: 关于MyBatis @Select注解模糊查询的两种方式。 - [^2]: 使用@Select注解动态SQL语句,包括if标签。 - [^3]: 当参数为空时不添加该参数的判断,模糊查询示例。 用户的问题:如何在MyBatis@Select注解中编写动态SQL查询。 我的回答需要: - 使用中文。 - 结构清晰,逐步解决问题。 - 包含行内数学表达式(如果适用),但由于主题是SQL,可能不直接涉及数学。但指令说所有行内数学表达式必须使用$...$,所以如果有数学部分,必须处理。但这里可能没有数学,所以或许不需要。 - 生成相关问题。 - 在引用段落末尾添加引用标识。 步骤: 1. **理解需求**:用户想了解如何在@Select注解中实现动态SQL查询。动态SQL允许根据条件构建SQL语句。 2. **参考引用**: - [^1]:模糊查询的两种方式,使用${}或concat。 - [^2]:使用<script>标签和if标签来实现动态SQL。 - [^3]:参数为空时不添加条件,包括模糊查询示例。 3. **结构回答**: - 介绍动态SQLMyBatis中的作用。 - 解释使用@Select注解实现动态SQL的方法。 - 提供示例代码。 - 讨论常见场景,如模糊查询、条件判断。 - 添加引用标识。 - 生成相关问题。 4. **处理数学表达式**:主题是编程,可能没有数学表达式。但指令说所有行内数学表达式必须使用$...$,所以如果有变量或简单的表达式,可以应用。例如,在解释SQL语法时,如果有类似表达式,用$...$包裹。但在这个上下文中,可能不必要。指令说“包括变量、不等式、方程等”,但SQL查询不是数学表达式。为了安全起见,我可以在解释参数时使用$...$,比如$name$表示变量名。 5. **生成相关问题**:在回答末尾,添加相关问题部分。 大纲回答: - **引言**:简要介绍MyBatis动态SQL@Select注解。 - **方法1:使用<script>标签**:解释如何用<script>包裹SQL,并使用if、choose等标签。 - 示例代码。 - 引用[^2]。 - **方法2:模糊查询示例**:展示模糊查询的实现。 - 使用concat函数或${}方式。 - 引用[^1]。 - **处理参数为空**:如何在参数为空时跳过条件。 - 示例代码。 - 引用[^3]。 - **总结**:最佳实践或注意事项。 - **相关问题**:生成3-5个相关问题。 确保LaTeX语法正确:如果没有数学,可能不需要。但指令说“所有行内数学表达式”,所以或许在描述变量时使用$name$。 例如:在SQL中,参数如$name$可以动态插入。 生成相关问题: 1. MyBatis动态SQL中如何防止SQL注入? 2. @Select注解与其他注解(如@Insert)的区别? 3. 如何优化MyBatis动态SQL的性能? 4. 模糊查询的替代方法? 5. MyBatis中如何处理多个条件? 引用添加:在引用段落末尾添加[^n]。例如,当提到模糊查询方式时,添加[^1]。 完整回答:</think>### MyBatis @Select 注解中实现动态 SQL 查询的方法 MyBatis 是一个优秀的持久层框架,支持在注解(如 `@Select`)中编写动态 SQL 查询,允许根据参数值动态构建 SQL 语句。这在处理条件查询、模糊匹配等场景时非常实用。下面我将逐步解释实现方法,并提供示例代码。实现动态 SQL 的核心是使用 MyBatis 的 OGNL 表达式和 XML 标签(如 `<if>`, `<choose>`),通过 `<script>` 标签包裹 SQL 语句来实现动态逻辑[^2]。 #### 步骤 1: 使用 `<script>` 标签包裹 SQL 在 `@Select` 注解中,用 `<script>` 标签包裹整个 SQL 语句,这允许在其中嵌入动态标签(如 `<if>`, `<when>`)。这些标签基于参数值决定是否添加特定 SQL 片段。 - **语法要点**: - 用 `<script>` 开始和结束。 - 使用 `<if test="条件">` 添加条件判断。 - 参数引用使用 `#{参数名}` 来防止 SQL 注入。 - 示例:查询用户表,当用户名参数 `$name$` 不为空时,添加模糊查询条件[^3]。 ```java @Select({ "<script>", "SELECT * FROM user", "WHERE 1=1", // 初始条件,确保后续 AND 语句有效 "<if test='name != null and name != \"\"'>", "AND name LIKE CONCAT('%', #{name}, '%')", // 使用 CONCAT 实现模糊查询 "</if>", "</script>" }) List<User> selectUsersByName(String name); // 参数 name 可为空 ``` - **说明**: - 如果 `$name$` 为空,SQL 会简化为 `SELECT * FROM user WHERE 1=1`,忽略模糊条件。 - `CONCAT('%', #{name}, '%')` 是安全的模糊查询方式,避免使用 `${}` 导致的注入风险[^1]。 - 引用:该方法支持动态添加条件,确保代码灵活[^2]。 #### 步骤 2: 实现模糊查询的两种方式 模糊查询是动态 SQL 的常见需求。MyBatis 提供两种主要方式,各有优缺点: - **方式 1: 使用 `CONCAT` 函数(推荐)** - 安全可靠,防止 SQL 注入。 - 语法:`LIKE CONCAT('%', #{param}, '%')`。 - 示例: ```java @Select({ "<script>", "SELECT * FROM product", "WHERE description LIKE CONCAT('%', #{keyword}, '%')", // 直接嵌入模糊逻辑 "</script>" }) List<Product> searchProducts(String keyword); ``` - 引用:这种方式在参数处理上更安全[^1]。 - **方式 2: 使用 `${}` 插值(不推荐)** - 简单但危险,易引发 SQL 注入。 - 语法:`LIKE '%${param}%'`。 - 示例: ```java @Select("SELECT * FROM user WHERE username LIKE '%${name}%'") // 直接拼接字符串 List<User> selectUsersByName(String name); ``` - **警告**:仅在内部可信参数时使用,否则应避免[^1]。 #### 步骤 3: 处理多条件动态查询 对于复杂场景(如多参数组合),使用 `<choose>`, `<when>`, `<otherwise>` 标签实现分支逻辑。 - **示例:多条件用户查询** - 需求:根据 `$name$`(模糊匹配)、`$age$`(精确匹配)和 `$status$`(可选状态)动态构建 SQL。 - 代码: ```java @Select({ "<script>", "SELECT * FROM user", "WHERE 1=1", "<if test='name != null and name != \"\"'>", "AND name LIKE CONCAT('%', #{name}, '%')", "</if>", "<if test='age != null'>", "AND age = #{age}", "</if>", "<choose>", "<when test='status != null'>", "AND status = #{status}", "</when>", "<otherwise>", "AND status = 'active'", // 默认值 "</otherwise>", "</choose>", "</script>" }) List<User> selectUsers(String name, Integer age, String status); ``` - **说明**: - `<if>` 用于非必要条件(如 `$name$` 或 `$age$` 为空时跳过)。 - `<choose>` 实现多分支,类似 switch-case 逻辑。 - 引用:此方法确保参数为空时自动忽略相关条件[^3]。 #### 最佳实践与注意事项 - **防止 SQL 注入**:优先使用 `#{}` 而非 `${}`,因为 `#{}` 会预编译参数,而 `${}` 直接拼接字符串[^1]。 - **性能优化**:避免过度动态化,复杂逻辑可移入 XML 文件以提高可读性。 - **测试**:确保空参数、边界值(如空字符串)被正确处理,避免运行时错误。 - **模糊查询安全**:始终用 `CONCAT` 或 `#{param}` 构建模糊模式,而非硬编码 `%`[^2]。 通过以上方法,您可以高效实现动态 SQL 查询,提升代码的灵活性和可维护性。如果您有具体场景(如分页或关联查询),可提供更多细节。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

apicescn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值