Spring Boot 2中如何通过JPA每日重置PostgreSQL序列?
实现每日重置的「日期+序列ID」生成规则(Spring Boot 2 + JPA + PostgreSQL)
当然可以搞定!这种「日期+每日自增序列」的业务需求其实挺常见的,结合Spring Boot 2、JPA、Hibernate和PostgreSQL的特性,我们有几种靠谱的实现方式,我给你一步步拆解清楚:
方案一:PostgreSQL序列 + Spring定时任务重置
利用PostgreSQL原生的序列特性,配合Spring定时任务每天零点重置序列,是最直接的方式。
1. 先在数据库创建序列
执行这条SQL创建一个初始值为1的序列:
CREATE SEQUENCE daily_seq START 1;
2. 自定义Hibernate ID生成器
我们需要写一个自定义的ID生成器,把当前日期和序列值拼接起来。Hibernate允许我们实现IdentifierGenerator接口来定制ID生成逻辑:
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.id.IdentifierGenerator; import java.io.Serializable; import java.time.LocalDate; import java.time.format.DateTimeFormatter; public class DailySequenceIdGenerator implements IdentifierGenerator { // 定义日期格式,比如20240520 private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd"); @Override public Serializable generate(SharedSessionContractImplementor session, Object object) { // 调用PostgreSQL的nextval获取下一个序列值 String seqQuery = "SELECT nextval('daily_seq')"; Long seqValue = session.createNativeQuery(seqQuery, Long.class).uniqueResult(); // 拼接日期和序列值,比如202405200001 String dateStr = LocalDate.now().format(DATE_FORMAT); return dateStr + String.format("%04d", seqValue); } }
然后在你的实体类的ID字段上配置这个生成器:
import javax.persistence.*; @Entity public class YourBusinessEntity { @Id @GenericGenerator( name = "daily_seq_generator", strategy = "com.your.package.path.DailySequenceIdGenerator" // 替换成你的类路径 ) @GeneratedValue(generator = "daily_seq_generator") private String id; // 其他业务字段、getter/setter... }
3. 定时任务每日重置序列
接下来需要每天零点把序列重置回1,用Spring的@Scheduled定时任务就能轻松实现:
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; @Component public class DailySequenceResetTask { @PersistenceContext private EntityManager entityManager; // 每天零点执行,cron表达式可以根据需求调整 @Scheduled(cron = "0 0 0 * * ?") public void resetDailySequence() { // PostgreSQL重置序列的SQL语句 String resetQuery = "ALTER SEQUENCE daily_seq RESTART WITH 1"; entityManager.createNativeQuery(resetQuery).executeUpdate(); } }
别忘了在Spring Boot的启动类上加上@EnableScheduling注解,开启定时任务支持!
方案二:用数据库表存储每日序列(无需定时任务)
如果不想依赖PostgreSQL的序列对象,也可以建一张专门的表来记录每天的序列值,每次生成ID时自动判断日期,新的一天就从1开始,完全不需要定时任务。
1. 创建序列记录表
先建一张表,存储日期和当前序列值:
CREATE TABLE daily_sequence ( record_date DATE PRIMARY KEY, current_seq BIGINT NOT NULL DEFAULT 1 );
2. 自定义ID生成器(基于表操作)
修改生成器类,用PostgreSQL的UPSERT语法(INSERT ... ON CONFLICT)来原子性地获取并更新序列值:
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.id.IdentifierGenerator; import java.io.Serializable; import java.time.LocalDate; import java.time.format.DateTimeFormatter; public class DailyTableSequenceIdGenerator implements IdentifierGenerator { private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd"); @Override public Serializable generate(SharedSessionContractImplementor session, Object object) { LocalDate today = LocalDate.now(); // UPSERT:如果今日记录存在则序列+1,不存在则插入初始值1,返回当前序列值 String upsertQuery = """ INSERT INTO daily_sequence (record_date, current_seq) VALUES (:today, 1) ON CONFLICT (record_date) DO UPDATE SET current_seq = daily_sequence.current_seq + 1 RETURNING current_seq """; Long seqValue = session.createNativeQuery(upsertQuery, Long.class) .setParameter("today", today) .uniqueResult(); String dateStr = today.format(DATE_FORMAT); return dateStr + String.format("%04d", seqValue); } }
然后实体类的ID配置和方案一一样,只是把生成器换成这个类就行。
关键注意事项
- 并发安全:两种方案都是线程安全的——PostgreSQL的序列本身是原子性的,方案二的
UPSERT语句也是原子操作,不用担心并发场景下的序列重复问题。 - 时区一致性:一定要确保应用服务器和数据库的时区一致,避免出现日期错位的情况。可以在Spring Boot配置文件中设置
spring.jpa.properties.hibernate.jdbc.time_zone=Asia/Shanghai(替换成你们业务用的时区)。 - 序列位数调整:根据你们每日的最大业务量,调整
String.format("%04d", seqValue)中的位数——比如如果每天最多9999条数据就用4位,要是业务量更大就改成5位(%05d)。
内容的提问来源于stack exchange,提问作者robert trudel
相关产品推荐
相关产品推荐

