Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange JPA behaviour, initialized field is null

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).

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

...
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.

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).

like image 758
Dominik Avatar asked Jan 16 '10 08:01

Dominik


3 Answers

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.

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.

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

"...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]

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.

like image 102
Robert Campbell Avatar answered Oct 15 '22 05:10

Robert Campbell


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.

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

like image 28
Dominik Avatar answered Oct 15 '22 04:10

Dominik


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

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

Empty constructors and setters on JPA Entites

like image 1
Chad Okere Avatar answered Oct 15 '22 03:10

Chad Okere