@Delete
导学
本节学习目标:
- 掌握
@Delete注解的基本用法,能够替代 XML 中的<delete>标签完成数据删除 - 理解
@Delete返回影响行数的业务意义,学会利用返回值判断删除是否成功 - 明确注解删除与 XML 删除在 SQL 编写和参数绑定上的等价性
- 了解批量删除在注解模式下的实现方式
定义
@Delete 是 MyBatis 提供的核心删除注解,标注在 Mapper 接口的方法上,用于声明 DELETE SQL 语句。它对应 XML 映射文件中的 <delete> 元素。
痛点解决:在纯注解开发中,开发者无需维护 XML 即可声明删除逻辑。对于按主键删除、按条件删除等常见场景,@Delete 一行注解即可完成映射;其返回的 int 影响行数可直接用于判断记录是否真实存在并被删除,是业务层做存在性校验的便捷手段。
注解方式 vs XML 方式对比
| 对比维度 | @Delete 注解方式 | XML <delete> 方式 |
|---|---|---|
| 单条删除 | 一行注解,简洁 | 需写 XML 标签,相对繁琐 |
| 批量删除 | 需借助 <script> 或 Provider | 原生支持 <foreach>,更直观 |
| 影响行数获取 | 接口返回 int 即可 | 同样返回 int,行为一致 |
| 维护性 | 短 SQL 集中管理 | 长 SQL、动态条件更易维护 |
适用场景建议:按主键或简单条件删除优先使用
@Delete;涉及复杂动态条件、批量删除大量数据时,建议使用 XML 的<foreach>或@DeleteProvider。
适用位置与核心属性
@Delete 只能标注在 Mapper 接口的方法 上。
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
value | String | 是 | 要执行的 DELETE SQL 语句,支持 #{} 引用参数 |
核心原理
MyBatis 解析 @Delete 后注册为 MappedStatement,语句类型标记为 DELETE。执行时,MyBatis 将 #{} 替换为预编译参数,调用 JDBC 的 Statement.executeUpdate()。数据库返回的影响行数会被 MyBatis 直接透传给 Mapper 接口的返回值(声明为 int 或 Integer)。
完整示例
场景说明
乐途公司学生管理系统需要实现:根据学生 ID 删除指定学生;同时支持批量删除多名学生。需要明确知道删除操作是否真正影响了数据。
操作前的数据库表结构及初始数据
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20),
age INT,
major VARCHAR(20),
score DECIMAL(5,2)
);
INSERT INTO student (name, age, major, score) VALUES
('大翔', 22, '计算机科学', 95.5),
('白歌', 21, '软件工程', 88.0),
('小崔', 20, '计算机科学', 92.0),
('黄俪', 21, '信息安全', 90.5),
('李眉', 22, '软件工程', 87.0);
当前数据状态:
| id | name | age | major | score |
|---|---|---|---|---|
| 1 | 大翔 | 22 | 计算机科学 | 95.50 |
| 2 | 白歌 | 21 | 软件工程 | 88.00 |
| 3 | 小崔 | 20 | 计算机科学 | 92.00 |
| 4 | 黄俪 | 21 | 信息安全 | 90.50 |
| 5 | 李眉 | 22 | 软件工程 | 87.00 |
完整的注解接口代码
实体类
package com.flying.entity;
public class Student {
private Integer id;
private String name;
private Integer age;
private String major;
private Double score;
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public String getMajor() { return major; }
public void setMajor(String major) { this.major = major; }
public Double getScore() { return score; }
public void setScore(Double score) { this.score = score; }
}
Mapper 接口
package com.flying.mapper;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface StudentMapper {
/**
* 根据 ID 删除单个学生
* 返回影响行数
*/
@Delete("DELETE FROM student WHERE id = #{id}")
int deleteById(Integer id);
/**
* 批量删除学生
* 使用 <script> 标签包裹 XML 动态 SQL 的 <foreach>
*/
@Delete("<script>" +
"DELETE FROM student WHERE id IN " +
"<foreach collection='ids' item='id' open='(' separator=',' close=')'>" +
"#{id}" +
"</foreach>" +
"</script>")
int batchDeleteByIds(@Param("ids") List<Integer> ids);
}
测试调用代码
package com.flying.test;
import com.flying.mapper.StudentMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
public class DeleteTest {
public static void main(String[] args) throws Exception {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
SqlSession session = factory.openSession();
StudentMapper mapper = session.getMapper(StudentMapper.class);
// 1. 单条删除:删除 ID=3 的学生(小崔)
System.out.println("=== 单条删除 ===");
int rows1 = mapper.deleteById(3);
System.out.println("影响行数: " + rows1);
if (rows1 == 0) {
System.out.println("警告:该学生不存在,删除失败!");
} else {
System.out.println("成功删除 ID=3 的学生");
}
// 2. 批量删除:删除 ID=1 和 ID=5 的学生(大翔、李眉)
System.out.println("\n=== 批量删除 ===");
List<Integer> ids = Arrays.asList(1, 5);
int rows2 = mapper.batchDeleteByIds(ids);
System.out.println("影响行数: " + rows2);
// 3. 删除一个不存在的 ID,验证影响行数为 0
System.out.println("\n=== 删除不存在的学生 ===");
int rows3 = mapper.deleteById(999);
System.out.println("影响行数: " + rows3);
if (rows3 == 0) {
System.out.println("提示:ID=999 的学生不存在");
}
session.commit();
session.close();
}
}
实际执行结果
控制台 SQL 输出
=== 单条删除 ===
[main] DEBUG com.flying.mapper.StudentMapper.deleteById - ==> Preparing: DELETE FROM student WHERE id = ?
[main] DEBUG com.flying.mapper.StudentMapper.deleteById - ==> Parameters: 3(Integer)
[main] DEBUG com.flying.mapper.StudentMapper.deleteById - <== Updates: 1
影响行数: 1
成功删除 ID=3 的学生
=== 批量删除 ===
[main] DEBUG com.flying.mapper.StudentMapper.batchDeleteByIds - ==> Preparing: DELETE FROM student WHERE id IN ( ? , ? )
[main] DEBUG com.flying.mapper.StudentMapper.batchDeleteByIds - ==> Parameters: 1(Integer), 5(Integer)
[main] DEBUG com.flying.mapper.StudentMapper.batchDeleteByIds - <== Updates: 2
影响行数: 2
=== 删除不存在的学生 ===
[main] DEBUG com.flying.mapper.StudentMapper.deleteById - ==> Preparing: DELETE FROM student WHERE id = ?
[main] DEBUG com.flying.mapper.StudentMapper.deleteById - ==> Parameters: 999(Integer)
[main] DEBUG com.flying.mapper.StudentMapper.deleteById - <== Updates: 0
影响行数: 0
提示:ID=999 的学生不存在
删除后数据状态
| id | name | age | major | score |
|---|---|---|---|---|
| 2 | 白歌 | 21 | 软件工程 | 88.00 |
| 4 | 黄俪 | 21 | 信息安全 | 90.50 |
分析
@Delete的返回值直接反映数据库实际删除的行数,这是做存在性校验的天然手段- 单条删除使用
#{id}预编译参数,安全且高效 - 批量删除借助
<script>标签在注解中嵌入 XML 动态 SQL,<foreach>遍历集合生成IN子句,这是注解模式下实现批量删除的主流做法 - 删除操作属于写操作,测试代码中必须调用
session.commit(),否则数据不会持久化到数据库
易错场景 / 常见误区
| 误区 | 错误示例 | 正解 |
|---|---|---|
| 删除后忘记提交事务 | 无 session.commit() | 写操作必须手动提交或设置自动提交 |
| 忽略影响行数为 0 的情况 | 不判断返回值 | 根据业务需要,返回 0 时应提示"记录不存在"或"已删除" |
| 批量删除时参数未加 @Param | int batchDeleteByIds(List<Integer> ids) | 写成 int batchDeleteByIds(@Param("ids") List<Integer> ids) |
| WHERE 条件漏写 | DELETE FROM student | 必须带 WHERE 条件,否则全表删除 |
面试考点
Q1:@Delete 返回的 int 代表什么?在什么场景下会返回 0?
返回的是数据库实际受影响的数据行数。返回 0 表示 WHERE 条件匹配不到任何记录,即要删除的数据不存在或已被其他事务删除。
Q2:注解方式下如何实现批量删除?
常见两种:(1) 在
@Delete中使用<script>标签包裹<foreach>生成IN子句;(2) 使用@DeleteProvider在 Java 代码中动态拼接批量删除 SQL。
Q3:@Delete 和 XML 的 <delete> 在事务行为上有区别吗?
没有区别。两者最终都生成 MappedStatement,由同一个 SqlSession 和 Executor 执行,事务边界由 SqlSession 控制,与注解或 XML 的声明方式无关。
Q4:MyBatis 删除时如何防止全表删除?
在业务层或拦截层对 DELETE SQL 进行校验,确保包含 WHERE 子句;MyBatis 本身不提供强制防全表删除机制,但可通过自定义拦截器在解析 SQL 后检查是否缺少 WHERE。生产环境中建议启用 SQL 审计或只读副本进行防护。
小结
@Delete 是注解开发中完成数据删除的核心注解。它用法简单,与 XML 的 <delete> 在底层完全等价。开发者应重点关注影响行数的业务含义,以及多参数场景下的 @Param 命名规范。对于需要批量删除的场景,通过 <script> 标签引入 <foreach> 是注解开发中的常用技巧。
下一章引子
删除和查询之后,我们经常会遇到数据库列名与 Java 属性名不一致的情况。@Results 和 @Result 注解负责替代 XML 的 <resultMap> 完成自定义结果映射。下一节将先详细讲解 @Results 的用法。