views:

368

answers:

3

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

+4  A: 

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.

rcampbell
but why would it set the field to null?
Dominik
I updated the answer to explain why the field might be null. The most common reason is that you have records in your database which predate the addition of your **amsData** field.
rcampbell
I just stored a new entity with a not null (but empty) AmsData Object and I get the same result again. And yes, the AmsData class is annotated embeddable.
Dominik
When storing a new entity with a AmsData Object containing some data, I get a not null AmsData Object from JPA, when storing a emtpy AmsData Object I get a null Object
Dominik
Take a look at this: http://stackoverflow.com/questions/1324266/can-i-make-an-embedded-hibernate-entity-non-nullable
rcampbell
A: 

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:

http://stackoverflow.com/questions/1027385/empty-constructors-and-setters-on-jpa-entites/1033822#1033822

Chad Okere
The embedded object also has only columns and no associations, which could be fetched eager.
Dominik
+1  A: 

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

Dominik
Had the same problem some weeks ago. That will do the trick. +1
Willi