You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Data JDBC中如何使用默认值插入并获取插入行ID?

Spring Data JDBC: 解决save用NULL而非DEFAULT、自定义INSERT无法获取ID的问题

我之前在项目里也踩过这两个Spring Data JDBC的坑,给你分享下实际可行的解决方案:

问题1:CrudRepository#save将null字段解析为NULL而非数据库DEFAULT值

Spring Data JDBC的默认save方法会把实体类中所有字段都包含在INSERT/UPDATE语句里,哪怕字段是null,也会明确写入NULL到数据库,这就会覆盖掉你在数据库列上设置的DEFAULT值。

解决思路是让Spring Data JDBC在字段为null时不将其加入SQL语句,这样数据库就会自动使用DEFAULT值,具体有两种常用方式:

方式1:使用@Column注解控制字段的可插入性

在实体类的目标字段上添加@Column(insertable = false),当字段值为null时,该字段不会出现在INSERT语句中:

import org.springframework.data.relational.core.mapping.Column;
import java.time.LocalDateTime;

public class User {
    private Long id;
    private String name;
    
    // 当createdAt为null时,INSERT语句不包含该字段,数据库使用DEFAULT值
    @Column(insertable = false)
    private LocalDateTime createdAt;
    
    // getter/setter省略
}

如果是更新操作时不想覆盖默认值,可以再加上updatable = false。

方式2:使用JdbcAggregateTemplate自定义保存逻辑

如果需要更灵活的控制,可以直接使用JdbcAggregateTemplate,手动指定要保存的字段:

@Service
public class UserService {
    private final JdbcAggregateTemplate jdbcAggregateTemplate;

    public UserService(JdbcAggregateTemplate jdbcAggregateTemplate) {
        this.jdbcAggregateTemplate = jdbcAggregateTemplate;
    }

    public User saveUserWithDefaults(User user) {
        AggregateChange<User> change = AggregateChange.forInsert(user);
        // 只添加非null的字段到INSERT语句
        if (user.getName() != null) {
            change.getMutableEntity().set("name", user.getName());
        }
        // createdAt不添加,让数据库用DEFAULT
        return jdbcAggregateTemplate.save(change);
    }
}

问题2:自定义@Query INSERT语句使用DEFAULT无法获取插入行的ID

自定义@Query的INSERT语句默认不会自动返回生成的主键,需要手动处理主键的获取,这里有两种通用方案:

方式1:使用JdbcTemplate结合KeyHolder(跨数据库通用)

这种方式不受数据库类型限制,是最通用的做法:

  1. 先定义自定义Repository接口:
public interface UserRepository extends CrudRepository<User, Long>, CustomUserRepository {
}

public interface CustomUserRepository {
    Long saveUserWithDefault(String name);
}
  1. 实现自定义Repository:
@Repository
public class CustomUserRepositoryImpl implements CustomUserRepository {
    private final JdbcTemplate jdbcTemplate;

    public CustomUserRepositoryImpl(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public Long saveUserWithDefault(String name) {
        String sql = "INSERT INTO users(name, created_at) VALUES (?, DEFAULT)";
        KeyHolder keyHolder = new GeneratedKeyHolder();
        
        jdbcTemplate.update(connection -> {
            // 指定要返回的主键列名
            PreparedStatement ps = connection.prepareStatement(sql, new String[]{"id"});
            ps.setString(1, name);
            return ps;
        }, keyHolder);
        
        // 获取生成的主键ID
        return keyHolder.getKey().longValue();
    }
}

方式2:利用数据库的RETURNING语法(针对PostgreSQL等支持的数据库)

如果你的数据库支持RETURNING(比如PostgreSQL),可以直接在@Query里返回主键:

public interface UserRepository extends CrudRepository<User, Long> {
    @Modifying
    @Query("INSERT INTO users(name, created_at) VALUES (:name, DEFAULT) RETURNING id")
    Long saveUserWithDefault(@Param("name") String name);
}

注意:MySQL不支持这种语法,MySQL可以用SELECT 267988,但需要分两次执行语句,不如KeyHolder方式通用。

内容的提问来源于stack exchange,提问作者Tobi Akinyemi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 15:47:29