@Insert
导学
本节学习目标:
- 掌握
@Insert注解的基本用法,能够替代 XML 中的<insert>标签完成数据插入 - 学会配合
@Options实现自增主键的自动回填 - 理解批量插入在注解模式下的实现方式(
<script>标签配合<foreach>) - 明确注解插入与 XML 插入在事务控制和主键获取上的行为一致性
定义
@Insert 是 MyBatis 提供的核心插入注解,标注在 Mapper 接口的方法上,用于声明插入语句。它对应 XML 映射文件中的 <insert> 元素。
痛点解决:在纯注解开发中,开发者无需编写 XML 即可声明 INSERT SQL,同时通过 @Options 或 @SelectKey 解决主键回填这一插入场景的核心诉求,使接口文件成为插入操作的唯一维护点。
注解方式 vs XML 方式对比
| 对比维度 | @Insert 注解方式 | XML <insert> 方式 |
|---|---|---|
| 主键回填 | 需额外加 @Options 或 @SelectKey | 直接在 <insert> 标签内写 useGeneratedKeys |
| 批量插入 | 需用 <script> 标签写 <foreach> | 原生支持 <foreach>,更直观 |
| 代码集中度 | SQL、主键策略、方法签名在一处 | SQL 与接口分离,策略分散在 XML 属性中 |
| 维护成本 | 单文件维护,适合简单插入 | 适合需要频繁调整字段映射的复杂插入 |
适用场景建议:单条插入且需要主键回填时,
@Insert + @Options非常简洁;批量插入或插入逻辑极复杂时,建议评估是否使用 XML 或@InsertProvider。
适用位置与核心属性
@Insert 只能标注在 Mapper 接口的方法 上。
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
value | String | 是 | 要执行的 INSERT SQL 语句,支持 #{} 引用传入对象的属性 |
核心原理
MyBatis 解析到 @Insert 后,将其注册为 MappedStatement,语句类型标记为 INSERT。执行时,MyBatis 先通过反射提取参数对象的属性值,绑定到 SQL 的 #{} 占位符;JDBC 执行插入后,如果配置了 @Options(useGeneratedKeys = true),MyBatis 会调用 Statement.getGeneratedKeys() 获取数据库生成的主键,并反射回填到参数对象的指定属性中。
完整示例
场景说明
乐途公司学生管理系统需要实现:新增一名学生并自动获取自增主键;同时支持一次性批量导入多名学生。
操作前的数据库表结构及初始数据
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;
// Getter 和 Setter
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 com.flying.entity.Student;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface StudentMapper {
/**
* 插入单条记录,并获取自增主键回填到对象中
*/
@Insert("INSERT INTO student(name, age, major, score) VALUES(#{name}, #{age}, #{major}, #{score})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertStudent(Student student);
/**
* 批量插入学生
* 使用 <script> 标签包裹 XML 动态 SQL 的 <foreach>
*/
@Insert("<script>" +
"INSERT INTO student(name, age, major, score) VALUES " +
"<foreach collection='list' item='item' separator=','>" +
"(#{item.name}, #{item.age}, #{item.major}, #{item.score})" +
"</foreach>" +
"</script>")
int batchInsert(@Param("list") List<Student> students);
}
测试调用代码
package com.flying.test;
import com.flying.entity.Student;
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.ArrayList;
import java.util.List;
public class InsertTest {
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. 单条插入并获取主键
System.out.println("=== 单条插入 ===");
Student newStu = new Student();
newStu.setName("赵新");
newStu.setAge(23);
newStu.setMajor("人工智能");
newStu.setScore(91.0);
int rows = mapper.insertStudent(newStu);
System.out.println("影响行数: " + rows);
System.out.println("回填主键 id: " + newStu.getId());
// 2. 批量插入
System.out.println("\n=== 批量插入 ===");
List<Student> batch = new ArrayList<>();
Student s1 = new Student(); s1.setName("钱多多"); s1.setAge(20); s1.setMajor("网络工程"); s1.setScore(85.0);
Student s2 = new Student(); s2.setName("孙小空"); s2.setAge(21); s2.setMajor("网络工程"); s2.setScore(89.5);
batch.add(s1);
batch.add(s2);
int batchRows = mapper.batchInsert(batch);
System.out.println("影响行数: " + batchRows);
session.commit();
session.close();
}
}
实际执行结果
控制台 SQL 输出
=== 单条插入 ===
[main] DEBUG com.flying.mapper.StudentMapper.insertStudent - ==> Preparing: INSERT INTO student(name, age, major, score) VALUES(?, ?, ?, ?)
[main] DEBUG com.flying.mapper.StudentMapper.insertStudent - ==> Parameters: 赵新(String), 23(Integer), 人工智能(String), 91.0(Double)
[main] DEBUG com.flying.mapper.StudentMapper.insertStudent - <== Updates: 1
影响行数: 1
回填主键 id: 6
=== 批量插入 ===
[main] DEBUG com.flying.mapper.StudentMapper.batchInsert - ==> Preparing: INSERT INTO student(name, age, major, score) VALUES (?, ?, ?, ?) , (?, ?, ?, ?)
[main] DEBUG com.flying.mapper.StudentMapper.batchInsert - ==> Parameters: 钱多多(String), 20(Integer), 网络工程(String), 85.0(Double), 孙小空(String), 21(Integer), 网络工程(String), 89.5(Double)
[main] DEBUG com.flying.mapper.StudentMapper.batchInsert - <== Updates: 2
影响行数: 2
插入后数据状态
| 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 |
| 6 | 赵新 | 23 | 人工智能 | 91.00 |
| 7 | 钱多多 | 20 | 网络工程 | 85.00 |
| 8 | 孙小空 | 21 | 网络工程 | 89.50 |
分析
@Options(useGeneratedKeys = true, keyProperty = "id")是 MySQL 自增主键回填的标准配置,执行后student.getId()从null变为6- 批量插入借助
<script>标签在注解中嵌入 XML 动态 SQL,<foreach>遍历集合生成多组VALUES,这是注解模式下实现批量的主流做法 - 批量插入时务必使用
@Param("list")明确参数名,否则 MyBatis 默认以list或collection作为键,在部分 JDK 版本下可能因参数名丢失而找不到集合
易错场景 / 常见误区
| 误区 | 错误示例 | 正解 |
|---|---|---|
忘记加 @Options 导致主键未回填 | 只写 @Insert | 增加 @Options(useGeneratedKeys = true, keyProperty = "id") |
keyProperty 写错属性名 | keyProperty = "studentId" | 必须与实体类属性名一致,如 id |
批量插入时参数未加 @Param | int batchInsert(List<Student> list) | 写成 int batchInsert(@Param("list") List<Student> list) |
| 批量插入后忘记提交事务 | 无 session.commit() | 插入属于写操作,必须手动 commit 或开启自动提交 |
面试考点
Q1:@Insert 如何获取数据库自增主键?
配合
@Options(useGeneratedKeys = true, keyProperty = "id")使用。MyBatis 在执行 INSERT 后调用 JDBC 的getGeneratedKeys()获取主键,并通过反射回填到传入实体对象的id属性中。
Q2:注解方式下批量插入有哪些实现方式?
常见三种:(1) 在
@Insert中使用<script>标签包裹<foreach>生成多条 VALUES;(2) 使用@InsertProvider在 Java 代码中拼接批量 SQL;(3) 借助 MySQL 的rewriteBatchedStatements=true配合循环单条插入提升性能。
Q3:useGeneratedKeys 对非自增主键的数据库有效吗?
无效。
useGeneratedKeys依赖数据库的自动生成键能力(如 MySQL 自增、SQL Server Identity)。对于 Oracle 序列等场景,应使用@SelectKey在插入前或插入后查询主键。
Q4:为什么批量插入时建议加 @Param?
JDK 8 之前编译默认不带参数名信息,MyBatis 无法通过反射获取形参名。加
@Param("list")后,MyBatis 明确知道集合在参数 Map 中的 key 是list,<foreach collection='list'>才能正确解析。
小结
@Insert 让插入操作在注解模式下变得简洁直观,配合 @Options 可一站式解决主键回填问题。对于批量场景,通过 <script> 标签引入 <foreach> 是注解开发中的常用技巧。需要更复杂的插入逻辑(如多表插入、动态字段)时,可继续学习 @InsertProvider 或评估 XML 方案。
下一章引子
插入之后往往是数据修正。@Update 注解负责替代 XML 的 <update> 完成数据更新,并且可以直接返回影响行数用于业务校验。下一节将详细讲解 @Update 的用法。