使用@MappedSuperclass后查询Moviment触发EntityNotFoundException问题
看起来你遇到的问题大概率和@Version注解的意外引入有关,另外hashCode方法的变化也可能埋下隐患,我来帮你拆解分析:
1. 最可能的根源:@Version注解的意外引入
对比修改前后的代码能发现关键差异:
- 原
Espai类中的version只是普通字段,没有@Version注解 - 抽取到
BaseEntity后,version被添加了@Version注解,成为JPA的乐观锁版本字段
JPA对@Version字段有严格要求:
- 字段值不能为null(数据库对应列不能存在null值)
- 每次实体更新时,JPA会自动递增该字段值
如果你的a_espai表中原本存在version为null的记录,当JPA加载这些记录时,会因为@Version字段不符合规范,导致实体无法正确实例化,进而触发EntityNotFoundException(关联的Moviment自然找不到有效的Espai实体)。
解决方法:
- 如果你不需要乐观锁功能,直接移除
BaseEntity中version字段的@Version注解,保持和原代码逻辑一致:
@MappedSuperclass public class BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; protected Integer version; // 去掉@Version注解 public Integer getId() { return id; } }
- 如果确实需要乐观锁,先将数据库中
a_espai表的version列所有null值更新为默认值(比如1),确保所有记录都有有效的版本号。
2. hashCode方法的潜在问题
原Espai类中,当servei为null时hashCode返回1;修改后返回0。虽然这不会直接触发EntityNotFoundException,但可能影响JPA一级缓存的实体识别:
- 多个
servei为null的Espai实体hashCode相同(都是0),会被放到缓存的同一个哈希桶中 - 虽然equals方法会通过id区分不同实体,但可能导致缓存查找效率下降,极端情况引发实体匹配混乱
优化建议:
hashCode方法应该基于稳定且唯一的属性,优先用实体的id(主键唯一且不可变),避免依赖关联实体的属性(比如servei.getId())——关联实体可能是懒加载代理,调用其方法可能触发额外查询,甚至引发LazyInitializationException。
修改后的hashCode方法示例:
@Override public int hashCode() { return getId() != null ? getId().hashCode() : super.hashCode(); }
3. equals方法的兼容性优化
当前的equals方法逻辑是正确的,但要注意JPA代理实体的情况:如果Espai被懒加载代理(比如其他关联中使用懒加载),obj instanceof Espai依然会返回true(代理类是Espai的子类),所以当前逻辑没问题。不过可以进一步优化,确保代理对象也能正确比较:
@Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; // 处理Hibernate代理对象,获取真实实体类 Class<?> objClass = obj.getClass(); if (objClass.getName().contains("HibernateProxy")) { objClass = org.hibernate.Hibernate.getClass(obj); } if (!Espai.class.equals(objClass)) return false; Espai other = (Espai) obj; return getId() != null && getId().equals(other.getId()); }
(注:如果使用其他JPA实现,需要对应调整代理类的处理方式)
总结
优先检查@Version注解的问题,这是最可能导致EntityNotFoundException的原因。调整后再验证hashCode和equals方法的优化,确保实体的标识逻辑稳定可靠。
内容的提问来源于stack exchange,提问作者Joe

