加入 Maven 依赖:MySQL 驱动 + MyBatis
在 Spring Boot 项目中,使用 MyBatis 操作 MySQL 数据库,首先需要在 pom.xml 文件中引入相关依赖。以下是核心依赖配置:
xml
<!-- MySQL 驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- MyBatis Spring Boot Starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
依赖说明:
mysql-connector-j:MySQL 官方提供的 JDBC 驱动,用于 Java 程序连接 MySQL 数据库。scope设置为runtime,表示仅在运行时需要,编译时不需要。mybatis-spring-boot-starter:MyBatis 官方为 Spring Boot 提供的起步依赖,它会自动配置SqlSessionFactory、SqlSessionTemplate等核心组件,简化集成过程。版本号建议与 Spring Boot 版本匹配。
添加依赖后,刷新 Maven 项目(IDE 中点击刷新按钮或执行 mvn clean install),等待依赖下载完成即可。
配置数据库连接
yml 配置
在 application.yml 中配置 MySQL 数据源连接信息:
yml
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
username: 数据库账号
password: 数据库密码
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 配置打印 MyBatis日志
map-underscore-to-camel-case: true #配置驼峰自动转换
准备数据库表
在 MySQL 中执行以下 SQL,创建测试数据库和用户表:
sql
-- 创建数据库
DROP DATABASE IF EXISTS mybatis_test;
CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;
-- 使用数据库
USE mybatis_test;
-- =============================================
-- 表名: user_info
-- 描述: 用户基础信息表,存储系统用户的核心数据
-- 注意事项:
-- 1. 密码应存储加密后的密文,VARCHAR(50)根据加密算法调整
-- 2. username 建议添加唯一索引,保证用户唯一
-- 3. 性别采用 TINYINT 存储,节省空间且含义清晰
-- 4. 默认字符集 utf8mb4,兼容emoji等特殊字符
-- 5. 自动维护 create_time 和 update_time,减少应用层负担
-- =============================================
DROP TABLE IF EXISTS user_info;
CREATE TABLE `user_info` (
`id` INT NOT NULL AUTO_INCREMENT COMMENT '主键ID,自增整数,作为用户唯一物理标识',
`username` VARCHAR(50) NOT NULL COMMENT '用户名,登录凭证,实际使用时应添加唯一索引',
`password` VARCHAR(50) NOT NULL COMMENT '加密后的密码,根据加密算法调整长度(如使用bcrypt建议VARCHAR(255))',
`age` INT DEFAULT NULL COMMENT '年龄,允许空值,便于隐私保护或未知场景',
`gender` TINYINT DEFAULT '0' COMMENT '性别:1-男,2-女,0-未知。使用TINYINT节省存储',
`phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号,格式如13800138000,允许空值',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间,由数据库自动填充',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间,每次修改行时自动更新',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户信息表';
-- 添加测试数据
INSERT INTO user_info (username, `password`, age, gender, phone) VALUES
('admin', 'admin', 18, 1, '18612340001'),
('zhangsan', 'zhangsan', 20, 1, '18612340002'),
('lisi', 'lisi', 22, 2, '18612340003');
创建实体类 UserInfo
java
@Data
public class UserInfo {
private Integer id;
private String username;
private String password;
private Integer age;
private Integer gender;
private String phone;
private Date createTime;
private Date updateTime;
}
参数传递
1. 单参数传递
当 Mapper 方法只有一个参数时,#{} 中的名称可以任意指定,MyBatis 会自动匹配:
java
@Select("SELECT * FROM user_info WHERE id = #{任意名称}")
UserInfo selectById(Integer id);
java
@Select("SELECT * FROM user_info WHERE id = #{aa}")
UserInfo selectById(Integer id);
两种写法都能正确执行,因为单参数时 MyBatis 不关心 #{} 中的名字。
2. 多参数传递
当 Mapper 方法有多个参数时,MyBatis 会按参数顺序自动命名:arg0、arg1、arg2... 或 param1、param2、param3...
java
@Select("SELECT * FROM user_info WHERE username = #{param1} AND password = #{param2}")
List<UserInfo> selectByNameAndPassword(String username, String password);
java
@Select("SELECT * FROM user_info WHERE username = #{arg0} AND password = #{arg1}")
List<UserInfo> selectByNameAndPassword(String username, String password);
注意 :
param1从 1 开始计数,arg0从 0 开始计数。推荐使用param前缀,更直观。
如果不想使用默认的 param1/arg0,也可以直接在 SQL 中使用 Java 方法参数名(需开启 -parameters 编译选项):
java
@Select("SELECT * FROM user_info WHERE username = #{username} AND password = #{password}")
List<UserInfo> selectByNameAndPassword(String username, String password);
参数命名必须与 ParamMap 中的 key 一致
MyBatis 多参数绑定的核心机制是 ParamMap------#{} 中的名称会作为 key 去 ParamMap 中查找对应的值。如果 key 不存在,会抛出 BindingException。
java
// ❌ 错误:运行时报 BindingException,ParamMap 中没有 key "a" 和 "b"
@Select("SELECT * FROM user_info WHERE username = #{a} AND password = #{b}")
List<UserInfo> selectByNameAndPassword(String password, String username);
ParamMap 中的可用 key 按优先级:
@Param指定的名称(下面就会说明)- 编译参数名(需开启
-parameters) - 默认命名
arg0/arg1/... 和param1/param2/...
不存在按位置自动匹配任意名称的机制 ,#{} 中的名称必须精确匹配上述三种来源之一。
参数重命名(@Param)
当需要显式指定 SQL 中参数的名称时,可以使用 @Param 注解为方法参数重命名。
基本用法
java
@Select("SELECT * FROM user_info WHERE username = #{userName} AND password = #{password}")
List<UserInfo> selectByNameAndPassword(
@Param("password") String password,
@Param("userName") String username);
@Param("userName") 将方法参数 username 重命名为 userName,SQL 中就可以使用 #{userName} 来引用它。参数顺序不再重要 ,MyBatis 完全按 @Param 指定的名称匹配。
传递对象参数
当需要传递一个实体对象时,@Param 可以为对象指定一个别名,SQL 中通过 别名.属性名 访问对象的字段:
java
@Insert("INSERT INTO user_info (username, password, age) " +
"VALUES (#{userInfo.username}, #{userInfo.password}, #{userInfo.age})")
Integer insertUser(@Param("userInfo") UserInfo userInfo);
如果不加 @Param,也可以直接使用属性名:
java
@Insert("INSERT INTO user_info (username, password, age) " +
"VALUES (#{username}, #{password}, #{age})")
Integer insertUser(UserInfo userInfo);
推荐 :当方法有多个参数且包含对象时,使用
@Param明确指定名称,提高代码可读性。
混合参数传递
@Param 也支持基本类型和对象混合使用:
java
@Select("SELECT * FROM user_info WHERE age > #{minAge} AND username LIKE CONCAT('%', #{userInfo.username}, '%')")
List<UserInfo> selectByCondition(
@Param("minAge") Integer minAge,
@Param("userInfo") UserInfo userInfo);
@Param 的底层原理
核心组件:ParamNameResolver
MyBatis 通过 ParamNameResolver 解析方法参数。在处理 #{} 表达式之前,ParamNameResolver 会遍历方法的每个参数,提取参数名并构造一个 ParamMap。
提取参数名的逻辑按优先级:
java
// MyBatis 源码简化逻辑(ParamNameResolver.getNamedParams)
for (int i = 0; i < paramCount; i++) {
// 1. 检查 @Param 注解
if (hasParamAnnotation) {
name = annotation.value(); // 使用 @Param 指定的名称
}
// 2. 使用实际参数名(需 -parameters 编译选项)
else if (useActualParamName) {
name = actualParamName; // 如 String username → "username"
}
// 3. 使用参数索引序号
else {
name = String.valueOf(i); // "0", "1", "2"...
}
}
ParamMap 的构造过程
ParamNameResolver 最终调用 getNamedParams() 返回一个 ParamMap。以如下方法为例:
java
@Select("SELECT * FROM user_info WHERE username = #{userName} AND age > #{minAge}")
List<UserInfo> selectByCondition(
@Param("minAge") Integer minAge,
@Param("userName") String username);
运行时的 ParamMap 内容为:
| Key | Value | 来源 |
|---|---|---|
"minAge" |
18 |
@Param("minAge") |
"userName" |
"zhangsan" |
@Param("userName") |
"param1" |
18 |
默认:第 1 个参数 |
"param2" |
"zhangsan" |
默认:第 2 个参数 |
注意 :即使使用了
@Param,param1/param2等默认 key 仍然存在,两种方式都可以在 SQL 中使用。
不同场景下 ParamMap 的差异⭐
| 条件 | ParamMap 中的 key | SQL 中可用的名称 |
|---|---|---|
单参数,无 @Param |
不构造 Map | 任意名称均可 |
单参数,有 @Param |
@Param名 + param1 |
@Param名 或 param1 |
多参数,无 @Param |
arg0/arg1 + param1/param2 |
只能用默认名 |
多参数,有 @Param |
@Param名 + param1/param2 |
推荐用 @Param名 |
场景 1:单参数 + 无 @Param
java
List<UserInfo> selectById(Integer id);
// → 不构造 ParamMap,原始参数对象直接传入
// → SQL 中 #{任意名称} 都能正确获取到值
场景 2:单参数 + 有 @Param
java
List<UserInfo> selectById(@Param("userId") Integer id);
// → 构造 ParamMap:{"userId" → id值, "param1" → id值}
// → SQL 中必须用 #{userId} 或 #{param1}
场景 3:多参数 + 无 @Param
java
List<UserInfo> selectByNameAndPassword(String username, String password);
// → 构造 ParamMap:{"arg0" → username值, "arg1" → password值,
// "param1" → username值, "param2" → password值}
// → SQL 中必须用 #{arg0}/#{arg1} 或 #{param1}/#{param2}
场景 4:多参数 + 有 @Param
java
List<UserInfo> selectByNameAndPassword(
@Param("name") String username,
@Param("pwd") String password);
// → 构造 ParamMap:{"name" → username值, "pwd" → password值,
// "param1" → username值, "param2" → password值}
// → SQL 中可用 #{name}/#{pwd} 或 #{param1}/#{param2}
ParamMap 与 OGNL 表达式解析
当 MyBatis 解析 SQL 中的 #{} 和 ${} 时,内部使用 OGNL (Object-Graph Navigation Language)对 ParamMap 进行求值:
java
@Insert("INSERT INTO user_info (username, password, age) " +
"VALUES (#{userInfo.username}, #{userInfo.password}, #{userInfo.age})")
Integer insertUser(@Param("userInfo") UserInfo userInfo);
解析过程:
#{userInfo.username}→ 从 ParamMap 取出 key="userInfo",得到UserInfo对象- 对取出的对象继续执行 OGNL 表达式
.username→ 调用UserInfo.getUsername() - 最终获得值并设置到 JDBC 的
PreparedStatement参数中
这也是为什么传递对象时,如果不加 @Param,可以直接写 #{username}------此时 ParamMap 未将对象包裹,OGNL 直接在原始对象上解析 .username。
${}和#{} ⭐
BindingException 的触发机制
ParamMap 继承自 HashMap,但重写了 get() 方法------当 key 不存在时,不会返回 null ,而是直接抛出 BindingException:
java
// ParamMap 源码(简化)
@Override
public Object get(Object key) {
if (!super.containsKey(key)) {
throw new BindingException(
"Parameter '" + key + "' not found. Available parameters are " + keySet());
}
return super.get(key);
}
这就是为什么 #{} 中写错名称时会看到这样的错误信息:
BindingException: Parameter 'a' not found. Available parameters are [arg1, arg0, param1, param2]
错误信息中的 Available parameters 直接列出了当前 ParamMap 的所有可用 key,方便排查。
查询:@Select
1. 按主键查询
java
/**
* 根据主键 ID 查询单个用户
* @param id 用户ID
* @return 用户对象,不存在则返回 null
*/
@Select("SELECT * FROM user_info WHERE id = #{id}")
UserInfo selectById(Integer id);
调用示例:
java
UserInfo user = userInfoMapper.selectById(1);
// 执行 SQL:SELECT * FROM user_info WHERE id = 1
// 结果:UserInfo{id=1, username="admin", age=18, ...}
2. 查询全部
java
/**
* 查询所有用户
* @return 用户列表,无数据则返回空列表(不会返回 null)
*/
@Select("SELECT * FROM user_info")
List<UserInfo> selectAll();
3. 条件查询(多参数 + @Param)
java
/**
* 按用户名和年龄下限查询
* @param username 用户名(模糊匹配)
* @param minAge 最小年龄
* @return 符合条件的用户列表
*/
@Select("SELECT * FROM user_info WHERE username LIKE CONCAT('%', #{username}, '%') " +
"AND age >= #{minAge}")
List<UserInfo> selectByCondition(
@Param("username") String username,
@Param("minAge") Integer minAge);
调用示例:
java
List<UserInfo> users = userInfoMapper.selectByCondition("zhang", 18);
// 执行 SQL:SELECT * FROM user_info WHERE username LIKE '%zhang%' AND age >= 18
// 结果:[UserInfo{username="zhangsan", age=20, ...}]
4. LIKE 模糊查询
java
// ✅ 安全写法:用 CONCAT + #{}
@Select("SELECT * FROM user_info WHERE username LIKE CONCAT('%', #{keyword}, '%')")
List<UserInfo> selectByKeyword(@Param("keyword") String keyword);
// ✅ 安全写法:用 CONCAT + #{}
@Select("SELECT * FROM user_info WHERE phone LIKE CONCAT(#{prefix}, '%')")
List<UserInfo> selectByPhonePrefix(@Param("prefix") String prefix);
调用示例:
java
List<UserInfo> users = userInfoMapper.selectByKeyword("admin");
// SQL:WHERE username LIKE '%admin%' → 匹配 "admin"
List<UserInfo> users2 = userInfoMapper.selectByPhonePrefix("186");
// SQL:WHERE phone LIKE '186%' → 匹配 186 开头的手机号
5. 排序查询(ORDER BY)
java
/**
* 按指定字段排序查询
* ⚠️ orderColumn 需在业务层做白名单校验
*/
@Select("SELECT * FROM user_info ORDER BY ${orderColumn} ${orderDirection}")
List<UserInfo> selectAllOrdered(
@Param("orderColumn") String orderColumn,
@Param("orderDirection") String orderDirection);
调用示例:
java
// 按年龄降序
List<UserInfo> users = userInfoMapper.selectAllOrdered("age", "DESC");
// SQL:SELECT * FROM user_info ORDER BY age DESC
// 按创建时间升序
List<UserInfo> users2 = userInfoMapper.selectAllOrdered("create_time", "ASC");
// SQL:SELECT * FROM user_info ORDER BY create_time ASC
⚠️ ORDER BY 必须用
${},因为 JDBC 的?占位符不能替代 SQL 关键字。但${orderColumn}必须做白名单校验,防止注入。
6. 分页查询(LIMIT)
java
/**
* 分页查询
* @param offset 偏移量(从第几条开始)
* @param limit 每页条数
*/
@Select("SELECT * FROM user_info LIMIT #{offset}, #{limit}")
List<UserInfo> selectByPage(
@Param("offset") Integer offset,
@Param("limit") Integer limit);
调用示例:
java
// 第 1 页,每页 2 条
List<UserInfo> page1 = userInfoMapper.selectByPage(0, 2);
// SQL:SELECT * FROM user_info LIMIT 0, 2
// 结果:[admin, zhangsan]
// 第 2 页
List<UserInfo> page2 = userInfoMapper.selectByPage(2, 2);
// SQL:SELECT * FROM user_info LIMIT 2, 2
// 结果:[lisi]
7. 聚合查询(COUNT / MAX / MIN / AVG)
java
/** 统计用户总数 */
@Select("SELECT COUNT(*) FROM user_info")
Integer countAll();
/** 统计性别为指定值的用户数 */
@Select("SELECT COUNT(*) FROM user_info WHERE gender = #{gender}")
Integer countByGender(@Param("gender") Integer gender);
/** 查询最大 / 最小年龄 */
@Select("SELECT MAX(age) FROM user_info")
Integer selectMaxAge();
@Select("SELECT MIN(age) FROM user_info")
Integer selectMinAge();
调用示例:
java
Integer total = userInfoMapper.countAll(); // → 3(初始数据 3 条)
Integer maleCount = userInfoMapper.countByGender(1); // → 2(admin + zhangsan)
Integer maxAge = userInfoMapper.selectMaxAge(); // → 22
8. IN 子句查询
java
/**
* 批量按 ID 查询
* MyBatis 自动将 Collection/Array 展开为 IN 子句
*/
@Select("<script>" +
"SELECT * FROM user_info WHERE id IN " +
"<foreach item='id' collection='idList' open='(' separator=',' close=')'>" +
"#{id}" +
"</foreach>" +
"</script>")
List<UserInfo> selectByIds(@Param("idList") List<Integer> idList);
调用示例:
java
List<Integer> ids = Arrays.asList(1, 3);
List<UserInfo> users = userInfoMapper.selectByIds(ids);
// SQL:SELECT * FROM user_info WHERE id IN (1, 3)
// 结果:[UserInfo{id=1, username="admin"}, UserInfo{id=3, username="lisi"}]
注意 :动态 SQL(
<foreach>等标签)在注解中需要包裹在<script>标签内,否则 MyBatis 会将其当作普通文本。
9. 返回单个字段值
java
/** 只查用户名列表 */
@Select("SELECT username FROM user_info")
List<String> selectAllUsernames();
/** 根据 ID 只查手机号 */
@Select("SELECT phone FROM user_info WHERE id = #{id}")
String selectPhoneById(Integer id);
10. 返回 Map
java
/** 返回 Map<列名, 列值>------适合单条记录 */
@Select("SELECT * FROM user_info WHERE id = #{id}")
Map<String, Object> selectByIdAsMap(Integer id);
/** 返回多条时用 List<Map> */
@Select("SELECT * FROM user_info")
List<Map<String, Object>> selectAllAsMapList();
调用示例:
java
Map<String, Object> map = userInfoMapper.selectByIdAsMap(1);
// → {id=1, username=admin, password=admin, age=18, gender=1, phone=18612340001, ...}
11. 查询去重
java
@Select("SELECT DISTINCT gender FROM user_info")
List<Integer> selectDistinctGenders();
// → [1, 2]
12. GROUP BY 分组统计
java
@Select("SELECT gender, COUNT(*) AS count FROM user_info GROUP BY gender")
List<Map<String, Object>> countGroupByGender();
调用示例:
java
List<Map<String, Object>> result = userInfoMapper.countGroupByGender();
// → [{gender=1, count=2}, {gender=2, count=1}]
插入:@Insert
1. 插入完整实体对象
java
/**
* 插入用户------直接传实体对象,通过属性名绑定
* @param userInfo 用户对象
* @return 受影响行数(成功返回 1)
*/
@Insert("INSERT INTO user_info (username, password, age, gender, phone) " +
"VALUES (#{username}, #{password}, #{age}, #{gender}, #{phone})")
Integer insertUser(UserInfo userInfo);
调用示例:
java
UserInfo newUser = new UserInfo();
newUser.setUsername("wangwu");
newUser.setPassword("123456");
newUser.setAge(25);
newUser.setGender(1);
newUser.setPhone("18612340004");
Integer rows = userInfoMapper.insertUser(newUser);
// 执行 SQL:INSERT INTO user_info (username, password, age, gender, phone)
// VALUES ('wangwu', '123456', 25, 1, '18612340004')
// 返回值:1(受影响行数)
// ⚠️ newUser.getId() 仍为 null(未回填自增主键)
2. 插入并回填自增主键
java
/**
* 插入用户,同时将数据库自增 ID 回填到实体对象
* useGeneratedKeys = true → 使用 JDBC getGeneratedKeys() 获取自增主键
* keyProperty = "id" → 回填到 UserInfo 对象的 id 属性
* keyColumn = "id" → 数据库主键列名
*/
@Insert("INSERT INTO user_info (username, password, age, gender, phone) " +
"VALUES (#{username}, #{password}, #{age}, #{gender}, #{phone})")
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
Integer insertUserAndReturnId(UserInfo userInfo);
调用示例:
java
UserInfo newUser = new UserInfo();
newUser.setUsername("zhaoliu");
newUser.setPassword("123456");
newUser.setAge(30);
newUser.setGender(1);
newUser.setPhone("18612340005");
userInfoMapper.insertUserAndReturnId(newUser);
System.out.println(newUser.getId()); // → 4(数据库自动分配的 ID)
关键 :
@Options的useGeneratedKeys只对自增主键有效。如果是手动赋值的主键(如 UUID),不需要此注解。
3. 使用 @Param 传递对象
java
/**
* 用 @Param 为对象指定前缀,SQL 中通过 前缀.属性 访问
*/
@Insert("INSERT INTO user_info (username, password, age, gender, phone) " +
"VALUES (#{user.username}, #{user.password}, #{user.age}, #{user.gender}, #{user.phone})")
@Options(useGeneratedKeys = true, keyProperty = "user.id", keyColumn = "id")
Integer insertUserWithPrefix(@Param("user") UserInfo userInfo);
调用示例:
java
UserInfo user = new UserInfo();
user.setUsername("sunqi");
user.setPassword("123456");
user.setAge(28);
user.setGender(2);
user.setPhone("18612340006");
userInfoMapper.insertUserWithPrefix(user);
System.out.println(user.getId()); // → 5
keyProperty = "user.id"需要加@Param前缀才能正确回填。
4. 逐字段插入
java
/**
* 逐字段传参插入
*/
@Insert("INSERT INTO user_info (username, password, age, gender, phone) " +
"VALUES (#{username}, #{password}, #{age}, #{gender}, #{phone})")
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
Integer insertUserByFields(
@Param("username") String username,
@Param("password") String password,
@Param("age") Integer age,
@Param("gender") Integer gender,
@Param("phone") String phone);
调用示例:
java
userInfoMapper.insertUserByFields("zhouba", "123456", 35, 1, "18612340007");
// ⚠️ 逐字段传参时无法回填 ID(没有实体对象承载)
5. 插入默认值
java
/**
* 插入时省略可空列,让数据库使用 DEFAULT 值
* age 和 phone 有 DEFAULT NULL,gender 有 DEFAULT 0
* create_time / update_time 由数据库自动填充
*/
@Insert("INSERT INTO user_info (username, password) VALUES (#{username}, #{password})")
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
Integer insertUserMinimal(UserInfo userInfo);
调用示例:
java
UserInfo user = new UserInfo();
user.setUsername("wujiu");
user.setPassword("123456");
userInfoMapper.insertUserMinimal(user);
// 插入后数据库记录:age=null, gender=0, phone=null
// create_time 和 update_time 由数据库自动设为当前时间
6. 批量插入(使用 <script> + <foreach>)
java
/**
* 批量插入,一条 SQL 插入多行
*/
@Insert("<script>" +
"INSERT INTO user_info (username, password, age, gender, phone) VALUES " +
"<foreach item='user' collection='userList' separator=','>" +
"(#{user.username}, #{user.password}, #{user.age}, #{user.gender}, #{user.phone})" +
"</foreach>" +
"</script>")
Integer batchInsert(@Param("userList") List<UserInfo> userList);
调用示例:
java
List<UserInfo> users = Arrays.asList(
buildUser("userA", "pwdA", 20, 1, "10001"),
buildUser("userB", "pwdB", 21, 2, "10002"),
buildUser("userC", "pwdC", 22, 1, "10003")
);
Integer rows = userInfoMapper.batchInsert(users);
// SQL:INSERT INTO user_info (...) VALUES (...), (...), (...)
// 返回值:3(插入行数)
// ⚠️ 批量插入时 @Options 的 useGeneratedKeys 行为因 JDBC 驱动而异
更新:@Update
1. 按主键更新全部字段
java
/**
* 根据 ID 更新用户所有字段
* @param userInfo 用户对象(必须包含 id)
* @return 受影响行数(1 = 成功,0 = ID 不存在)
*/
@Update("UPDATE user_info SET " +
"username = #{username}, " +
"password = #{password}, " +
"age = #{age}, " +
"gender = #{gender}, " +
"phone = #{phone} " +
"WHERE id = #{id}")
Integer updateById(UserInfo userInfo);
调用示例:
java
UserInfo user = userInfoMapper.selectById(1); // 查出 id=1 的用户
user.setAge(26);
user.setPhone("18600000000");
Integer rows = userInfoMapper.updateById(user);
// 执行 SQL:UPDATE user_info SET username='admin', password='admin',
// age=26, gender=1, phone='18600000000' WHERE id=1
// 返回值:1
// update_time 由数据库 ON UPDATE CURRENT_TIMESTAMP 自动更新
2. 按主键选择性更新(逐字段传参)
java
/**
* 只更新密码------适用于修改密码场景
*/
@Update("UPDATE user_info SET password = #{newPassword} WHERE id = #{id}")
Integer updatePassword(
@Param("id") Integer id,
@Param("newPassword") String newPassword);
/**
* 只更新手机号
*/
@Update("UPDATE user_info SET phone = #{phone} WHERE id = #{id}")
Integer updatePhone(
@Param("id") Integer id,
@Param("phone") String phone);
调用示例:
java
userInfoMapper.updatePassword(1, "newPwd123");
// SQL:UPDATE user_info SET password = 'newPwd123' WHERE id = 1
userInfoMapper.updatePhone(2, "18699990000");
// SQL:UPDATE user_info SET phone = '18699990000' WHERE id = 2
3. 条件更新(WHERE 多条件)
java
/**
* 按 username 和 password 更新年龄(登录后修改年龄的场景)
*/
@Update("UPDATE user_info SET age = #{newAge} WHERE username = #{username} AND password = #{password}")
Integer updateAgeByCondition(
@Param("username") String username,
@Param("password") String password,
@Param("newAge") Integer newAge);
调用示例:
java
Integer rows = userInfoMapper.updateAgeByCondition("zhangsan", "zhangsan", 30);
// SQL:UPDATE user_info SET age = 30 WHERE username = 'zhangsan' AND password = 'zhangsan'
// 返回值:1(匹配到并更新),0(用户名或密码不匹配)
4. 批量更新(逐条或 CASE WHEN)
java
// 方案 A:循环调用(简单但性能较差)
// 在 Service 层 for 循环调用 updateById()
// 方案 B:CASE WHEN 批量更新(一条 SQL,性能最优)
@Update("<script>" +
"UPDATE user_info SET age = " +
"<foreach item='user' collection='userList' open='CASE id' close='END'>" +
"WHEN #{user.id} THEN #{user.age} " +
"</foreach>" +
"WHERE id IN " +
"<foreach item='user' collection='userList' open='(' separator=',' close=')'>" +
"#{user.id}" +
"</foreach>" +
"</script>")
Integer batchUpdateAge(@Param("userList") List<UserInfo> userList);
调用示例:
java
List<UserInfo> updates = Arrays.asList(
buildUserWithId(1, 25),
buildUserWithId(2, 30),
buildUserWithId(3, 35)
);
userInfoMapper.batchUpdateAge(updates);
// SQL:UPDATE user_info SET age = CASE id
// WHEN 1 THEN 25 WHEN 2 THEN 30 WHEN 3 THEN 35 END
// WHERE id IN (1, 2, 3)
5. 全量更新(不带 WHERE------慎用)
java
/**
* ⚠️ 重置所有用户的性别为未知(0)
* 不带 WHERE 子句,影响全表
*/
@Update("UPDATE user_info SET gender = #{gender}")
Integer updateAllGender(@Param("gender") Integer gender);
调用示例:
java
Integer rows = userInfoMapper.updateAllGender(0);
// SQL:UPDATE user_info SET gender = 0
// 返回值:3(所有行都被更新)
删除:@Delete
1. 按主键删除
java
/**
* 根据 ID 删除用户
* @param id 用户ID
* @return 受影响行数(1 = 成功,0 = ID 不存在)
*/
@Delete("DELETE FROM user_info WHERE id = #{id}")
Integer deleteById(Integer id);
调用示例:
java
Integer rows = userInfoMapper.deleteById(5);
// SQL:DELETE FROM user_info WHERE id = 5
// 返回值:1(删除成功),0(ID 不存在)
2. 条件删除
java
/**
* 按用户名删除
*/
@Delete("DELETE FROM user_info WHERE username = #{username}")
Integer deleteByUsername(@Param("username") String username);
/**
* 按年龄范围删除
*/
@Delete("DELETE FROM user_info WHERE age >= #{minAge} AND age <= #{maxAge}")
Integer deleteByAgeRange(
@Param("minAge") Integer minAge,
@Param("maxAge") Integer maxAge);
调用示例:
java
userInfoMapper.deleteByUsername("wangwu");
// SQL:DELETE FROM user_info WHERE username = 'wangwu'
userInfoMapper.deleteByAgeRange(30, 40);
// SQL:DELETE FROM user_info WHERE age >= 30 AND age <= 40
// 返回值:删除的行数
3. 批量删除(IN 子句)
java
/**
* 根据 ID 列表批量删除
*/
@Delete("<script>" +
"DELETE FROM user_info WHERE id IN " +
"<foreach item='id' collection='idList' open='(' separator=',' close=')'>" +
"#{id}" +
"</foreach>" +
"</script>")
Integer deleteByIds(@Param("idList") List<Integer> idList);
调用示例:
java
List<Integer> ids = Arrays.asList(4, 5, 6);
Integer rows = userInfoMapper.deleteByIds(ids);
// SQL:DELETE FROM user_info WHERE id IN (4, 5, 6)
// 返回值:实际删除的行数(如果 id=6 不存在,只删 2 行,返回 2)
4. 清空表(⚠️ 危险操作)
java
/**
* ⚠️ 删除全部数据------TRUNCATE 不能用 @Delete(DDL 不是 DML)
* DELETE FROM 逐行删除,可回滚(在事务中)
*/
@Delete("DELETE FROM user_info")
Integer deleteAll();
调用示例:
java
Integer rows = userInfoMapper.deleteAll();
// SQL:DELETE FROM user_info
// 返回值:删除的总行数
TRUNCATE TABLE是 DDL 语句,MyBatis 的@Delete不能执行它。如需 TRUNCATE,必须通过@Update或 JDBC 原生方式。
5. 删除的返回值判断
java
// Service 层调用示例
public boolean deleteUserSafe(Integer id) {
UserInfo user = userInfoMapper.selectById(id);
if (user == null) {
throw new RuntimeException("用户不存在,id=" + id);
}
Integer rows = userInfoMapper.deleteById(id);
return rows > 0; // 理论上 rows 一定为 1,因为上面已校验存在
}