猜您喜欢::法语考研辅导班学费-法语考研辅导班收费 梦见给人接生小孩有什么预兆-梦见接生小孩预兆 唐僧法号叫什么(唐僧法号是玄奘) 双喷布多少钱一平方(双喷布每平方米价格) 扬州十大旅游景点(扬州必游十大景点) 留学生苏琪照片(留学生苏琪写真) 爷爷的爷爷从哪里来的作者是谁(爷爷的爷爷从哪里来) 吉林省中考成绩将公布(吉林中考成绩揭晓) 成都滨江东路在哪个区(成都滨江东路属锦江区) 前端培训的感悟(前端学习心得)
深入解析 MyBatis `foreach` 标签:原理、实践与避坑指南
在 MyBatis 的开发实践中,`一、 回顾:什么是 ``?
`基本语法示例
```xml ```- item:集合中当前元素的别名。
- collection:要迭代的集合名称(通常是参数名)。
- open/close:拼接后的字符串开头和结尾字符。
- separator:元素之间的分隔符。
- index:索引(对于 List 是序号,对于 Map 是 key)。
二、 核心原理:从动态 SQL 到最终 SQL
MyBatis 的核心机制是动态 SQL。`1. 解析阶段(Parsing)
当 MyBatis 启动或首次执行 SQL 时,它会读取 Mapper XML 文件,将 XML 标签转换为 `SqlNode` 对象树。- `
` 标签会被解析为 `ForEachSqlNode` 对象。 - `ForEachSqlNode` 持有配置信息:`item`、`collection`、`open`、`close`、`separator` 以及内部的 `content`(即 `#{id}` 部分)。
2. 绑定阶段(Binding)
在执行 SQL 前,MyBatis 会将 Java 方法参数绑定到上下文(`Context`)中。- 如果参数是单个对象,`collection` 会被解析为对象的属性名。
- 如果参数是 `@Param("ids") List
ids`,则直接查找名为 "ids" 的集合。 - 关键点:MyBatis 使用 `Ognl` 或反射机制来获取集合数据。
3. 拼接阶段(Dynamic SQL Application)
这是 `foreach` 最核心的逻辑所在。`ForEachSqlNode` 实现了 `SqlNode.apply(Context context)` 方法。其伪代码逻辑如下: ```java public boolean apply(Context context) { // 1. 获取集合数据 Object collection = context.getBindings().get(collectionName); // 2. 检查集合是否为空 if (collection null || (collection instanceof Collection && ((Collection>) collection).isEmpty())) { // 如果为空,根据 open/close 可能生成空字符串或报错 return true; } StringBuilder sqlBuffer = new StringBuilder(); // 3. 添加 open 字符 if (open != null) { sqlBuffer.append(open); } // 4. 遍历集合 Iterator> iterator = createIterator(collection, context); while (iterator.hasNext()) { // 5. 处理当前元素 Object item = iterator.next(); context.setBindings(item); // 将当前元素放入上下文,以便 #{item} 解析 // 6. 递归处理内部 SQL 片段(如 #{id}) content.apply(context); // 7. 获取内部片段生成的 SQL String sqlPart = sqlBuffer.toString(); // 8. 添加 separator if (separator != null && iterator.hasNext()) { sqlBuffer.append(separator); } } // 9. 添加 close 字符 if (close != null) { sqlBuffer.append(close); } // 10. 将生成的 SQL 追加到最终 SQL 中 context.appendSql(sqlBuffer.toString()); return true; } ```4. 预处理与执行
经过 `foreach` 处理后,最终的 SQL 字符串会被提交给 JDBC 的 `PreparedStatement`。- 重要原则:`#{id}` 会被转换为 `?` 占位符,并由 JDBC 驱动设置参数。
- 禁止使用 `{id}`,会导致 SQL 注入风险,且无法利用预编译性能。
三、 源码深度剖析
1. `ForEachSqlNode` 的关键方法
在 MyBatis 源码中,`org.apache.ibatis.scripting.xmltags.ForEachSqlNode` 是核心类。获取迭代器 (`createIterator`)
MyBatis 需要兼容多种集合类型:`List`、`Array`、`Map`。 ```java private Iterator> createIterator(Object collection, Context context) { if (collection instanceof List) { return ((List>) collection).iterator(); } else if (collection instanceof Collection) { return ((Collection>) collection).iterator(); } else if (collection instanceof Map) { // 对于 Map,迭代的是 Entry return ((Map, ?>) collection).entrySet().iterator(); } else { // 处理数组 return ArrayUtils.iterator(collection); } } ```上下文绑定 (`setBindings`)
在每次迭代中,MyBatis 会将当前 `item` 放入 `Context` 的绑定变量中。这使得内部表达式如 `#{id}` 能够正确解析为当前元素的值。2. 为什么推荐 `#{}` 而不是 `${}`?
| 特性 | `#{id}` | `${id}` |
|---|---|---|
| SQL 注入 | 安全(预编译参数) | 不安全(直接拼接字符串) |
| 性能 | 高(PreparedStatement 可缓存) | 低(每次需重新编译 SQL) |
| 类型转换 | 自动处理 | 无处理,直接字符串替换 |
四、 常见陷阱与最佳实践
1. 空集合处理
如果传入的 `List` 为空,`- 在 Java 层判断集合是否为空,为空时直接返回或抛出异常。
- 在 XML 中使用 `
` 标签包裹:
2. 批量插入的性能优化
`- JDBC Batch:在 `SqlSession` 或 `Executor` 中启用批量模式。
- MyBatis 配置:设置 `defaultExecutorType=BATCH`。
- 数据库支持:某些数据库(如 MySQL)支持多值插入:
3. 集合大小限制
- SQL 长度限制:大多数数据库对单条 SQL 的长度有限制(如 MySQL 默认 `max_allowed_packet`)。如果 `IN` 列表过长,可能导致 SQL 截断或报错。
- 建议:单次批量操作建议控制在 500-1000 条以内。若数据量更大,应分批处理。
4. 参数名匹配问题
`collection` 属性必须与 Java 方法参数名一致。如果使用了 `@Param` 注解,`collection` 应指向注解中的名称;如果没有注解,MyBatis 会根据参数位置自动推断(如 `param1`, `param2`),但这不够直观,强烈建议使用 `@Param`。五、 总结
`文章版权声明:除非注明,否则均为
静秋号原理 原创文章,转载或复制请以超链接形式并注明出处。