当前位置: 首页 > ops >正文

MyBatis04-MyBatis小技巧

一、#{}与${}

结论先行:

表达式名字安全性SQL 注入风险常用于底层
#{}预编译占位符✅ 安全❌ 不会参数值PreparedStatement
${}字符串拼接❌ 不安全✅ 可能表名、列名等动态 SQLStatement

#{} 是“给 SQL 填值”,${} 是“把参数原样拼到 SQL 里”。


1-1、解释:

1、#{}:预编译,占位符(推荐使用

作用:
  • 用来传递参数值

  • MyBatis 会用 ? 占位,底层通过 PreparedStatement 设置参数,自动防止 SQL 注入

示例:
<select id="getUserById" resultType="User">SELECT * FROM user WHERE id = #{id}
</select>

等价于 JDBC:

SELECT * FROM user WHERE id = ?

然后由 MyBatis 自动 set:

statement.setInt(1, id);
特点:
  • 安全!

  • 自动处理字符串加引号、类型转换等!!!!


2、${}:字符串拼接(慎用!

作用:
  • 直接把参数拼接到 SQL 字符串中

  • 没有 ? 占位,MyBatis 不处理类型、不加引号!!!

  • 很容易出现 SQL 注入漏洞

【示例1】:
<select id="getTableData" resultType="Map">SELECT * FROM ${tableName}
</select>

如果调用:

mapper.getTableData("user");

生成的 SQL 是:

SELECT * FROM user

但如果有人传入:

mapper.getTableData("user; DROP TABLE user; --");

你的数据库就炸了!

【示例2】:

要是写的是:

<select id="getUserByName" resultType="Map">SELECT * FROM user where name = ${name}
</select>

此时代码会报错:

因为代码会直接对sql语句拼接,再编译:

select * from user where name = sam

直接传入值,不会做任何的处理!

 


 

1-2、应用场景对比:

场景推荐用法示例
查询某个用户#{id}WHERE id = #{id}
动态指定表名、列名${}SELECT * FROM ${tableName}
模糊查询(like)#{} 配合 %WHERE name LIKE CONCAT('%', #{keyword}, '%')

1-3、模糊查询的正确方式:

<select id="searchUser" resultType="User">SELECT * FROM user WHERE name LIKE CONCAT('%', #{name}, '%')
</select>

或者:

SELECT * FROM user WHERE name LIKE #{name}

调用时传入:

mapper.searchUser("%Alice%");

1-4、使用 ${} 的误区

SELECT * FROM user WHERE name = '${name}'

这样写看起来像加了引号,但其实没任何防护机制。如果 name"Alice' OR 1=1 --",你就会中招。


1-5、总结:

对比点#{}${}
是否预编译✅ 是❌ 否
是否安全✅ 是❌ 否
是否防 SQL 注入✅ 是❌ 否
是否自动加引号✅ 是❌ 否
用途传参数值拼字段名、表名、排序字段等

实用建议:

80%以上的情况请用 #{},除非你明确知道你要拼的是表名、列名等结构,不是值!

二、MyBatis 中的 批量删除

2-1、常见的批量删除方式:

DELETE FROM user WHERE id IN (1, 2, 3);

我们只需要在 MyBatis 的 mapper XML 中写出动态拼接 id 列表的语句即可。


2-2、使用 <foreach> 实现批量删除

示例:批量删除用户

1、Mapper 接口

int deleteUsers(@Param("ids") List<Integer> ids);

2、对应 XML 映射

<delete id="deleteUsers">DELETE FROM user WHERE id IN<foreach collection="ids" item="id" open="(" separator="," close=")">#{id}</foreach>
</delete>

示例调用

List<Integer> ids = Arrays.asList(1, 2, 3, 4);
mapper.deleteUsers(ids);

生成的 SQL 就是:

DELETE FROM user WHERE id IN (1, 2, 3, 4)

2-3、语法说明:

属性含义
collection要遍历的集合名,对应方法参数名或 @Param("xxx")
item每次迭代的变量名
open / close括号包围,生成 ( ... )
separator每个元素之间的分隔符,如逗号

2-4、补充:支持数组

如果你传的是数组:

int deleteUsers(int[] ids);

也同样能用 <foreach collection="ids"> 遍历。


2-5、不推荐的错误方式:用 ${} 拼接

<delete id="deleteUsers">DELETE FROM user WHERE id IN (${ids})
</delete>

调用:

mapper.deleteUsers("1,2,3")

❌ 不安全、容易 SQL 注入,而且参数类型变成了字符串,控制不方便,不推荐!

三、mybatis 模糊查询

对应的模糊查询的sql:

SELECT * FROM user WHERE name LIKE '%Ali%'

3-1、在 SQL 中使用CONCAT + #{},拼接 %(推荐)

示例:

<select id="searchUser" resultType="User">SELECT * FROM user WHERE name LIKE CONCAT('%', #{name}, '%')
</select>

java调用:

mapper.searchUser("Ali"); // 实际生成 SQL: name LIKE '%Ali%'

3-2、使用 ${} 拼接模糊查询(不推荐!不安全!)

SELECT * FROM user WHERE name LIKE '%${name}%'

这样会把参数原样拼进 SQL,能执行成功!但是,可能导致SQL 注入漏洞

【注意】:

SELECT * FROM user WHERE name LIKE '%#{name}%'

这样写则会报错!

因为代码会先用?占位符代替,变为:select * from user where name like '%?%'

但是,在字符串中的?JDBC只会当做是普通的?字符串,不会填值!

3-3、推荐做法总结:

方式SQL 写法Java 传参安全性推荐
SQL 中用 CONCATLIKE CONCAT('%', #{name}, '%')"Ali"✅ 安全✅ 推荐
传参时拼好 %LIKE #{name}"%Ali%"✅ 安全✅ 也可用
直接用 ${}LIKE '%${name}%'"Ali"❌ 不安全❌ 不推荐

3-4、模糊前缀、后缀查询

类型SQL 写法
前缀匹配LIKE CONCAT(#{name}, '%') 例如:name LIKE 'Ali%'
后缀匹配LIKE CONCAT('%', #{name}) 例如:name LIKE '%Ali'
全部匹配LIKE CONCAT('%', #{name}, '%') 例如:name LIKE '%Ali%'

四、 给 Java 类起一个简短的“别名”:<typeAlias> 标签

<typeAlias> 是用来给 Java 类起一个简短的“别名”,在 MyBatis 的 XML 文件中可以直接用别名代替类的全限定名。

4-1、使用前后的对比:

1、不使用 <typeAlias>

<select id="selectByActno" resultType="com.wsbazinga.bank.pojo.Account">select * from t_act where actno = #{actno}
</select>

resultType会写的很长!

2、使用 <typeAlias> 起别名:

【注意】:

标签的位置有约束,不对的话,控制台会报错!

 

4-2、使用方法详解:

mybatis-config.xml 中配置。

1、单个别名配置:

2、一整个包下的类自动起别名:

自动起别名时,MyBatis 会默认使用 类名小写首字母形式作为别名,例如:
com.example.domain.User → 别名是 "user"(不区分大小写)

 

3、小细节:

  • alias="..." 是你起的别名,可以自己定义(区分大小写与否取决于 MyBatis 版本和配置

  • 如果你不写 alias,MyBatis 默认用类名(首字母小写)

  • 自动注册包时,只会注册普通的 JavaBean 类(带 getter/setter)

 

4、使用场景:

使用位置说明
<resultType>查询结果返回值类型
<parameterType>SQL 参数类型
<resultMap>type 属性映射对象类型

【注意】:

namespace没有别名,必须写接口的全限定名称!

5、高级补充(可选):

你还可以在类上加注解来自定义别名:

@Alias("User")
public class User {...
}

需要依赖 MyBatis 的注解支持(比如在 Spring 中使用) 

6、Spring Boot + MyBatis 项目

Spring Boot + MyBatis 项目中,确实不需要手动写 <typeAlias> 标签了——MyBatis-Spring-Boot-Starter 帮你做了这件事!

在 Spring Boot 中,你可以通过配置 + 注解,自动扫描实体类并注册别名,根本不需要手动写 <typeAlias>

(1)、方法一:使用 @MapperScan + 自动扫描别名(推荐方式

Spring Boot 的 mybatis-spring-boot-starter 支持在 application.ymlapplication.properties 中配置扫描别名的包路径:

application.yml 示例:

mybatis:type-aliases-package: com.example.domain

自动将 com.example.domain 包下所有类注册为别名,默认是类名(不区分大小写)


同时,在启动类或配置类加上扫描 Mapper 接口:

@MapperScan("com.example.mapper")
@SpringBootApplication
public class MyApp {public static void main(String[] args) {SpringApplication.run(MyApp.class, args);}
}

(2)方法二:使用注解 @Alias 给类手动指定别名(可选)

你也可以在实体类上加注解:

import org.apache.ibatis.type.Alias;@Alias("User")
public class User {private int id;private String name;// getter/setter
}

如果用了 @Alias,MyBatis 会优先使用你定义的别名。 

五、mybatis-config.xml中的<mapper>标签

<mapper> 标签的属性有三个: resourceurlclass 三种方式之一。


<mappers> 标签有三种子标签写法

<mappers><!-- 1. 使用 class 属性 --><mapper class="com.example.mapper.UserMapper"/><!-- 2. 使用 resource --><mapper resource="mapper/UserMapper.xml"/><!-- 3. 使用 url --><mapper url="file:///absolute/path/to/mapper/UserMapper.xml"/>
</mappers>
写法含义用途
class指定一个 Mapper 接口(Java 类)适用于使用注解的 Mapper 或注解+XML 双结合
resource指定一个 XML 文件路径(类路径下)适用于只有 XML 的 SQL 映射
url指定一个 XML 文件的绝对路径不推荐!主要用于外部加载,绝对路径,可扩展性差!

 


5-1、重点解释:<mapper class="...">

示例:

<mappers><mapper class="com.example.mapper.UserMapper"/>
</mappers>

含义:

  • 这是一个 Mapper 接口(Java 接口)

  • MyBatis 会根据接口名去找对应的 XML(如有)或注解(此时XML文件要和Mapper接口在同一个目录下!)


5-2、绑定规则(很重要):

当你使用 <mapper class="..."> 方式时:

1、xxxMapper接口和xxxMapper.xml文件在同一个目录下;

2、名字一致!即:carMapper接口---> carMapper.xml文件


5-3、使用场景举例:

接口 + XML(常见方式)

public interface CarMapper {Car selectById(String carNum);}
<!-- UserMapper.xml -->
<mapper namespace="com.wsbazinga.bank.dao.CarMapper"><select id="selectById" resultType="com.wsbazinga.bank.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefrom t_carwhere id = #{id}</select>
</mapper>

注册方式:

<mappers><mapper class="com.wsbazinga.bank.dao.CarMapper"/>
</mappers>

【注意】:Mapper接口和Mapper.xml文件的位置

目录的结构是一样的,只是一个是java根目录,一个是resources根目录,但是编译之后:

在同一个目录下!


提醒!!!!!!!!!!!!!!!!!!!!!!

在IDEA的resources目录下新建多重目录的话,必须是这样创建:

com/powernode/mybatis/mapper 不能这样: com.powernode.mybatis.mapper(这样是单层的)

因为resources目录下只能创建directory不能创建package!

5-4、拓展

若是有多个mapper文件,不需要每一个都写,直接使用<package>

六、Mybatis获取插入数据后自增的主键

6-1、业务场景:

6-2、实现方式:使用 useGeneratedKeyskeyProperty属性

示例:

属性含义
useGeneratedKeys="true"表示使用 JDBC 的自动主键返回功能
keyProperty="id"表示把返回的主键值赋值给 User 对象中的 id 属性

这要求你数据库表中 id 是自增主键(如 AUTO_INCREMENT) 

6-3、Mybatis-plus的实现方式

前提是你用的是 MyBatis-Plus,而不是纯 MyBatis。
@TableId(type = IdType.ASSIGN_ID) 这个注解是 MyBatis-Plus 的主键策略配置,不适用于原生 MyBatis!

这个注解用于告诉 MyBatis-Plus:这个字段是主键,且主键生成策略是什么


1、常用主键生成策略类型:

策略含义说明
AUTO数据库自增(默认)MySQL 的 AUTO_INCREMENT
INPUT手动输入主键由你自己赋值
ASSIGN_ID自动生成(雪花算法)推荐方式,默认用雪花算法生成全局唯一 ID(19位长整型)
ASSIGN_UUID自动生成 UUID用 UUID(适合主键是字符串)
NONE不设置策略通常不建议用

2、示例代码:

@Data
public class User {@TableId(type = IdType.ASSIGN_ID)private Long id;private String name;private String email;
}

然后你在使用 userMapper.insert(user) 时,MyBatis-Plus 会自动为你填充 id(不依赖数据库自增)


3、特点:@TableId 

项目@TableId(type = …)
所属框架✅ MyBatis-Plus 专属
依赖数据库主键不依赖数据库自增
是否需要数据库生成主键MyBatis-Plus自动生成
推荐使用场景分布式系统、主键冲突多的业务
http://www.xdnf.cn/news/15286.html

相关文章:

  • QT 多线程 管理串口
  • Node.js特训专栏-实战进阶:16. RBAC权限模型设计
  • 沃尔玛 卡号查询 滑块 分析
  • 深度学习图像分类数据集—角膜溃疡识别分类
  • TensorFlow深度学习实战(24)——变分自编码器详解与实现
  • spring-ai-alibaba 1.0.0.2 学习(十六)——多模态
  • IP 地址与网络基础全面解析
  • ARC 02 runner scale set chart:对接集群与 Github Action 服务器
  • 在 OCI 生成式 AI 上搭一个「指定地区拉面店 MCP Server」——从 0 到 1 实战记录
  • 基于SpringBoot3集成Kafka集群
  • CSS个人笔记分享【仅供学习交流】
  • Utils系列之内存池(MultiSizePool)
  • 电商系统未来三年趋势:体验升级、技术赋能与模式重构
  • 关于ISO 26262的Single-Point Fault/Residual Fault/Latent Fault/Dual-Point Fault的整理
  • Android 响应式编程完整指南:StateFlow、SharedFlow、LiveData 详解
  • Docker 基于 Cgroups 实现资源限制详解【实战+源码】
  • CAU数据挖掘第四章 分类问题
  • Linux修炼:开发工具
  • 软件开发中的瀑布式开发与敏捷开发
  • 2025湖北省信息安全管理与评估赛项一阶段技能书
  • 在 JetBrains 系列 IDE(如 IntelliJ IDEA、PyCharm 等)中如何新建一个 PlantUML 文件
  • 新手向:使用Python构建高效的日志处理系统
  • Llama系列:Llama1, Llama2,Llama3内容概述
  • Web攻防-PHP反序列化魔术方法触发条件POP链构造变量属性修改黑白盒角度
  • Python爬虫实战:研究xlwings库相关技术
  • Qt 3D模块加载复杂模型
  • CA复习功课
  • 前端进阶之路-从传统前端到VUE-JS(第五期-路由应用)
  • react中为啥使用剪头函数
  • 【Java入门到精通】(三)Java基础语法(下)