乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 MyBatis概述与快速上手

    • 本章定位
    • MyBatis简介
    • 环境搭建
    • 第一个MyBatis程序
    • SqlSessionFactoryBuilder与openSession重载
    • SqlSessionFactory与SqlSession
    • SqlSession核心方法
    • 不使用 XML 构建 SqlSessionFactory
    • Mapper接口与映射方式
    • Java API 目录结构
  • 第2章 全局配置文件详解

    • 本章定位
    • properties
    • settings
    • typeAliases
    • typeHandlers
    • objectFactory
    • plugins
    • environments
    • transactionManager
    • dataSource
    • databaseIdProvider
    • mappers
    • 日志配置
  • 第3章 SQL映射文件基础

    • 本章定位
    • select
    • insert
    • update
    • delete
    • 参数传递与占位符
    • 主键生成策略
    • resultType
    • resultMap
    • 自动映射详解
    • sql片段
    • SQL 语句构建器
  • 第4章 动态SQL

    • 本章定位
    • if
    • choose、when、otherwise
    • where
    • set
    • foreach
    • trim
    • bind
    • script 元素:在注解映射器中启用动态 SQL
    • _databaseId 与动态 SQL 的多数据库支持
    • 动态 SQL 中插入脚本语言
  • 第5章 结果映射与关联查询

    • 本章定位
    • resultMap详解
    • association
    • collection
    • discriminator
    • N+1查询问题
    • 延迟加载
  • 第6章 MyBatis注解开发

    • 本章定位
    • @Select
    • @Insert
    • @Update
    • @Delete
    • @Param
    • @Options
    • @SelectKey
    • @Results
    • @Result
    • @One
    • @Many
    • @SelectProvider
  • 第7章 缓存与性能优化

    • 本章定位
    • 一级缓存
    • 二级缓存
    • 缓存配置详解
    • 自定义缓存
    • Executor执行器类型
    • 分页插件

@Options

导学

本节学习目标:

  • 掌握 @Options 注解的核心属性,能够为 SQL 语句配置语句级选项
  • 学会使用 useGeneratedKeys 和 keyProperty 实现自增主键回填
  • 理解 useCache、flushCache 对二级缓存的影响
  • 明确 @Options 与 XML 中 <select>、<insert> 等标签属性的对应关系

定义

@Options 是 MyBatis 提供的语句级选项注解,标注在 Mapper 接口的方法上,用于配置该 SQL 语句的执行特性。它对应 XML 映射文件中 <select>、<insert>、<update>、<delete> 标签上的各类属性(如 useGeneratedKeys、flushCache、timeout 等)。

痛点解决:在注解开发中,开发者无法像 XML 那样在标签上写属性。@Options 填补了这一空白,让注解方式也能控制主键生成、缓存刷新、超时时间等语句级行为。最常见的场景是与 @Insert 配合,实现插入后自动获取数据库生成的主键。

注解方式 vs XML 方式对比

对比维度@Options 注解方式XML 标签属性方式
主键回填@Options(useGeneratedKeys = true, keyProperty = "id")<insert useGeneratedKeys="true" keyProperty="id">
缓存控制@Options(flushCache = Options.FlushCachePolicy.TRUE)<select flushCache="true">
超时设置@Options(timeout = 5)<select timeout="5">
适用性只能标注在方法上,与 SQL 注解配合直接写在 XML 标签上

适用场景建议:需要主键回填、需要控制缓存、需要设置超时时间的场景,无论注解还是 XML 都需要配置相应选项。@Options 让注解开发也能获得与 XML 完全一致的语句级控制能力。

适用位置与核心属性

@Options 标注在 Mapper 接口的方法 上,通常与 @Insert、@Update、@Delete、@Select 配合使用。

属性类型必填说明
useCacheboolean否是否使用二级缓存。默认 true。仅对 @Select 有效
flushCacheFlushCachePolicy否执行后是否刷新缓存。DEFAULT、TRUE、FALSE。TRUE 表示执行后清空缓存
useGeneratedKeysboolean否是否使用 JDBC 的 getGeneratedKeys 获取数据库生成的主键。默认 false
keyPropertyString否指定生成的主键回填到传入实体对象的哪个属性。配合 useGeneratedKeys 使用
keyColumnString否指定数据库生成键的列名。当主键列名与 keyProperty 不一致时使用
timeoutint否设置语句执行超时时间(秒)。-1 表示使用全局配置
fetchSizeint否设置 JDBC 的 fetchSize,影响结果集拉取策略
resultSetTypeResultSetType否结果集类型:FORWARD_ONLY、SCROLL_INSENSITIVE、SCROLL_SENSITIVE

核心原理

MyBatis 在构建 MappedStatement 时,会读取方法上的 @Options 注解,将其属性值设置到 MappedStatement 的对应字段中。执行 SQL 时,Executor 根据这些配置决定:是否刷新缓存、是否获取生成键、设置多少超时时间等。对于 useGeneratedKeys,MyBatis 在执行 INSERT 后会调用 Statement.getGeneratedKeys(),读取生成的主键值并通过反射回填到参数对象。

完整示例

场景说明

乐途公司学生管理系统需要实现:插入学生后自动获取自增主键;同时演示 @Options 的其他常用属性。

操作前的数据库表结构及初始数据

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);

当前数据状态:

idnameagemajorscore
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 com.flying.entity.Student;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;
import java.util.List;

public interface StudentMapper {

    /**
     * 插入学生,获取自增主键回填到 id 属性
     */
    @Insert("INSERT INTO student(name, age, major, score) VALUES(#{name}, #{age}, #{major}, #{score})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insertStudent(Student student);

    /**
     * 插入学生,同时设置超时时间为 3 秒
     */
    @Insert("INSERT INTO student(name, age, major, score) VALUES(#{name}, #{age}, #{major}, #{score})")
    @Options(useGeneratedKeys = true, keyProperty = "id", timeout = 3)
    int insertWithTimeout(Student student);

    /**
     * 查询所有学生,执行后刷新二级缓存
     */
    @Select("SELECT id, name, age, major, score FROM student")
    @Options(flushCache = Options.FlushCachePolicy.TRUE)
    List<Student> selectAllFlushCache();
}

测试调用代码

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;

public class OptionsTest {
    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=== 带超时的插入 ===");
        Student newStu2 = new Student();
        newStu2.setName("钱多多");
        newStu2.setAge(20);
        newStu2.setMajor("网络工程");
        newStu2.setScore(85.0);

        int rows2 = mapper.insertWithTimeout(newStu2);
        System.out.println("影响行数: " + rows2);
        System.out.println("回填主键 id: " + newStu2.getId());

        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.insertWithTimeout - ==>  Preparing: INSERT INTO student(name, age, major, score) VALUES(?, ?, ?, ?)
[main] DEBUG com.flying.mapper.StudentMapper.insertWithTimeout - ==> Parameters: 钱多多(String), 20(Integer), 网络工程(String), 85.0(Double)
[main] DEBUG com.flying.mapper.StudentMapper.insertWithTimeout - <==    Updates: 1
影响行数: 1
回填主键 id: 7

插入后数据状态

idnameagemajorscore
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

分析

  • @Options(useGeneratedKeys = true, keyProperty = "id") 是 MySQL 自增主键回填的标准配置,执行后 student.getId() 从 null 变为 6
  • timeout = 3 设置了语句执行超时时间为 3 秒,如果数据库响应超过该时间将抛出超时异常
  • flushCache = Options.FlushCachePolicy.TRUE 会在查询执行后清空该命名空间下的二级缓存,确保后续查询获取最新数据
  • keyColumn 在列名与属性名不一致时使用,例如数据库主键列是 stu_id,而属性是 id,则需要写 keyColumn = "stu_id"

易错场景 / 常见误区

误区错误示例正解
useGeneratedKeys 与不支持自增的数据库混用对 Oracle 使用 useGeneratedKeys = trueOracle 应使用 @SelectKey 获取序列值
keyProperty 写错属性名keyProperty = "studentId"必须与实体类属性名一致,如 id
认为 @Options 只能配合 @Insert—@Options 可配合 @Select、@Update、@Delete 使用
忘记提交事务无 session.commit()写操作必须手动提交或设置自动提交

面试考点

Q1:@Options 的 useGeneratedKeys 和 @SelectKey 有什么区别?

useGeneratedKeys 依赖 JDBC 的 getGeneratedKeys() 能力,适用于 MySQL、SQL Server 等支持自动生成主键的数据库;@SelectKey 通过执行额外的 SQL 语句获取主键,适用于 Oracle 序列、UUID 等不支持自动生成键的场景。两者互斥,根据数据库特性选择。

Q2:flushCache 的三种取值分别代表什么?

DEFAULT:遵循全局配置和语句类型的默认行为(默认查询不刷新,增删改刷新);TRUE:执行后强制刷新缓存;FALSE:执行后不刷新缓存。对于某些特殊查询(如查询最新统计结果),可设置为 TRUE 确保数据新鲜。

Q3:@Options 可以设置 fetchSize,它的作用是什么?

fetchSize 设置 JDBC 驱动每次从数据库拉取的行数。对于大数据量查询,适当增大 fetchSize 可以减少网络往返次数,提升查询性能;但过大的 fetchSize 会增加内存占用。

Q4:keyColumn 在什么情况下必须使用?

当数据库生成键的列名与 keyProperty 指定的属性名不一致时使用。例如复合主键场景,或者数据库列名为 user_id 但实体属性为 id 时,需要显式指定 keyColumn = "user_id"。

小结

@Options 是注解开发中控制语句级行为的核心配置注解。useGeneratedKeys + keyProperty 解决了插入后主键回填的刚需,flushCache 和 timeout 则提供了缓存与性能层面的精细控制。掌握这些属性,可以让注解方式获得与 XML 完全对等的能力。

下一章引子

对于不支持自动生成主键的数据库(如 Oracle),@Options 的 useGeneratedKeys 无能为力。@SelectKey 注解通过执行额外的 SQL 语句在插入前后获取主键,是跨数据库主键策略的通用解决方案。下一节将详细讲解 @SelectKey 的用法。

上一页
@Param
下一页
@SelectKey