Spring Data JPA批量插入时如何自动生成基于PostgreSQL序列的自定义ID
有两种成熟的实现方案可以解决批量生成非主键序列ID的问题,无需逐行查询数据库序列:
方案一:基于Hibernate值生成机制自动实现(无侵入业务代码)
Hibernate内置的SequenceStyleGenerator天然支持批量预取序列值的能力,你可以通过自定义非主键值生成注解复用该能力,完全自动完成otherId的生成:
- 自定义生成注解
import org.hibernate.annotations.ValueGenerationType import java.lang.annotation.* @ValueGenerationType(generatedBy = OtherIdGenerator::class) @Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY_GETTER) @Retention(AnnotationRetention.RUNTIME) annotation class GeneratedOtherId
- 实现序列生成器
import org.hibernate.engine.spi.SharedSessionContractImplementor import org.hibernate.generator.BeforeExecutionGenerator import org.hibernate.generator.EventType import org.hibernate.id.SequenceStyleGenerator import org.hibernate.type.BasicType import org.hibernate.service.ServiceRegistry import java.util.* class OtherIdGenerator : BeforeExecutionGenerator { private lateinit var internalSeqGenerator: SequenceStyleGenerator // 自定义你的前缀 private val idPrefix = "MY_PREFIX_" override fun initialize( sqlType: Int, parameters: Properties, serviceRegistry: ServiceRegistry ) { val seqConfig = Properties().apply { // 替换成你的PG序列名 setProperty(SequenceStyleGenerator.SEQUENCE_PARAM, "my_entity_other_id_seq") // 批量预取大小,必须和PG序列定义的INCREMENT BY值完全一致 setProperty(SequenceStyleGenerator.INCREMENT_PARAM, "100") setProperty(SequenceStyleGenerator.INITIAL_PARAM, "1") } internalSeqGenerator = SequenceStyleGenerator() internalSeqGenerator.initialize(BasicType.LONG, seqConfig, serviceRegistry) } override fun generate( session: SharedSessionContractImplementor, entity: Any, currentValue: Any?, eventType: EventType ): String { val seqValue = internalSeqGenerator.generate(session, entity) as Long return "${idPrefix}${seqValue}" } override fun getEventTypes(): EnumSet<EventType> = EnumSet.of(EventType.INSERT) }
- 改造实体类
@Entity class MyEntity { @Id @GeneratedValue var id: UUID? = null @GeneratedOtherId @Column(unique = true, nullable = false, updatable = false) val otherId: String? = null }
该方案下直接调用JPA的saveAll方法即可自动批量生成otherId,每预取一批序列值只会发起1次数据库查询。
方案二:基于JPA自定义批量方法手动实现(兼容性更强)
如果你不想依赖Hibernate私有API,可以封装批量保存方法手动预取序列值,逻辑完全可控:
- 改造实体类,将
otherId改为可赋值
@Entity class MyEntity( var otherId: String? = null ) { @Id @GeneratedValue var id: UUID? = null }
- 在Repository中扩展批量保存方法
@Repository interface MyEntityRepository : JpaRepository<MyEntity, UUID> { // 批量获取指定数量的序列值 @Query(value = "SELECT nextval('my_entity_other_id_seq') FROM generate_series(1, :count)", nativeQuery = true) fun listNextSequenceValues(count: Int): List<Long> fun batchSaveWithGeneratedOtherId(entities: List<MyEntity>): List<MyEntity> { if (entities.isEmpty()) return emptyList() // 一次性获取所有需要的序列值 val sequenceValues = listNextSequenceValues(entities.size) // 为每个实体赋值otherId entities.forEachIndexed { index, entity -> entity.otherId = "MY_PREFIX_${sequenceValues[index]}" } // 批量保存 return saveAll(entities) } }
该方案下业务代码调用batchSaveWithGeneratedOtherId即可完成批量插入,全程仅发起1次序列查询+1次批量插入。
注意事项
- 若使用方案一,必须保证PostgreSQL序列的
INCREMENT BY配置和生成器的INCREMENT_PARAM值完全一致,否则会出现ID冲突 - 序列预取大小可根据你的平均批量插入规模调整,数值越大批量插入时查询序列的次数越少
- 两种方案都依赖PostgreSQL序列的全局唯一性,无需担心
otherId重复问题
内容的提问来源于stack exchange,提问作者Deedee Megadoodoo
相关产品推荐
相关产品推荐

