异步方法中使用容器管理EntityManager的无状态会话Bean触发数据库主键违例异常
问题分析与解决方案
你的问题核心在于无状态Bean的异步方法并发执行时,Hibernate序列生成器重复分配ID,同时需要保留容器管理事务。以下是针对性的解决步骤:
1. 修正序列生成器配置(根源解决)
主键冲突的常见原因是Hibernate的序列缓存与数据库序列配置不一致:
- 检查实体类的
@SequenceGenerator注解,确保allocationSize值与数据库序列的INCREMENT BY完全匹配。 - 如果不希望Hibernate预取ID(牺牲少量性能换绝对唯一),可将
allocationSize设为1。
示例(以LoeschProtokoll为例):
@Entity public class LoeschProtokoll { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "loeschProtokollSeq") @SequenceGenerator( name = "loeschProtokollSeq", sequenceName = "LOESCH_PROTOKOLL_SEQ", // 对应数据库中的序列名 allocationSize = 1 // 必须和数据库序列的INCREMENT BY一致 ) private Long id; // 其他字段与方法 }
2. 同步异步方法的执行(避免并发冲突)
无状态Bean是多实例的,普通同步块无法生效。可通过以下方式实现串行执行:
方案:将Loeschlauf改为Singleton Bean并添加锁
Singleton Bean默认是线程安全的,配合@Lock注解可保证异步方法串行执行,同时保留容器管理事务:
@Singleton @Lock(LockType.WRITE) // 同一时间仅允许一个线程执行标注方法 public class Loeschlauf { private static Logger log = LoggerFactory.getLogger(Loeschlauf.class); @PersistenceContext protected EntityManager entityManager; @Asynchronous public void run() { // 原有业务逻辑不变 } // 其他方法保持不变 }
@Lock(LockType.WRITE)确保所有异步调用的run方法串行执行,避免多个线程同时操作导致的序列ID冲突。- 容器会为每个
run调用分配独立事务,完全保留容器管理事务的特性。
3. 优化EntityManager操作(额外建议)
在behandeleIndex方法中,查询返回的PersonenIndex已是托管状态,无需重复persist,可直接修改并判断:
private void behandeleIndex(PersonenIndex index) { index.getTransaktionen().clear(); // 移除多余的persist操作,因为index已处于EntityManager托管状态 if (index.isLeer()) { entityManager.remove(index); } }
内容的提问来源于stack exchange,提问作者André
相关产品推荐
相关产品推荐

