MyBatis提升数据库安全与性能的4大实战技巧(防注入/动态SQL/模糊优化/连接池)
作者:爱吃烤鸡翅的酸菜鱼
1.前言
哈喽大家好吖,【MyBatis保姆级教程】上下篇已经将主要内容讲解完毕,剩下的仍然重要的部分我将放到杂谈篇进行讲解。众所周知,MyBatis数据库操作是后端开发的基石,但你是否常被这些问题困扰?
- SQL注入攻击防不胜防,数据安全岌岌可危?
- 拼接动态SQL既麻烦又危险,容易出错且不安全?
LIKE '%keyword%'模糊查询慢如蜗牛,用户体验糟糕?- 连接池配置不当,高并发下性能骤降甚至连接耗尽?
这篇实战指南,将直击这四大核心痛点,提供可落地的解决方案。 阅读后,你将能够:
- 彻底防御SQL注入: 掌握正确安全防注入的代码操作。
- 编写安全的动态SQL: 学会安全、高效地构建复杂查询条件,避免手动拼接风险。
- 显著优化模糊查询性能: 理解索引失效原因,应用实用优化技巧提升搜索速度。
- 合理配置数据库连接池: 理解关键参数含义,根据负载进行调优,保障稳定高效。
解决这些问题,你的应用将更安全、更快速、更稳定。下面进入正题:
插播一条消息~
🔍 十年经验淬炼 · 系统化AI学习平台推荐
系统化学习AI平台 https://www.captainbed.cn/scy/
✅ 为什么值得投入?
- 📚 完整知识体系:从数学基础 → 工业级项目(人脸识别/自动驾驶/GANs),内容由浅入深
- 💻 实战为王:每小节配套可运行代码案例(提供完整源码)
- 🎯 零基础友好:用生活案例讲解算法,无需担心数学/编程基础
🚀 特别适合
- 想系统补强AI知识的开发者
- 转型人工智能领域的从业者
- 需要项目经验的学生
2.正文
2.1区分${}与#{}
核心区别总结
| 特性 | #{} (预编译/占位符) | ${} (字符串替换) |
|---|---|---|
| 工作原理 | 生成 PreparedStatement,使用 ? 占位 | 直接拼接字符串到 SQL 语句中 |
| 安全性 | ✅ 高 (防止 SQL 注入) | ❌ 极低 (存在 SQL 注入风险) |
| 性能 | ✅ 高 (SQL 可被数据库缓存复用) | ❌ 低 (每次需重新解析编译 SQL) |
| 适用场景 | 参数值传递 (WHERE 值, INSERT 值等) | 动态表名/列名、ORDER BY 子句 (需严格校验) |
2.1.1SQL注入问题
场景说明
假设有一个根据用户名查询用户的功能,攻击者输入恶意参数 ' OR '1'='1 试图获取全部用户数据。
示例代码
2.1.1.1 使用 ${} (存在注入漏洞)
<!-- Mapper XML -->
<select id="findUserByNameUnsafe" resultType="User">
SELECT * FROM user WHERE name = '${name}'
</select>// Java 调用 String maliciousInput = "' OR '1'='1"; List<User> users = userMapper.findUserByNameUnsafe(maliciousInput);
最终执行的 SQL:
SELECT * FROM user WHERE name = '' OR '1'='1'
✅ 运行结果: 返回
user表中所有用户数据('1'='1'永远为真)。
💥 风险: 数据库被非法拖库!
2.1.1.2 使用 #{} (安全防注入)
<!-- Mapper XML -->
<select id="findUserByNameSafe" resultType="User">
SELECT * FROM user WHERE name = #{name}
</select>// Java 调用 String maliciousInput = "' OR '1'='1"; List<User> users = userMapper.findUserByNameSafe(maliciousInput);
最终执行的 SQL:
SELECT * FROM user WHERE name = ?
参数值: "' OR '1'='1"(作为整体字符串传入)
✅ 运行结果: 查询 name = "' OR '1'='1" 的用户 → 返回空列表。
🔒 安全: 恶意代码被当作普通字符串处理,无法改变 SQL 结构!
2.1.2预编译SQL与即时间SQL
2.1.2.1 #{} 工作原理 (预编译 SQL)
Step 1: MyBatis 解析 SQL,将 #{name} 替换为 ?。
SELECT * FROM user WHERE name = ?
Step 2: 将 SQL 发送到数据库 预编译(生成执行计划)。
Step 3: 执行时传入参数值 "' OR '1'='1",数据库将其作为纯字符串处理。
✅ 优势:
- 防 SQL 注入(参数值无法改变 SQL 结构)。
- 高性能(预编译 SQL 可被数据库缓存复用)。
2.1.2.2${} 工作原理 (即时间 SQL)
Step 1: MyBatis 直接拼接字符串:
SELECT * FROM user WHERE name = '' OR '1'='1'
Step 2: 每次执行都会生成全新的 SQL 发送到数据库。
Step 3: 数据库每次都需要重新解析、编译、执行。
❌ 风险与代价:
- 引发 SQL 注入(用户输入可改变 SQL 语义)。
- 性能低下(无法复用执行计划)。
2.1.3${} 的正确使用场景与避坑指南
2.1.3.1场景:动态排序字段 (ORDER BY)
<select id="findUsersOrdered" resultType="User">
SELECT * FROM user
ORDER BY ${sortField} ${sortOrder}
</select>2.1.3.2避坑措施:白名单校验 (Java 层)
// Service 层校验排序字段
public List<User> getUsersOrdered(String sortField, String sortOrder) {
// 定义允许的字段 (白名单)
Set<String> validFields = Set.of("id", "name", "create_time");
Set<String> validOrders = Set.of("ASC", "DESC");
if (!validFields.contains(sortField)) {
sortField = "id"; // 默认字段
}
if (!validOrders.contains(sortOrder)) {
sortOrder = "ASC"; // 默认排序
}
return userMapper.findUsersOrdered(sortField, sortOrder);
}2.1.3.3执行示例
// 正常调用
getUsersOrdered("name", "DESC");
// SQL: SELECT * FROM user ORDER BY name DESC
// 攻击尝试
getUsersOrdered("(SELECT 1 FROM DUAL); DROP TABLE user; --", "DESC");
// 白名单过滤 → SQL: SELECT * FROM user ORDER BY id ASC ✅2.1.4关键结论
#{}用于值传递:安全可靠,99% 场景使用它。${}用于 SQL 片段:仅用于动态表名、列名、排序字段(必须配合白名单校验)。- 禁止
${}拼接用户输入:否则等于敞开大门欢迎 SQL 注入攻击。 - 预编译是性能与安全的基石:永远优先选择
#{}!
2.2动态SQL
什么是动态 SQL?
动态 SQL 是 MyBatis 最强大的特性之一,它允许根据运行时条件动态构建 SQL 语句。通过条件判断、循环等逻辑,可以避免编写大量重复 SQL,提高代码复用性和灵活性。
2.2.1<if>标签
XML
<select id="findUsers" resultType="User">
SELECT * FROM user
WHERE 1=1
<if test="name != null">
AND name = #{name}
</if>
<if test="email != null">
AND email = #{email}
</if>
</select>执行示例:
// 只传 name
mapper.findUsers("Alice", null);
// SQL: SELECT * FROM user WHERE 1=1 AND name = 'Alice'
// 传 name 和 email
mapper.findUsers("Bob", "bob@example.com");
// SQL: SELECT * FROM user WHERE 1=1 AND name = 'Bob' AND email = 'bob@example.com'2.2.2<choose><when><otherwise>标签(类似switch-case)
XML
<select id="searchUsers" resultType="User">
SELECT * FROM user
WHERE
<choose>
<when test="id != null">
id = #{id}
</when>
<when test="name != null">
name LIKE CONCAT('%', #{name}, '%')
</when>
<otherwise>
status = 'ACTIVE'
</otherwise>
</choose>
</select>执行示例:
// 传 id mapper.searchUsers(1001, null, null); // SQL: SELECT * FROM user WHERE id = 1001 // 传 name mapper.searchUsers(null, "John", null); // SQL: SELECT * FROM user WHERE name LIKE '%John%' // 不传参数 mapper.searchUsers(null, null, null); // SQL: SELECT * FROM user WHERE status = 'ACTIVE'
2.2.3<where>标签
解决条件前缀问题(自动处理 AND/OR)
XML
<select id="findActiveUsers" resultType="User">
SELECT * FROM user
<where>
<if test="name != null">
name = #{name}
</if>
<if test="role != null">
AND role = #{role}
</if>
</where>
</select>优势:
- 自动删除首个条件前的 AND/OR
- 当所有条件为空时,不生成 WHERE 关键字
- 避免
WHERE 1=1这种不优雅的写法
2.2.4<set>标签
XML
<update id="updateUser">
UPDATE user
<set>
<if test="name != null">name=#{name},</if>
<if test="email != null">email=#{email},</if>
<if test="role != null">role=#{role}</if>
</set>
WHERE id=#{id}
</update>执行示例:
// 只更新 email
User user = new User();
user.setId(1001);
user.setEmail("new@example.com");
mapper.updateUser(user);
// SQL: UPDATE user SET email='new@example.com' WHERE id=1001特点: 自动去除末尾多余的逗号
2.2.5<foreach>标签
场景 1:IN 查询
XML
<select id="findUsersByIds" resultType="User">
SELECT * FROM user
WHERE id IN
<foreach item="id" collection="ids"
open="(" separator="," close=")">
#{id}
</foreach>
</select>执行示例:
List<Integer> ids = Arrays.asList(101, 102, 103); mapper.findUsersByIds(ids); // SQL: SELECT * FROM user WHERE id IN (101,102,103)
场景 2:批量插入
XML
<insert id="batchInsert">
INSERT INTO user (name, email) VALUES
<foreach item="user" collection="users" separator=",">
(#{user.name}, #{user.email})
</foreach>
</insert>执行示例:
INSERT INTO user (name, email) VALUES
('Alice','alice@test.com'),
('Bob','bob@test.com'),
('Charlie','charlie@test.com')2.2.6<trim>标签
功能: 更灵活地处理前缀/后缀
XML
<select id="dynamicSearch" resultType="User">
SELECT * FROM user
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="name != null">
AND name = #{name}
</if>
<if test="role != null">
OR role = #{role}
</if>
</trim>
</select>参数说明:
prefix:添加前缀prefixOverrides:删除指定的前缀(支持正则)suffix:添加后缀suffixOverrides:删除指定的后缀
动态SQL总结
| 场景 | 推荐方案 | 优势 |
|---|---|---|
| 条件查询 | <where> + <if> | 智能处理 AND/OR |
| 动态更新 | <set> | 自动处理逗号 |
| 多路分支 | <choose>/<when> | 类似 switch-case 结构 |
| 批量操作 | <foreach> | 高效处理集合 |
| 复杂条件 | <trim> | 自定义字符串修剪 |
| SQL 片段复用 | <sql> + <include> | 减少重复代码 |
| 动态排序 | <choose> 或 <foreach> | 安全实现 ORDER BY |
最佳实践:动态 SQL 应保持简洁可读,超过 10 行的动态 SQL 建议拆分为多个方法或使用注解方式实现。
2.3like查询(模糊查询)
问题核心
- 直接拼接导致错误:
LIKE #{value}→LIKE ?(需值为%value%) ${}有注入风险:LIKE '%${value}%'不安全
2.3.1使用拼接符
// Service层处理
public List<User> searchUsers(String keyword) {
String searchTerm = "%" + keyword + "%"; // 添加通配符
return userMapper.findByNameLike(searchTerm);
}<!-- Mapper XML -->
<select id="findByNameLike" resultType="User">
SELECT * FROM user
WHERE name LIKE #{pattern}
</select>✅ 执行结果:
searchUsers("john");
// SQL:SELECT * FROM user WHERE name LIKE '%john%'
// 返回:所有包含"john"的用户(如"john123", "xxx_john")2.3.2使用CONCAT函数
<!-- MySQL示例 -->
<select id="findByNameLike" resultType="User">
SELECT * FROM user
WHERE name LIKE CONCAT('%', #{keyword}, '%')
</select>
<!-- Oracle示例 -->
<select id="findByNameLike" resultType="User">
SELECT * FROM user
WHERE name LIKE '%' || #{keyword} || '%'
</select>⚠️ 优缺点:
- 优点:SQL意图清晰
- 缺点:数据库兼容性差(不同数据库语法不同)
2.3.3 使用<bind>标签(XML专用)
<select id="findByNameLike" resultType="User">
<bind name="pattern" value="'%' + keyword + '%'" />
SELECT * FROM user
WHERE name LIKE #{pattern}
</select>🚫 限制:
- 仅适用于XML配置
- 灵活性有限(难以实现左模糊
value%)
2.3.4模糊查询方案对比
| 方案 | 安全性 | 灵活性 | 数据库兼容性 | 推荐指数 |
|---|---|---|---|---|
| Java层拼接通配符 | ✅ 高 | ✅ 高 | ✅ 完美 | ⭐⭐⭐⭐⭐ |
| SQL的CONCAT函数 | ✅ 高 | ❌ 低 | ❌ 各数据库不同 | ⭐⭐ |
<bind>标签 | ✅ 高 | ⚠️ 中 | ✅ 完美 | ⭐⭐⭐ |
${}直接拼接 | ❌ 危险 | ✅ 高 | ✅ 完美 | ⛔ 禁止使用 |
2.4数据库连接池
2.4.1介绍
什么是数据库连接池?
数据库连接池(Connection Pool)是管理数据库连接的缓冲池技术。它在应用启动时创建一定数量的数据库连接并保持这些连接处于活动状态。当应用需要访问数据库时,直接从池中获取连接;使用完毕后将连接归还给连接池,而不是真正关闭连接。
为什么需要连接池?
- 性能优化:避免每次请求都创建新连接(TCP三次握手、数据库认证等耗时操作)
- 资源管理:控制最大连接数,防止数据库被过多连接压垮
- 连接复用:减少系统开销,提高响应速度
- 健康监测:自动检测失效连接,保证连接可用性
连接池工作原理:

核心参数解析:
| 参数名 | 说明 | 默认值 |
|---|---|---|
initialSize | 初始连接数 | 0-10 |
minIdle | 最小空闲连接数 | 0-10 |
maxActive/maxPoolSize | 关键 最大活跃连接数 | 8-100 |
maxWait | 获取连接的最大等待时间(ms) | 30000 |
validationQuery | 连接检测SQL(如SELECT 1) | - |
testWhileIdle | 是否对空闲连接进行检测 | true/false |
timeBetweenEvictionRunsMillis | 检测间隔(ms) | 60000 |
2.4.2使用
2.4.2.1HikariCP (性能王者)
特点:
- Spring Boot 2.x+ 默认连接池
- 极致轻量(130KB)
- 高并发性能最优
- 智能连接管理
Spring Boot 配置 (application.yml):
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
pool-name: MyHikariPool
maximum-pool-size: 20 # 关键配置!
minimum-idle: 5 # 推荐等于maximum-pool-size
connection-timeout: 30000 # 30秒
idle-timeout: 600000 # 10分钟
max-lifetime: 1800000 # 30分钟
connection-test-query: SELECT 1
leak-detection-threshold: 5000 # 连接泄露检测(ms)关键参数说明:
maximum-pool-size:必须根据实际负载设置(公式:CPU核心数*2 + 有效磁盘数)leak-detection-threshold:生产环境建议设置(5000-10000ms)
2.4.2.2 Druid (监控之王)
特点:
- 阿里巴巴开源
- 强大的监控功能
- SQL防火墙
- 详细统计信息
- 加密支持
Spring Boot 配置 (application.yml):
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource # 指定类型
druid:
initial-size: 5
min-idle: 5
max-active: 20 # 相当于Hikari的maximum-pool-size
max-wait: 60000 # 获取连接超时时间
filters: stat,wall,slf4j # 开启监控
# 监控配置
stat-view-servlet:
enabled: true
url-pattern: /druid/*
login-username: admin
login-password: admin
web-stat-filter:
enabled: true
url-pattern: /*
filter:
stat:
log-slow-sql: true # 记录慢SQL
slow-sql-millis: 1000 # 慢SQL阈值(ms)启用监控页面:
访问 http://localhost:8080/druid 查看:
- 数据源状态
- SQL监控
- URL监控
- Session监控
- Spring监控
2.4.2.3连接池对比:HikariCP vs Druid
| 特性 | HikariCP | Druid |
|---|---|---|
| 性能 | ⭐⭐⭐⭐⭐ (最快) | ⭐⭐⭐⭐ (优秀) |
| 监控功能 | ⭐⭐ (基础) | ⭐⭐⭐⭐⭐ (强大) |
| SQL防火墙 | ❌ 不支持 | ✅ 支持 |
| 加密支持 | ❌ 不支持 | ✅ 支持 |
| 连接泄露检测 | ✅ 优秀 | ✅ 优秀 |
| 配置复杂度 | ⭐⭐ (简单) | ⭐⭐⭐ (较复杂) |
| 社区活跃度 | ⭐⭐⭐⭐ (全球活跃) | ⭐⭐⭐⭐ (国内活跃) |
| Spring Boot整合 | ✅ 默认集成 | ✅ 需显式配置 |
3.小结
掌握这四招,你数据库操作的水平绝对能上一个新台阶!安全问题刻不容缓,性能优化也永无止境。 别再让这些问题拖后腿了,现在就动手优化吧!
到此这篇关于MyBatis提升数据库安全与性能的4大实战技巧(防注入/动态SQL/模糊优化/连接池)的文章就介绍到这了,更多相关MyBatis数据库安全与性能优化内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
