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

Spring Data JPA批量插入时如何自动生成基于PostgreSQL序列的自定义ID

有两种成熟的实现方案可以解决批量生成非主键序列ID的问题,无需逐行查询数据库序列:

方案一:基于Hibernate值生成机制自动实现(无侵入业务代码)

Hibernate内置的SequenceStyleGenerator天然支持批量预取序列值的能力,你可以通过自定义非主键值生成注解复用该能力,完全自动完成otherId的生成:

  1. 自定义生成注解
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
  1. 实现序列生成器
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)
}
  1. 改造实体类
@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,可以封装批量保存方法手动预取序列值,逻辑完全可控:

  1. 改造实体类,将otherId改为可赋值
@Entity
class MyEntity(
    var otherId: String? = null
) {
    @Id
    @GeneratedValue
    var id: UUID? = null
}
  1. 在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 10:18:04