Spring/JPA复合主键按指定列分组生成序列问题咨询
解决方案
核心思路
你原来使用的SequenceStyleGenerator是基于数据库全局序列实现的,天生不支持按指定字段分组递增的需求,需要修改自定义生成器的生成逻辑:在生成No值时,先从当前待插入实体中拿到Code字段的值,查询该Code对应的最大No,累加后拼接前缀返回即可。
修改后的自定义生成器代码
public class GroupedStringSequenceGenerator extends SequenceStyleGenerator { public static final String VALUE_PREFIX_PARAMETER = "valuePrefix"; public static final String VALUE_PREFIX_DEFAULT = ""; private String valuePrefix; public static final String NUMBER_FORMAT_PARAMETER = "numberFormat"; public static final String NUMBER_FORMAT_DEFAULT = "%d"; private String numberFormat; // 分组字段名,可根据需求调整为可配置参数 private static final String GROUP_COLUMN_NAME = "Code"; @Override public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException { // 1. 获取当前待插入实体的Code分组值 Example entity = (Example) object; String currentCode = entity.getCode(); if (currentCode == null || currentCode.trim().isEmpty()) { throw new HibernateException("分组字段Code不能为空"); } // 2. 查询当前Code对应的最大No值 String hql = String.format("select e.No from Example e where e.%s = :code order by e.No desc limit 1", GROUP_COLUMN_NAME); Query<String> query = session.createQuery(hql, String.class); query.setParameter("code", currentCode); String maxNo = query.uniqueResult(); // 3. 计算下一个序号 int nextNum = 1; if (maxNo != null && maxNo.startsWith(valuePrefix)) { String numStr = maxNo.substring(valuePrefix.length()); try { nextNum = Integer.parseInt(numStr) + 1; } catch (NumberFormatException e) { throw new HibernateException("No字段格式非法: " + maxNo, e); } } // 4. 拼接返回最终序号 return valuePrefix + String.format(numberFormat, nextNum); } @Override public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException { super.configure(LongType.INSTANCE, params, serviceRegistry); valuePrefix = ConfigurationHelper.getString(VALUE_PREFIX_PARAMETER, params, VALUE_PREFIX_DEFAULT); numberFormat = ConfigurationHelper.getString(NUMBER_FORMAT_PARAMETER, params, NUMBER_FORMAT_DEFAULT); } }
实体类调整
仅需要把生成器策略替换为修改后的自定义生成器即可,其余原有逻辑保持不变:
@Entity @Table @IdClass(...) public class Example { @Id @Column private String Code; @Id @Column @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "XyzIdGenerator") @GenericGenerator(name = "XyzIdGenerator", strategy = "com.mycompany.myapp.id.GroupedStringSequenceGenerator", parameters = { @Parameter(name = "GroupedStringSequenceGenerator.VALUE_PREFIX_PARAMETER", value = "V"), @Parameter(name = "GroupedStringSequenceGenerator.NUMBER_FORMAT_PARAMETER", value = "%01d") }) private String No; // 原有getter、setter保持不变 }
注意事项
- 并发场景下需要做冲突处理:如果多个线程同时插入同Code的记录,可能出现主键重复,建议捕获重复主键异常后重试,或者在查询最大No的逻辑中加数据库悲观锁保证原子性。
- 如果需要适配多个不同实体的分组生成需求,可以把分组字段名也做成可配置参数,从
@Parameter注解中传入,无需硬编码Code字段。
内容的提问来源于stack exchange,提问作者Raghav
相关产品推荐
相关产品推荐

