MyBatis 插入(INSERT)数据时返回自动生成的 ID

1、概览

MyBatis 是一个开源 Java 持久层框架,可作为 JDBC 和 Hibernate 的替代品。它能简化持久层的代码,并自动封装结果集,开发者只需专注于编写自定义 SQL 查询或存储过程。

本文将带你了解如何在 Spring 中使用 MyBatis 插入(INSERT)数据时返回自动生成的 ID。

2、依赖设置

首先在 pom.xml 中添加 mybatis-spring-boot-starter 依赖:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>

3、示例

先创建一个简单的示例,在整个文章中都会用到。

3.1、定义实体

首先,创建一个代表汽车的简单实体类 Car

public class Car {
    private Long id;

    private String model;

    // Getter / Setter 方法省略
}

其次,定义一条创建表的 SQL 语句,并将其放入 car-schema.sql 文件中:

CREATE TABLE IF NOT EXISTS CAR
(
    ID    INTEGER PRIMARY KEY AUTO_INCREMENT,
    MODEL VARCHAR(100) NOT NULL
);

如上,ID 列是自增自主键(AUTO_INCREMENT)。

3.2、定义数据源

使用 H2 嵌入式数据库:

@Bean
public DataSource dataSource() {
    EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
    return builder
      .setType(EmbeddedDatabaseType.H2)
      .setName("testdb")
      .addScript("car-schema.sql")
      .build();
}

@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(dataSource());
    return factoryBean.getObject();
}

就绪后,来看看如何使用基于 注解 和基于 XML 的方法来检索自动生成的 ID。

4、使用注解

定义 Mapper,即 MyBatis 用于将方法绑定到相应 SQL 语句的接口:

@Mapper
public interface CarMapper {
    // ...
}

接下来,添加一条 insert 语句:

@Insert("INSERT INTO CAR(MODEL) values (#{model})")
void save(Car car);

直观感觉上,我们可能会在 Mapper 方法上直接返回 Long,认为 MyBatis 会返回实体的自增 ID。然而,并不会,Mapper 接口返回的 Long / Integer 表示受影响的行数,并不是 INSERT 语句的自增 ID

要检索生成的 ID,可以使用 @Options@SelectKey 注解。

4.1、@Options 注解

使用 @Options 注解来扩展 insert 语句:

@Insert("INSERT INTO CAR(MODEL) values (#{model})")
@Options(useGeneratedKeys = true, keyColumn = "ID", keyProperty = "id")
void saveUsingOptions(Car car);

如上,设置了三个属性:

  • useGeneratedKeys - 是否要启动自动生成 KEY 功能。
  • keyColumn - 表示 KEY 所在列的名称(表中的自增列名)。
  • keyProperty - 表示保存自动生成的 KYE 值的字段名称(对象中的字段名)。

如果有多个自增列的话,可以使用逗号分割。

在底层,MyBatis 使用反射将 ID 列中的自增值映射到 Car对象的 id 字段中。

测试如下:

@Test
void givenCar_whenSaveUsingOptions_thenReturnId() {
    Car car = new Car();
    car.setModel("BMW");

    carMapper.saveUsingOptions(car);

    assertNotNull(car.getId());
}

4.2、@SelectKey 注解

另一种返回 ID 自增值的方法是使用 @SelectKey 注解。当我们想要使用序列或 ID 生成函数来检索 ID 时,这个注解非常有用。

如果用 @SelectKey 注解 Mapper 方法,MyBatis 就会忽略 @Options 等注解

CarMapper 中创建一个新方法,在执行插入后检索 ID 值:

@Insert("INSERT INTO CAR(MODEL) values (#{model})")
@SelectKey(statement = "CALL IDENTITY()", before = false, keyColumn = "ID", keyProperty = "id", resultType = Long.class)
void saveUsingSelectKey(Car car);

属性解释:

  • statement - 定义在 INSERT 语句后执行的语句。
  • before - 语句是在 INSERT 之前还是之后执行。
  • keyColumn - KEY 所在列的名称(表中的自增列名)。
  • keyProperty - 保存自增 KEY 值的字段名称。
  • resultType - 表示 keyProperty 的类型

注意,H2 数据库中的 IDENTITY() 函数已被删除。更多详情请点击 此处

为了能在 H2 数据库上执行 CALL IDENTITY(),我们需要将模式设置为 LEGACY

"testdb;MODE=LEGACY"

测试如下:

@Test
void givenCar_whenSaveUsingSelectKey_thenReturnId() {
    Car car = new Car();
    car.setModel("BMW");

    carMapper.saveUsingSelectKey(car);

    assertNotNull(car.getId());
}

5、使用 XML

现在,来看看如何在基于 XML 的方式中返回自增 ID。

首先,定义 CarXmlMapper 接口:

@Mapper
public interface CarXmlMapper {
     // ...
}

与基于注解的方法不同,我们不会直接在 Mapper 接口中编写 SQL 语句。相反,我们需要定义 Mapper 的 XML 文件,并将所有查询定义在其中:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="com.baeldung.mybatis.generatedid.CarXmlMapper">

</mapper>

注意,namespace 属性值需要指定 CarXmlMapper Mapper 接口的全路径。

5.1、UseGeneratedKeys 属性

接下来,在 CarXmlMapper 接口中定义一个方法:

void saveUsingOptions(Car car);

然后,在 Mapper XML 中定义 INSERT 语句,通过 insert 节点的 id 属性将其映射到 CarXmlMapper 接口中的 saveUsingOptions() 方法:

<insert id="saveUsingOptions" parameterType="com.baeldung.mybatis.generatedid.Car"
  useGeneratedKeys="true" keyColumn="ID" keyProperty="id">
    INSERT INTO CAR(MODEL)
    VALUES (#{model});
</insert>

insert 节点上定义的属性如下:

  • id - 将查询绑定至 CarXmlMapper 类中的指定方法。
  • parameterType - saveUsingOptions() 方法参数的类型。
  • useGeneratedKeys - 表示要使用 ID 自动生成功能。
  • keyColumn - 指定自增 ID 的列名。
  • keyProperty - 指定 Car 对象中保存自增 ID 值的字段名称。

测试如下:

@Test
void givenCar_whenSaveUsingOptions_thenReturnId() {
    Car car = new Car();
    car.setModel("BMW");

    carXmlMapper.saveUsingOptions(car);

    assertNotNull(car.getId());
}

5.2、SelectKey 元素

接下来,在 CarXmlMapper 接口中添加一个新方法,这次使用 selectKey 元素检索自增 ID:

void saveUsingSelectKey(Car car);

们在 Mapper XML 文件中指定语句,并将其绑定到 Mapper 接口方法:

<insert id="saveUsingSelectKey" parameterType="com.baeldung.mybatis.generatedid.Car">
    INSERT INTO CAR(MODEL)
    VALUES (#{model});

    <selectKey resultType="Long" order="AFTER" keyColumn="ID" keyProperty="id">
        CALL IDENTITY()
    </selectKey>
</insert>

如上,在 insert 元素中定义了 selectKey 元素,其属性如下:

  • resultType - 指定语句返回的类型。
  • order - 应在插入语句之前还是之后调用 CALL IDENTITY() 语句。
  • keyColumn - 表示 ID 列的名称。
  • keyProperty - 保存 ID 自增列值的属性名称。

最后,测试如下:

@Test
void givenCar_whenSaveUsingSelectKey_thenReturnId() {
    Car car = new Car();
    car.setModel("BMW");

    carXmlMapper.saveUsingSelectKey(car);

    assertNotNull(car.getId());
}

6、总结

本文介绍了如何在 Spring 中使用 MyBatis 插入(INSERT)数据时返回自动生成的 ID,主要介绍了使用 @Options@SelectKey 注解以及 XML 的方式。


Ref:https://www.baeldung.com/spring-mybatis-return-auto-generated-id