如何使用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
相关产品推荐
相关产品推荐

