Hibernate中PESSIMISTIC_READ与实体继承场景不兼容问题如何处理
根因说明
你遇到的报错本质是Hibernate多态查询+PostgreSQL行锁限制共同作用的结果:
你当前的继承映射默认使用JOINED策略,父类A和子类B各对应一张数据库表。当你查询父类A的实体时,Hibernate无法提前判定该ID对应的是A本身的实例还是子类B的实例,因此会生成LEFT OUTER JOIN B表 ON A.id = B.id的关联查询语句。加悲观锁时,Hibernate默认会给所有关联的表都加FOR SHARE锁,但PostgreSQL不允许对外连接中可能返回NULL的一侧(也就是B表的关联部分)加行锁,因此直接抛出报错。
对应问题解答
- 是否应该调整为抽象父类加两个具体子类?
该设计优化可以大幅降低这类问题的触发概率。当前父类A是具体类的场景下,只要查询A类型的实体,必然会触发多态关联查询。改为抽象父类+两个具体子类后,业务查询默认可以面向具体子类发起,不会触发外连接,自然不会触发锁冲突。如果现有架构改造成本过高,也可以通过其他方案规避,不是必须调整。 - 加载实体时是否需要提前知晓具体类型?
这是成本最低的规避方案。如果业务侧可以通过ID规则、类型缓存等方式提前确定ID对应的具体实体类型,直接传入具体类型调用sessionManager.get方法,Hibernate不会生成多态外连接查询,完全避免该问题,性能也和普通单表加锁查询一致。 - 该问题是否属于Hibernate的bug?
属于Hibernate针对PostgreSQL方言的适配缺失,不属于功能性bug。PostgreSQL不支持外连接侧加行锁的限制已存在多年,但Hibernate的PostgreSQL10Dialect及更高版本方言目前都没有针对多态继承加锁场景做特殊处理,没有自动生成符合PostgreSQL限制的SQL语句。你可以向Hibernate官方JIRA提交对应特性需求。 - 是否存在其他未考虑到的原因?
可以先检查你的继承映射策略:如果使用SINGLE_TABLE(单表)继承策略,父类子类数据都存在同一张表中,查询时不需要关联外键,不会触发该问题,如果你能接受单表存储的冗余字段,也可以更换继承策略规避。另外如果你的ID生成规则允许父类和子类存在同ID的实例,你当前的临时规避方案可能会查到错误的实例,需要注意业务风险。
更优的替代规避方案
你当前的两次查询方案可以优化为两步查询,减少锁等待时间:
- 先无锁查询该ID对应的实体类型:
String type = session.createQuery("select type(a) from A a where a.id = :id", String.class) .setParameter("id", id) .getSingleResult();
- 再根据返回的具体类型发起加锁查询,不会触发外连接。
如果业务接受锁主表即可的逻辑,也可以先通过原生SQL锁A表的对应行,再无锁查询实体,效果和悲观读一致:
session.createNativeQuery("select id from a where id = ? for share") .setParameter(1, id) .getSingleResult(); A a = sessionManager.get(A.class, id);
内容的提问来源于stack exchange,提问作者Walfrat
相关产品推荐
相关产品推荐

