news 2026/9/3 1:35:52

动态SQL实现模糊查询

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
动态SQL实现模糊查询

在数据库查询中,模糊查询是最常用的功能之一。然而,当查询条件变得复杂多变时,静态SQL往往显得力不从心。今天我们来探讨如何通过动态SQL实现灵活、安全的模糊查询。

一、为什么需要动态SQL模糊查询?

1.1 传统模糊查询的局限性

-- 静态SQL示例 SELECT * FROM users WHERE username LIKE '%张%' AND email LIKE '%example.com%';

这种写法的问题在于:

  • 当某个条件为空时,查询会失效

  • 条件组合多变时,需要写大量重复代码

  • 难以应对复杂的业务逻辑

1.2 动态SQL的优势

  • 灵活性:根据实际参数动态生成SQL

  • 可维护性:代码更简洁,易于维护

  • 性能优化:避免不必要的查询条件

二、MyBatis动态SQL实现模糊查询

2.1 基础示例:单条件模糊查询

<!-- MyBatis Mapper XML --> <select id="searchUsers" resultType="User"> SELECT * FROM users <where> <if test="keyword != null and keyword != ''"> AND (username LIKE CONCAT('%', #{keyword}, '%') OR email LIKE CONCAT('%', #{keyword}, '%') OR phone LIKE CONCAT('%', #{keyword}, '%')) </if> </where> </select>

2.2 多条件组合模糊查询

<select id="advancedSearch" resultType="User"> SELECT * FROM users <where> <!-- 姓名模糊查询 --> <if test="name != null and name != ''"> AND username LIKE CONCAT('%', #{name}, '%') </if> <!-- 邮箱模糊查询 --> <if test="email != null and email != ''"> AND email LIKE CONCAT('%', #{email}, '%') </if> <!-- 电话号码模糊查询(支持中间四位*号) --> <if test="phonePattern != null and phonePattern != ''"> AND phone LIKE REPLACE(#{phonePattern}, '*', '%') </if> <!-- 地址多字段模糊查询 --> <if test="address != null and address != ''"> AND ( province LIKE CONCAT('%', #{address}, '%') OR city LIKE CONCAT('%', #{address}, '%') OR detail LIKE CONCAT('%', #{address}, '%') ) </if> </where> ORDER BY create_time DESC </select>

2.3 使用<choose>实现条件选择

<select id="smartSearch" resultType="User"> SELECT * FROM users <where> <choose> <when test="searchType == 'name' and keyword != null"> AND username LIKE CONCAT('%', #{keyword}, '%') </when> <when test="searchType == 'email' and keyword != null"> AND email LIKE CONCAT('%', #{keyword}, '%') </when> <when test="searchType == 'phone' and keyword != null"> AND phone LIKE CONCAT('%', #{keyword}, '%') </when> <otherwise> AND status = 'ACTIVE' </otherwise> </choose> </where> </select>

三、Java代码中的动态构建

3.1 使用StringBuilder动态构建SQL

// 服务层代码示例 public List<User> dynamicSearch(UserSearchCriteria criteria) { StringBuilder sql = new StringBuilder("SELECT * FROM users WHERE 1=1"); List<Object> params = new ArrayList<>(); // 姓名模糊查询 if (StringUtils.isNotBlank(criteria.getName())) { sql.append(" AND username LIKE ?"); params.add("%" + criteria.getName() + "%"); } // 邮箱模糊查询 if (StringUtils.isNotBlank(criteria.getEmail())) { sql.append(" AND email LIKE ?"); params.add("%" + criteria.getEmail() + "%"); } // 分页处理 if (criteria.getPageSize() > 0) { sql.append(" LIMIT ?, ?"); params.add(criteria.getOffset()); params.add(criteria.getPageSize()); } return jdbcTemplate.query(sql.toString(), params.toArray(), new BeanPropertyRowMapper<>(User.class)); }

3.2 使用JPA Specification实现(Spring Data JPA)

// 使用Specification构建动态查询 public class UserSpecifications { public static Specification<User> nameContains(String name) { return (root, query, cb) -> StringUtils.isBlank(name) ? cb.conjunction() : cb.like(root.get("username"), "%" + name + "%"); } public static Specification<User> emailContains(String email) { return (root, query, cb) -> StringUtils.isBlank(email) ? cb.conjunction() : cb.like(root.get("email"), "%" + email + "%"); } public static Specification<User> multiFieldSearch(String keyword) { return (root, query, cb) -> { if (StringUtils.isBlank(keyword)) { return cb.conjunction(); } String pattern = "%" + keyword + "%"; return cb.or( cb.like(root.get("username"), pattern), cb.like(root.get("email"), pattern), cb.like(root.get("phone"), pattern) ); }; } } // 使用示例 public List<User> searchUsers(String name, String email) { return userRepository.findAll( Specification.where(UserSpecifications.nameContains(name)) .and(UserSpecifications.emailContains(email)) ); }

四、高级技巧与优化

4.1 防止SQL注入

// 使用预编译语句,永远不要直接拼接用户输入 String safePattern = "%" + escapeSql(keyword) + "%"; // MyBatis自动处理参数,防止SQL注入 <if test="keyword != null"> AND username LIKE CONCAT('%', #{keyword}, '%') </if>

4.2 性能优化建议

-- 为经常查询的字段创建索引 CREATE INDEX idx_username ON users(username); CREATE INDEX idx_email ON users(email); -- 避免前导通配符导致索引失效的情况 -- 不推荐:LIKE '%keyword%' -- 推荐:LIKE 'keyword%'(如果业务允许)

4.3 使用全文索引提升模糊查询性能

-- MySQL全文索引示例 ALTER TABLE users ADD FULLTEXT INDEX ft_search (username, email); -- 使用全文索引进行模糊查询 SELECT * FROM users WHERE MATCH(username, email) AGAINST('+张* +example*' IN BOOLEAN MODE);

五、实际应用场景

5.1 电商商品搜索

<select id="searchProducts" resultType="Product"> SELECT * FROM products <where> <if test="productName != null"> AND product_name LIKE CONCAT('%', #{productName}, '%') </if> <if test="categoryId != null"> AND category_id = #{categoryId} </if> <if test="minPrice != null"> AND price >= #{minPrice} </if> <if test="maxPrice != null"> AND price <= #{maxPrice} </if> <!-- 模糊搜索商品描述 --> <if test="keyword != null"> AND ( product_name LIKE CONCAT('%', #{keyword}, '%') OR description LIKE CONCAT('%', #{keyword}, '%') OR tags LIKE CONCAT('%', #{keyword}, '%') ) </if> </where> ORDER BY <choose> <when test="sortBy == 'price'">price ${sortOrder}</when> <when test="sortBy == 'sales'">sales_count DESC</when> <otherwise>create_time DESC</otherwise> </choose> </select>

5.2 日志查询系统

public List<Log> searchLogs(LogQuery query) { StringBuilder sql = new StringBuilder( "SELECT * FROM system_logs WHERE 1=1"); // 模糊匹配操作内容 if (StringUtils.isNotBlank(query.getContent())) { sql.append(" AND content LIKE ?"); params.add("%" + query.getContent() + "%"); } // 模糊匹配用户IP if (StringUtils.isNotBlank(query.getIp())) { sql.append(" AND ip_address LIKE ?"); params.add(query.getIp() + "%"); // IP前缀匹配 } // 时间范围查询 if (query.getStartTime() != null) { sql.append(" AND create_time >= ?"); params.add(query.getStartTime()); } return jdbcTemplate.query(sql.toString(), params.toArray(), new BeanPropertyRowMapper<>(Log.class)); }

六、最佳实践总结

  1. 安全性第一:始终使用参数化查询,防止SQL注入

  2. 性能优化:为频繁查询的字段建立索引,考虑使用全文搜索

  3. 代码可读性:保持SQL语句的清晰和可维护性

  4. 适度使用:避免过度复杂的动态SQL,必要时拆分查询

  5. 测试覆盖:确保各种条件组合都能正确工作

结语

动态SQL模糊查询是现代应用开发中不可或缺的技能。通过合理运用MyBatis动态标签、JPA Specification或自定义SQL构建,我们可以在保证安全性的同时,实现灵活高效的查询功能。记住,好的查询设计不仅能让程序跑得更快,也能让代码更易于维护和扩展。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/3 0:20:15

Gemini3ProImage(nano banana 2 )异步调用接口(API)生成图片

什么是 Gemini3ProImage"Nano Banana 2"&#xff1f;&#x1f9e0; 这不是一次简单的版本迭代&#xff0c;而是架构的重构。 Gemini 3 Pro Image 搭载了最新的 Nano Banana 2 核心。Nano Banana 2 引入了革命性的**“思维链&#xff08;Thinking Process&#xff09…

作者头像 李华
网站建设 2026/8/27 12:47:19

Java计算机毕设之基于springboo的小区车辆管理系统车位信息、临时车辆信息、IC卡管理(完整前后端代码+说明文档+LW,调试定制等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/9/3 0:18:12

【Vue知识点总结】Vue 路由中的 hidden: true:路由控制技巧

一、什么是 hidden: true? 在 Vue Router 中, hidden: true 是一个自定义的元信息字段(meta field),它本身不是 Vue Router 原生的配置项,而是开发团队约定俗成的一种用法。通常我们会这样配置路由: {path: /login,component: Login,meta: {hidden: true // 这里的 hi…

作者头像 李华
网站建设 2026/9/2 23:27:20

系统思考:以客户为中心

周一的学习实验室&#xff0c;有小伙伴提到“控场力”。我当下的判断是&#xff1a;控场本身并不是能力&#xff0c;而是系统良性运转后的外显结果。 顺着这个判断继续拆&#xff0c;我们发现一个关键变量反复出现——是否真正以学员为中心、以客户为中心。 这让我想到企业里那…

作者头像 李华
网站建设 2026/9/2 20:38:45

计算机Java毕设实战-基于小程序的上班企业考勤签到签退下班打卡系统设计与实现【完整源码+LW+部署说明+演示视频,全bao一条龙等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/9/2 23:07:35

书单推荐之豆包高效学习:AI时代的教育破局指南

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 &#x1f38f;&#xff1a;你只管努力&#xff0c;剩下的交给时间 &#x1f3e0; &#xff1a;小破站 书单推荐之豆包高效学习&#xff1a;AI时代的教育破局指南 为什么这本书值得每个家庭拥有&#x…

作者头像 李华