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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 17:18:00