JdbcTemplate 获取自增ID

JdbcTemplate 是由 spring-jdbc 提供的一个 JDBC 工具类模块,可以快速地对数据库进行 CURD 操作。

本文将会教你如何在使用 JdbcTemplate 执行 INSERT 操作时获取到自增ID。

项目设置

创建任意 spring boot 项目。添加 mysql-connector-j 以及 spring-boot-starter-jdbc 依赖。

你可以通过 start.springboot.io 快速创建此示例项目。

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

配置数据源

application.yaml 中配置基础的数据源信息。

spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
    username: root
    password: root

配置 JdbcTemplate

通过配置类,注册 JdbcTemplate Bean。

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;

@Configuration
public class JdbcTemplateConfiguration {

    @Bean
    public JdbcTemplate jdbcTemplate (DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

创建示例表

CREATE TABLE `users` (
  `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `account` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '账户',
  `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名称',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='系统用户';

其中 id 字段是自增的。

GeneratedKeyHolder

spring-jdbc 提供了一个 GeneratedKeyHolder 类,通过它可以获取到自增列的值。

public class GeneratedKeyHolder implements KeyHolder {

    // 返回唯一自增 key 的值
    public Number getKey();
    // 返回唯一自增 key 的值,指定其返回类型
    public <T> T getKeyAs(Class<T> keyType);

    // 返回多个自增 key 及其值
    public Map<String, Object> getKeys();
    public List<Map<String, Object>> getKeyList();
}

JdbcTeplate 获取自增 ID

src/test/main 包下创建测试类:

import java.sql.Types;
import java.time.LocalDateTime;
import java.util.Arrays;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.PreparedStatementCreatorFactory;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.test.context.junit4.SpringRunner;

import cn.springdoc.demo.DemoApplication;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public class DemoApplicationTest {

    static final Logger logger = LoggerFactory.getLogger(DemoApplicationTest.class);

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Test
    public void test() {

        // 创建 GeneratedKeyHolder 对象
        GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();

        // Insert SQL
        String sql = "INSERT INTO `users`(`id`, `account`, `create_at`, `name`) VALUES(?, ?, ?, ?);";

        // 初始化 CreatorFactory,指定要执行的 SQL,以及参数的 Jdbc 类型
        PreparedStatementCreatorFactory factory = new PreparedStatementCreatorFactory(sql, 
                Types.BIGINT, Types.VARCHAR, Types.TIMESTAMP, Types.VARCHAR);
        
        // 需要返回自增记录值
        factory.setReturnGeneratedKeys(true);

        // 设置占位符参数,返回 creator
        PreparedStatementCreator creator = factory
                .newPreparedStatementCreator(Arrays.asList(null, "springdoc.cn", LocalDateTime.now(), "Spring 中文网"));

        // 执行更新,返回受影响行数
        int retVal = this.jdbcTemplate.update(creator, keyHolder);

        // 通过 keyHolder 获取到自增值
        Long id = keyHolder.getKey().longValue();

        logger.info("受影响行数 = {}, 自增ID = {}", retVal, id);
    }
}

执行测试,输出日志如下:

INFO 5372 --- [           main] cn.springdoc.test.DemoApplicationTest    : 受影响行数 = 1, 自增ID = 101

NamedParameterJdbcTemplate 获取自增 ID

NamedParameterJdbcTemplate 封装了 JdbcTeplate,提供了 “命名参数” 的支持。

同样,需要先在配置类中注册 Bean。

@Bean
public NamedParameterJdbcTemplate namedParameterJdbcTemplate (DataSource dataSource) {
    return new NamedParameterJdbcTemplate(dataSource);
}

创建测试:

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.test.context.junit4.SpringRunner;

import cn.springdoc.demo.DemoApplication;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public class DemoApplicationTest {

    static final Logger logger = LoggerFactory.getLogger(DemoApplicationTest.class);

    @Autowired
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    @Test
    public void test() {

        // 创建 GeneratedKeyHolder 对象
        GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();

        // Insert SQL
        String sql = "INSERT INTO `users`(`id`, `account`, `create_at`, `name`) VALUES(:id, :account, :create_at, :name);";

        // 使用 Map 封装参数,key 就是 SQL 中对应的参数名称
        Map<String, Object> params = new HashMap<>();
        params.put("id", null);
        params.put("account", "springdoc.cn");
        params.put("create_at", LocalDateTime.now());
        params.put("name", "Spring 中文网");
        
        // 执行更新,返回受影响行数
        int retVal = this.namedParameterJdbcTemplate.update(sql, new MapSqlParameterSource(params), keyHolder);
        
        // 通过 keyHolder 获取到自增值
        Long id = keyHolder.getKey().longValue();

        logger.info("受影响行数 = {}, 自增ID = {}", retVal, id);
    }
}

注意,这里的 insert SQL 语句中,我们使用了 “命名参数” 占位符,而不是问号。

执行测试,输出日志如下:

INFO 13016 --- [           main] cn.springdoc.test.DemoApplicationTest    : 受影响行数 = 1, 自增ID = 102