【MyBatis】注释方式实现CRUD

加入 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 提供的起步依赖,它会自动配置 SqlSessionFactorySqlSessionTemplate 等核心组件,简化集成过程。版本号建议与 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 会按参数顺序自动命名:arg0arg1arg2... 或 param1param2param3...

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 按优先级:

  1. @Param 指定的名称(下面就会说明)
  2. 编译参数名(需开启 -parameters
  3. 默认命名 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 个参数

注意 :即使使用了 @Paramparam1/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);

解析过程:

  1. #{userInfo.username} → 从 ParamMap 取出 key="userInfo",得到 UserInfo 对象
  2. 对取出的对象继续执行 OGNL 表达式 .username → 调用 UserInfo.getUsername()
  3. 最终获得值并设置到 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)

关键@OptionsuseGeneratedKeys 只对自增主键有效。如果是手动赋值的主键(如 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,因为上面已校验存在
}
相关推荐
XiYang-DING1 小时前
【MyBatis】XML方式实现CRUD
xml·mybatis
小饼干在学嘎瓦2 小时前
秒杀场景Redis做预扣减,问题在哪里?
数据库·redis·mybatis
来杯@Java12 小时前
图书管理系统(基于springboot+vue前后端分离的项目)计算机毕业设计java
java·spring boot·spring·vue·毕业设计·mybatis·课程设计
Pluchon1 天前
萌萌技术分享笔记——Java综合项目
java·开发语言·笔记·git·github·mybatis·postman
骄马之死1 天前
MyBatis SqlSession 与缓存机制详解
mysql·mybatis
IronMurphy2 天前
SSM拷打第二讲!!!
java·spring·mybatis
C+-C资深大佬3 天前
SSM 框架(Spring + SpringMVC + MyBatis)
java·spring·mybatis
二王一个今3 天前
springboot security 权限控制---循环依赖问题
mybatis
落木萧萧8253 天前
为什么我把 MyBatisGX 设计成现在这样
mybatis·orm