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(跨数据库通用)
这种方式不受数据库类型限制,是最通用的做法:
- 先定义自定义Repository接口:
public interface UserRepository extends CrudRepository<User, Long>, CustomUserRepository { } public interface CustomUserRepository { Long saveUserWithDefault(String name); }
- 实现自定义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
相关产品推荐
相关产品推荐

