MyBatis04-MyBatis小技巧 一、#{}与${}结论先行表达式名字安全性SQL 注入风险常用于底层#{}预编译占位符✅ 安全❌ 不会参数值PreparedStatement${}字符串拼接❌ 不安全✅ 可能表名、列名等动态 SQLStatement#{}是“给 SQL填值”${}是“把参数原样拼到 SQL 里”。1-1、解释1、#{}预编译占位符推荐使用作用用来传递参数值MyBatis 会用?占位底层通过PreparedStatement设置参数自动防止 SQL 注入。示例select idgetUserById resultTypeUser SELECT * FROM user WHERE id #{id} /select等价于 JDBCSELECT * FROM user WHERE id ?然后由 MyBatis 自动 setstatement.setInt(1, id);特点安全自动处理字符串加引号、类型转换等2、${}字符串拼接慎用作用直接把参数拼接到 SQL 字符串中没有?占位MyBatis不处理类型、不加引号很容易出现SQL 注入漏洞【示例1】sql注入漏洞select idgetTableData resultTypeMap SELECT * FROM ${tableName} /select如果调用mapper.getTableData(user);生成的 SQL 是SELECT * FROM user但如果有人传入mapper.getTableData(user; DROP TABLE user; --);你的数据库就炸了【示例2】字符串参数要是写的是select idgetUserByName resultTypeMap SELECT * FROM user where name ${name} /select此时代码会报错因为代码会直接对sql语句拼接再编译select * from user where name sam直接传入值不会做任何的处理因为sam没有引号数据库认为它是一个列名或关键字不是字符串。【示例3】不处理类型假设传入birthday new Date()用#{}SELECT * FROM user WHERE birthday ? -- 预编译 -- MyBatis 自动把 Date 转成数据库认识的日期格式用${}SELECT * FROM user WHERE birthday Wed Jul 15 22:33:00 CST 2026 -- ❌ 直接调用 toString() 拼进去数据库根本不认识这个格式这就是不处理类型#{}会根据字段类型Date/String/Integer做合适的转换${}就是简单粗暴的字符串拼接不管原来是什么类型。1-2、应用场景对比场景推荐用法示例查询某个用户#{id}WHERE id #{id}动态指定表名、列名${}SELECT * FROM ${tableName}模糊查询like#{}配合%WHERE name LIKECONCAT(%, #{keyword}, %)1-3、模糊查询的正确方式select idsearchUser resultTypeUser 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 11 --你就会中招。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) ListInteger ids);2、对应 XML 映射delete iddeleteUsers DELETE FROM user WHERE id IN foreach collectionids itemid open( separator, close) #{id} /foreach /delete示例调用ListInteger 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 collectionids遍历。2-5、不推荐的错误方式用${}拼接delete iddeleteUsers DELETE FROM user WHERE id IN (${ids}) /delete调用mapper.deleteUsers(1,2,3)❌ 不安全、容易 SQL 注入而且参数类型变成了字符串控制不方便不推荐三、mybatis 模糊查询对应的模糊查询的sqlSELECT * FROM user WHERE name LIKE %Ali%3-1、在 SQL 中使用CONCAT #{}拼接%推荐示例select idsearchUser resultTypeUser SELECT * FROM user WHERE name LIKE CONCAT(%, #{name}, %) /selectjava调用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、不使用typeAliasselect idselectByActno resultTypecom.wsbazinga.bank.pojo.Account select * from t_act where actno #{actno} /selectresultType会写的很长2、使用typeAlias起别名【注意】标签的位置有约束不对的话控制台会报错4-2、使用方法详解在mybatis-config.xml中配置1、单个别名配置2、一整个包下的类自动起别名自动起别名时MyBatis 会默认使用类名的小写首字母形式作为别名例如com.example.domain.User→ 别名是user不区分大小写3、小细节alias...是你起的别名可以自己定义区分大小写与否取决于 MyBatis 版本和配置如果你不写aliasMyBatis 默认用类名首字母小写自动注册包时只会注册普通的 JavaBean 类带 getter/setter4、使用场景使用位置说明resultType查询结果返回值类型parameterTypeSQL 参数类型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.yml或application.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 }如果用了AliasMyBatis 会优先使用你定义的别名。五、mybatis-config.xml中的mapper标签mapper标签的属性有三个resource、url或class三种方式之一。mappers标签有三种子标签写法mappers !-- 1. 使用 class 属性 -- mapper classcom.example.mapper.UserMapper/ !-- 2. 使用 resource -- mapper resourcemapper/UserMapper.xml/ !-- 3. 使用 url -- mapper urlfile:///absolute/path/to/mapper/UserMapper.xml/ /mappers写法含义用途class指定一个Mapper 接口Java 类适用于使用注解的 Mapper 或注解XML 双结合resource指定一个 XML 文件路径类路径下适用于只有 XML 的 SQL 映射url指定一个 XML 文件的绝对路径不推荐主要用于外部加载绝对路径可扩展性差5-1、重点解释mapper class...示例mappers mapper classcom.example.mapper.UserMapper/ /mappers含义这是一个Mapper 接口Java 接口MyBatis 会根据接口名去找对应的XML如有或注解此时XML文件要和Mapper接口在同一个目录下5-2、绑定规则很重要当你使用mapperclass...方式时1、xxxMapper接口和xxxMapper.xml文件编译后在同一个目录下2、名字一致即carMapper接口--- carMapper.xml文件5-3、使用场景举例接口 XML常见方式public interface CarMapper { Car selectById(String carNum); }!-- UserMapper.xml -- mapper namespacecom.wsbazinga.bank.dao.CarMapper select idselectById resultTypecom.wsbazinga.bank.pojo.Car select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where id #{id} /select /mapper注册方式mappers mapper classcom.wsbazinga.bank.dao.CarMapper/ /mappers【注意】Mapper接口和Mapper.xml文件的位置目录的结构是一样的只是一个是java根目录一个是resources根目录但是编译之后在同一个目录下【提醒】在IDEA的resources目录下新建多重目录的话必须是这样创建com/powernode/mybatis/mapper不能这样 com.powernode.mybatis.mapper这样是单层的因为resources目录下只能创建directory不能创建package5-4、拓展若是有多个mapper文件不需要每一个都写直接使用package这个name填的是Mapper 接口Java 类所在的包路径不是 XML 文件的路径。因为 MyBatis 有个约定如果 Mapper 接口在com.wsbazinga.bank.dao包下它会自动去 classpath 下找com/wsbazinga/bank/dao/这个路径下的同名 XML 文件。java 目录下 com/wsbazinga/bank/dao/ └── UserMapper.java ← 接口 resources 目录下 com/wsbazinga/bank/dao/ └── UserMapper.xml ← XML和接口同路径package扫描的是接口XML 是靠同路径同名自动关联上的。MyBatis 扫描这个包找到所有接口然后再根据接口的全限定名去 classpath 下找对应的 XML 文件。它不负责直接扫描 XML 文件。两种项目结构的区别① 传统 MyBatis非 Spring BootXML 文件通常放在java目录下和接口同包src/main/java/ └── com/wsbazinga/bank/dao/ ├── UserMapper.java └── UserMapper.xml此时mybatis-config.xml里mappers package namecom.wsbazinga.bank.dao/ !-- 扫描这个包下的所有接口 -- /mappers② Spring Boot 项目XML 文件通常放在resources目录下src/main/java/ └── com/wsbazinga/bank/dao/ └── UserMapper.java src/main/resources/ └── mapper/ ← XML 单独放 └── UserMapper.xml此时 Spring Boot 的application.yml里要额外指定 XML 位置mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.wsbazinga.bank.dao而 Mapper 接口上的MapperScan(com.wsbazinga.bank.dao)或在启动类上加MapperScan才是起package类似的作用。六、Mybatis获取插入数据后自增的主键6-1、业务场景6-2、实现方式使用useGeneratedKeys和keyProperty属性示例属性含义useGeneratedKeystrue表示使用 JDBC 的自动主键返回功能keyPropertyid表示把返回的主键值赋值给User对象中的id属性这要求你数据库表中id是自增主键如AUTO_INCREMENT6-3、Mybatis-plus的实现方式前提是你用的是 MyBatis-Plus而不是纯 MyBatis。TableId(type IdType.ASSIGN_ID)这个注解是MyBatis-Plus 的主键策略配置不适用于原生 MyBatis这个注解用于告诉 MyBatis-Plus这个字段是主键且主键生成策略是什么。1、常用主键生成策略类型策略含义说明AUTO数据库自增默认MySQL 的 AUTO_INCREMENTINPUT手动输入主键由你自己赋值ASSIGN_ID自动生成雪花算法推荐方式默认用雪花算法生成全局唯一 ID19位长整型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自动生成推荐使用场景分布式系统、主键冲突多的业务七、Directory VS Package1. Directory目录是什么就是普通的文件夹和你在电脑上右键新建文件夹一模一样。我的电脑 ├── 学习资料 ├── 游戏 └── 照片在 IDE如 IDEA里你右键 → New → Directory创建的就是一个空文件夹里面可以放任何文件。2. Package包是什么Package 是Java 代码的命名空间它看起来是文件夹但本质是给 Java 类分类的。代码里的体现package com.example.service; // ← 这一行就是包声明 public class UserService { }这个package com.example.service告诉 Java这个类属于com.example.service这个包。文件夹层面的体现为了配合上面的包声明你的文件夹结构必须是src/main/java/ └── com/ └── example/ └── service/ └── UserService.java关键点com/example/service这三层文件夹是因为代码里写了package com.example.service才存在的。3. 核心区别Directory目录Package包本质普通文件夹带有 Java 包语义的文件夹代码关联和代码无关必须和package声明一致能放什么任何文件.xml、.properties、图片主要放.java源代码编译后原样复制到 target按包结构生成.class文件创建方式New → DirectoryNew → Package4. 为什么 resources 下只能创建 Directory先看项目标准结构src/main/ ├── java/ ← 放 Java 源代码 │ └── com/ │ └── example/ │ └── service/ │ └── UserService.java // 代码里有 package com.example.service; │ └── resources/ ← 放配置文件、静态资源 ├── mapper/ │ └── UserMapper.xml ├── application.yml └── static/ └── logo.png原因resources 里的东西不需要packageapplication.yml、UserMapper.xml、logo.png这些文件没有package概念它们就是普通的配置文件/资源文件编译时Maven/Gradle 直接把 resources 里的内容原样复制到target/classes/目录下所以 resources 下你只需要创建普通文件夹Directory来组织文件即可。5. 为什么 java 目录下可以创建 Package因为 Java 代码必须有 package 声明package com.example.service; // 包声明 public class UserService { }IDE 创建 Package 时实际上也是创建文件夹但会强制和代码里的 package 声明对应。如果你在 IDEA 里右键 java 目录 → New → Package输入com.example.serviceIDE 会帮你创建三层文件夹com/example/service。6. 一句话总结Directory 就是普通文件夹用来放任何文件Package 是给 Java 代码分类的特殊文件夹必须和代码里的package声明一致。resources下放的是配置文件没有 package 概念所以只能用 Directory。7. 补充IDEA 里的视觉区别在 IDEA 中src/main/java └── com.example.service ← 中间是点这是 Package包 └── UserService.java src/main/resources └── mapper ← 没有点这是 Directory目录 └── UserMapper.xmlPackage 显示时用com.example.service带点Directory 显示时用mapper就是一个文件夹名如果你在 resources 下输入com.example.mapperIDEA 会问你Create asPackageorDirectory?在 resources 下必须选 Directory否则它会按包结构创建com/example/mapper三层文件夹虽然也能用但容易让人误解。