Spring Boot+MySQL环境下如何重置Hibernate序列,每年财年从1开始?
解决方案
一、先明确核心问题
如果序列生成的是数据库主键,绝对不能让其每年从1重启——主键必须全局唯一,否则必然出现重复冲突。你之前的错误就源于此:业务表中已有历史主键值,重置序列到1后,新插入的记录会重复使用已存在的主键。
如果序列生成的是财年维度的业务编号(比如FY2024-001这类),那可以按财年重置序列,同时数据库主键用独立的自增列保证全局唯一。
下面分两种场景给出具体实现:
二、场景1:生成财年维度的业务编号(推荐)
1. 数据库层处理
MySQL没有原生序列,Hibernate默认用hibernate_sequence表模拟。我们可以创建按财年区分的序列表,比如fy_sequence,结构如下:
CREATE TABLE fy_sequence ( fiscal_year VARCHAR(4) PRIMARY KEY, -- 财年,比如"2024" next_val BIGINT NOT NULL DEFAULT 1 );
2. 自定义序列生成器
在Spring Boot中自定义Hibernate序列生成器,实现按财年获取/重置序列值:
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.id.IdentifierGenerator; import java.io.Serializable; import java.time.LocalDate; public class FiscalYearSequenceGenerator implements IdentifierGenerator { @Override public Serializable generate(SharedSessionContractImplementor session, Object obj) { // 获取当前财年(若财年为非自然年,比如4月到次年3月,需调整此处逻辑) String fiscalYear = String.valueOf(LocalDate.now().getYear()); // 插入或更新序列值:不存在则初始化,存在则自增 String sql = "INSERT INTO fy_sequence (fiscal_year, next_val) VALUES (:fy, 1) " + "ON DUPLICATE KEY UPDATE next_val = next_val + 1"; session.createNativeQuery(sql) .setParameter("fy", fiscalYear) .executeUpdate(); // 获取更新后的序列值 Long nextVal = (Long) session.createNativeQuery("SELECT next_val FROM fy_sequence WHERE fiscal_year = :fy") .setParameter("fy", fiscalYear) .uniqueResult(); // 返回带财年的业务编号,比如"2024-001" return fiscalYear + "-" + String.format("%03d", nextVal); } }
3. 实体类中使用
在实体类的业务编号字段上指定自定义生成器:
import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import org.hibernate.annotations.GenericGenerator; @Entity public class BusinessRecord { // 全局唯一主键 @Id @GeneratedValue(strategy = jakarta.persistence.GenerationType.IDENTITY) private Long id; // 财年业务编号 @GeneratedValue(generator = "fy-sequence") @GenericGenerator(name = "fy-sequence", strategy = "com.yourpackage.FiscalYearSequenceGenerator") private String fiscalYearNumber; // 其他字段... }
三、场景2:必须用序列作为主键(需复合主键)
如果业务要求主键是财年+序列值的组合(全局唯一),则需要使用复合主键:
1. 定义复合主键类
import jakarta.persistence.Embeddable; import java.io.Serializable; @Embeddable public class FiscalYearId implements Serializable { private String fiscalYear; private Long sequence; // 构造函数、getter、setter、equals和hashCode方法 }
2. 实体类使用复合主键
import jakarta.persistence.EmbeddedId; import jakarta.persistence.Entity; @Entity public class BusinessRecord { @EmbeddedId private FiscalYearId id; // 其他字段... }
3. 插入时生成主键
插入记录时,先获取当前财年的最大sequence值,加1后作为新的sequence值:
@Service public class BusinessRecordService { @Autowired private EntityManager entityManager; public void createRecord(BusinessRecord record) { String fiscalYear = String.valueOf(LocalDate.now().getYear()); // 获取当前财年的最大sequence值 Long maxSeq = (Long) entityManager.createQuery("SELECT MAX(r.id.sequence) FROM BusinessRecord r WHERE r.id.fiscalYear = :fy") .setParameter("fy", fiscalYear) .getSingleResult(); Long nextSeq = maxSeq == null ? 1 : maxSeq + 1; FiscalYearId id = new FiscalYearId(); id.setFiscalYear(fiscalYear); id.setSequence(nextSeq); record.setId(id); entityManager.persist(record); } }
四、避免主键重复的关键注意事项
- 绝对不要在全局唯一的主键列上使用按财年重置的序列,这违反主键唯一性约束。
- 若要重置序列,必须确保当前财年的业务表中没有该序列值的记录,或者序列值是财年维度的局部唯一(配合财年组成复合主键)。
- 不要手动截断序列表,正确的做法是:
- 若使用财年序列表,直接更新对应财年的
next_val为1(仅在财年切换时执行)。 - 若使用复合主键,财年切换时无需额外操作,插入时自动从1开始计数。
- 若使用财年序列表,直接更新对应财年的
内容的提问来源于stack exchange,提问作者param desai
相关产品推荐
相关产品推荐

