奇怪的 JPA 行为,初始化字段为空

2022-09-02 21:25:44

我正在观察一个非常奇怪的实体类行为,并在JPA(休眠实体管理器 3.3.1.ga)加载该类的对象。类具有一个(嵌入式)字段,该字段在声明中初始化。字段的 setter 实现 null 检查(即,在设置 null 值时会引发异常)。

...
@Entity
public class Participant extends BaseEntity implements Comparable<Participant> {
   ...
    @Embedded
 private AmsData amsData = new AmsData();

 public void setAmsData(AmsData amsData) {
  Checks.verifyArgNotNull(amsData, "amsdata");
  this.amsData = amsData;
 }
    ...
}

当我使用 JPA 获取此对象时,如果 db 中没有嵌入对象中指定的字段的数据,则该字段为 null。

...
public class ParticipantJpaDao implements ParticipantDao {
 @PersistenceContext
 private EntityManager em;

 @Override
 public Participant getParticipant(Long id) {
  return em.find(Participant.class, id);
 }
    ...
}

我在字段上使用观察点调试了该过程(当访问或修改字段时应停止),并且在初始化字段时看到一个修改,但是当我从 find 调用获得结果时,该字段为 null。

谁能解释一下,为什么会这样?我如何确保该字段不为空,当数据库中没有嵌入对象的字段的数据时也是如此(除了在查找调用后手动设置它)。


答案 1

JPA规范没有明确说明如何处理一组表示可嵌入对象的列,这些列都是空的。它可以发出空引用或具有所有空字段的对象实例的信号。在这种情况下,Hibernate 选择空引用,尽管其他 JPA 实现可能会选择后者。

您的 setter 从不被调用的原因是 Hibernate 通过反射访问您的字段,绕过您实现的 setter。它之所以这样做,是因为您利用了基于字段的访问,而不是基于属性的访问。

Chad的答案将提供您正在寻找的功能,但有一个警告(见下文)。

"...实体的持久状态由持久性提供程序运行时[1]通过JavaBeans样式的属性访问器或实例变量访问。单一访问类型(字段或属性访问)适用于实体层次结构。使用注释时,将映射注释放置在实体类的持久性字段或持久性属性上时,会将访问类型分别指定为基于字段或基于属性的访问...”[ejb3 持久性规范]

因此,通过将注释向下移动到 setter,您可以告诉 JPA 您希望使用基于属性的访问,而不是基于字段的访问。但是,您应该知道,基于字段的访问(如您当前实现的那样)优先于基于属性的访问。不鼓励基于属性的访问有几个原因,但一个是它们迫使您为所有持久性实体字段添加 getter 和 setter,但您可能不希望这些相同的字段容易受到外部客户端的更改。换句话说,使用 JPA 基于属性的访问会迫使您削弱实体的封装。


答案 2

答案是(感谢 rcampell),如果嵌入对象的所有数据都为 null(在 db 中),则嵌入对象也将为 null,尽管在声明中初始化时也是如此。唯一的解决方案似乎是手动设置对象。

@Override
public Participant getParticipant(Long id) {
    Participant participant = em.find(Participant.class, id);
    if(participant != null && participant.getAmsData() == null)
    {
        participant.setAmsData(new AmsData());
    }
    return participant;
}

我仍然觉得很奇怪...


推荐