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

如何使用Hibernate注解实现含自定义主键生成逻辑的动态插入查询?

使用Hibernate注解实现自定义Max+1 ID的插入操作

这问题我之前也碰到过,用Hibernate实现这种自定义ID生成的插入其实不难,但要注意并发坑,下面给你一步步讲两种实现方式:

方式一:业务层手动生成ID(直观易理解)

1. 定义Customer实体类

先给实体类加上Hibernate注解,注意customer_id作为主键,我们手动赋值,所以不需要@GeneratedValue:

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "customers")
public class Customer {

    @Id
    @Column(name = "customer_id")
    private Integer customerId;

    @Column(name = "firstname")
    private String firstname;

    @Column(name = "surname")
    private String surname;

    // 空构造函数(Hibernate要求)
    public Customer() {}

    // 带参构造函数
    public Customer(Integer customerId, String firstname, String surname) {
        this.customerId = customerId;
        this.firstname = firstname;
        this.surname = surname;
    }

    // Getter和Setter方法,这里省略,记得自己补充
}

2. 实现插入逻辑

在DAO或Service层,先查询当前最大的customer_id,加1后赋值给新实体,再保存。重点要处理并发问题,否则多线程插入时可能会出现主键冲突:

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.transaction.annotation.Transactional;

public class CustomerService {

    private final SessionFactory sessionFactory;

    // 构造注入SessionFactory(如果用Spring的话)
    public CustomerService(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    @Transactional // 必须加事务,确保查询和插入在同一个事务中
    public void addCustomer(String firstname, String surname) {
        Session session = sessionFactory.getCurrentSession();

        // 用原生SQL加FOR UPDATE锁定表,防止并发时重复获取相同ID
        String maxIdSql = "SELECT MAX(customer_id) + 1 FROM customers FOR UPDATE";
        Integer newCustomerId = session.createNativeQuery(maxIdSql, Integer.class)
                                      .uniqueResult();

        // 如果表是空的,maxId会是null,默认设为1
        if (newCustomerId == null) {
            newCustomerId = 1;
        }

        // 创建并保存Customer实例
        Customer customer = new Customer(newCustomerId, firstname, surname);
        session.save(customer);
    }
}

方式二:自定义Hibernate ID生成器(更优雅)

如果不想在业务层写ID生成逻辑,可以自定义Hibernate的ID生成器,让Hibernate自动帮你生成ID:

1. 实现IdentifierGenerator接口

写一个自定义生成器类,实现Hibernate的IdentifierGenerator接口:

import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentifierGenerator;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class MaxPlusOneIdGenerator implements IdentifierGenerator {

    @Override
    public Serializable generate(SharedSessionContractImplementor session, Object entity) {
        try (Connection connection = session.getJdbcConnectionAccess().obtainConnection();
             PreparedStatement ps = connection.prepareStatement("SELECT MAX(customer_id)+1 FROM customers FOR UPDATE");
             ResultSet rs = ps.executeQuery()) {

            if (rs.next()) {
                Integer maxId = rs.getInt(1);
                return maxId != null ? maxId : 1;
            }
        } catch (Exception e) {
            throw new RuntimeException("生成Customer ID失败", e);
        }
        return 1;
    }
}

2. 在实体类中配置自定义生成器

修改Customer实体类的customerId字段,指定使用我们自定义的生成器:

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.GeneratedValue;

@Entity
@Table(name = "customers")
public class Customer {

    @Id
    @Column(name = "customer_id")
    @GenericGenerator(
        name = "maxPlusOneGenerator",
        strategy = "com.yourpackage.MaxPlusOneIdGenerator" // 替换成你的类全路径
    )
    @GeneratedValue(generator = "maxPlusOneGenerator")
    private Integer customerId;

    @Column(name = "firstname")
    private String firstname;

    @Column(name = "surname")
    private String surname;

    // 构造函数、Getter和Setter省略
}

3. 插入操作简化

现在插入的时候直接保存实体就行,ID会由Hibernate自动生成:

@Transactional
public void addCustomer(String firstname, String surname) {
    Session session = sessionFactory.getCurrentSession();
    Customer customer = new Customer();
    customer.setFirstname(firstname);
    customer.setSurname(surname);
    session.save(customer);
}

重要注意事项

  • 并发安全:两种方式都用了FOR UPDATE来锁定表,避免多线程同时获取相同的max ID导致主键冲突。如果不加锁,高并发场景下肯定会出问题。
  • 性能问题:这种Max+1的方式每次插入都要查询一次表,性能不如数据库原生的自增(比如MySQL的AUTO_INCREMENT)或序列(Oracle的SEQUENCE),如果能改用原生生成策略会更好。
  • 空表处理:一定要处理表为空的情况,此时MAX(customer_id)会返回null,需要默认设为1。

内容的提问来源于stack exchange,提问作者Thomas George

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:59:32