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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2076644/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 19:17:35  来源:igfitidea点击:

Strange JPA behaviour, initialized field is null

javahibernatejpa

提问by Dominik

I'm observing a very strange behaviour with an entity class and loading an object of this class whith JPA (hibernate entitymanager 3.3.1.ga). The Class has a (embedded) field, that is initialized in the declaration. The setter of the field implements a null check (i.e. would throw an exception when a null value is set).

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

...
@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;
 }
    ...
}

When I get this object with JPA, the field is null, if there is no data in the db for the fields specified in the embedded object.

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

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

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

I debugged the process with a watchpoint on the field (should halt when the field is accessed or modified), and I see one modification when the field is initialized, but when I get the result from the find call, the field is null.

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

Can anybody explain, why this is so? How can I ensure, that the field is not null, also when there is no data for the embedded object's fields in the db (besides from setting it manually after the find call).

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

采纳答案by Robert Campbell

The JPA specification doesn't explicitly say how to handle a set of columns representing an embeddable object which are all empty. It could signal a null reference, or an object instance with all null fields. Hibernate chooses a null reference in this case, though other JPA implementations may pick the later.

JPA 规范没有明确说明如何处理一组表示所有空的可嵌入对象的列。它可以表示一个空引用,或者一个包含所有空字段的对象实例。在这种情况下,Hibernate 选择空引用,但其他 JPA 实现可能会选择后者。

The reason why your setter is never called is because Hibernate is accessing your field via reflection, bypassing the setter you implemented. It's doing this because you utilize field-based access rather than property-based access.

您的 setter 从未被调用的原因是因为 Hibernate 正在通过反射访问您的字段,绕过您实现的 setter。这样做是因为您使用基于字段的访问而不是基于属性的访问。

Chad'sanswer would provide the functionality you're looking for, but there is a caveat (see below).

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

"...The persistent state of an entity is accessed by the persistence provider runtime[1] either via JavaBeans style property accessors or via instance variables. A single access type (field or property access) applies to an entity hierarchy. When annotations are used, the placement of the mapping annotations on either the persistent fields or persistent properties of the entity class specifies the access type as being either field- or property-based access respectively..." [ejb3 persistence spec]

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

so by moving the annotations down to the setter, you are telling JPA that you want to used property-based access instead of field-based access. You should know, however, that field-based access - as you currently implement it - is preferred over property-based access. There are a couple reasons why property-based access is discouraged, but one is that they you're forced to add getters and setters for all of your persistent entity fields, but you may not want those same fields susceptible to mutation by external clients. In other words, using JPA's property-based access forces you to weaken your entity's encapsulation.

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

回答by Dominik

The answer is (thanks to rcampell), if all data of an embedded object is null (in the db), the embedded object will also be null, although when it is initialized in the declaration. The only solution seems to be, setting the object manually.

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

@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;
}

Still feels strange to me ...

我还是觉得很奇怪...

回答by Carlos Nantes

It's 2018 now and I had the same problem in a similiar situation.

现在是 2018 年,我在类似的情况下遇到了同样的问题。

Using your code as example, I solved the problem like this:

以您的代码为例,我解决了这样的问题:

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

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

    public AmsData getAmsData(){
        if(amsData == null){
            amsData = new AmsData();
        }
        return amsData;
    }
    ...
}

回答by Chad Okere

Well, it's possible that your object could be getting constructed twice behind the scenes. JPA implementations will usually set those fields directly.

好吧,您的对象可能会在幕后构造两次。JPA 实现通常会直接设置这些字段。

I think you need to put the annotations on the Getters and setters themselves if you want them to be used. See this answer:

我认为如果您希望使用它们,您需要将注释放在 Getter 和 setter 本身上。看到这个答案:

Empty constructors and setters on JPA Entites

JPA 实体上的空构造函数和设置器