java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > MyBatis数据库安全与性能优化

MyBatis提升数据库安全与性能的4大实战技巧(防注入/动态SQL/模糊优化/连接池)

作者:爱吃烤鸡翅的酸菜鱼

在使用MyBatis框架进行数据库操作时,确保数据库的安全性和性能是非常重要的,下面是一些关键的实战技巧,可以帮助你提升数据库的安全性和性能

1.前言

哈喽大家好吖,【MyBatis保姆级教程】上下篇已经将主要内容讲解完毕,剩下的仍然重要的部分我将放到杂谈篇进行讲解。众所周知,MyBatis数据库操作是后端开发的基石,但你是否常被这些问题困扰?

这篇实战指南,将直击这四大核心痛点,提供可落地的解决方案。 阅读后,你将能够:

解决这些问题,你的应用将更安全、更快速、更稳定。下面进入正题:

插播一条消息~

🔍 十年经验淬炼 · 系统化AI学习平台推荐

系统化学习AI平台 https://www.captainbed.cn/scy/

为什么值得投入?

🚀 特别适合

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",数据库将其作为纯字符串处理。

✅ 优势:

2.1.2.2${} 工作原理 (即时间 SQL)

Step 1: MyBatis 直接拼接字符串:

SELECT * FROM user WHERE name = '' OR '1'='1'

Step 2: 每次执行都会生成全新的 SQL 发送到数据库。

Step 3: 数据库每次都需要重新解析、编译、执行

❌ 风险与代价:

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关键结论

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>

优势:

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>

参数说明:

动态SQL总结

场景推荐方案优势
条件查询<where> + <if>智能处理 AND/OR
动态更新<set>自动处理逗号
多路分支<choose>/<when>类似 switch-case 结构
批量操作<foreach>高效处理集合
复杂条件<trim>自定义字符串修剪
SQL 片段复用<sql> + <include>减少重复代码
动态排序<choose> 或 <foreach>安全实现 ORDER BY

最佳实践:动态 SQL 应保持简洁可读,超过 10 行的动态 SQL 建议拆分为多个方法或使用注解方式实现。

2.3like查询(模糊查询)

问题核心

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>

⚠️ 优缺点:

2.3.3 使用<bind>标签(XML专用)

<select id="findByNameLike" resultType="User">
  <bind name="pattern" value="'%' + keyword + '%'" />
  SELECT * FROM user 
  WHERE name LIKE #{pattern}
</select>

🚫 限制:

2.3.4模糊查询方案对比

方案安全性灵活性数据库兼容性推荐指数
Java层拼接通配符✅ 高✅ 高✅ 完美⭐⭐⭐⭐⭐
SQL的CONCAT函数✅ 高❌ 低❌ 各数据库不同⭐⭐
<bind>标签✅ 高⚠️ 中✅ 完美⭐⭐⭐
${}直接拼接❌ 危险✅ 高✅ 完美⛔ 禁止使用

2.4数据库连接池

2.4.1介绍

什么是数据库连接池?
数据库连接池(Connection Pool)是管理数据库连接的缓冲池技术。它在应用启动时创建一定数量的数据库连接并保持这些连接处于活动状态。当应用需要访问数据库时,直接从池中获取连接;使用完毕后将连接归还给连接池,而不是真正关闭连接。

为什么需要连接池?

连接池工作原理:

核心参数解析:

参数名说明默认值
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 配置 (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)

关键参数说明:

2.4.2.2 Druid (监控之王)

特点:

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 查看:

2.4.2.3连接池对比:HikariCP vs Druid

特性HikariCPDruid
性能⭐⭐⭐⭐⭐ (最快)⭐⭐⭐⭐ (优秀)
监控功能⭐⭐ (基础)⭐⭐⭐⭐⭐ (强大)
SQL防火墙❌ 不支持✅ 支持
加密支持❌ 不支持✅ 支持
连接泄露检测✅ 优秀✅ 优秀
配置复杂度⭐⭐ (简单)⭐⭐⭐ (较复杂)
社区活跃度⭐⭐⭐⭐ (全球活跃)⭐⭐⭐⭐ (国内活跃)
Spring Boot整合✅ 默认集成✅ 需显式配置

3.小结

掌握这四招,你数据库操作的水平绝对能上一个新台阶!安全问题刻不容缓,性能优化也永无止境。 别再让这些问题拖后腿了,现在就动手优化吧!

到此这篇关于MyBatis提升数据库安全与性能的4大实战技巧(防注入/动态SQL/模糊优化/连接池)的文章就介绍到这了,更多相关MyBatis数据库安全与性能优化内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文