10

Here is a simplified POJO i have:

@Entity
@Table( name = "Patient" )
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn
(
                name="Discriminator",
                discriminatorType=DiscriminatorType.STRING
                )
@DiscriminatorValue(value="P")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Patient implements Serializable{

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    @Column(name = "ID", unique = true, nullable = false)
    protected Integer ID;

    @ManyToOne(targetEntity = TelephoneType.class, fetch=FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name="IDPhoneType")
    protected TelephoneType phoneType;


    @JsonProperty(required=false, value="phoneType")
    public TelephoneType getPhoneType() {
        return phoneType;
    }
    public void setPhoneType(TelephoneType phoneType) {
        this.phoneType = phoneType;
    }
}

Now here is my class TelephoneType:

@Entity
@Table( name = "TelephoneType" )
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
@JsonAutoDetect(getterVisibility=Visibility.NONE, isGetterVisibility=Visibility.NONE, fieldVisibility=Visibility.NONE)
public class TelephoneType implements Serializable{

private static final long serialVersionUID = -3125320613557609205L;

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name = "ID", unique = true, nullable = false)
private Integer ID;

@Column(name = "Name")
private String name;

@Column(name = "Description")
private String description;

public TelephoneType() {
}

@JsonProperty(value="id")
public int getID() {
    return ID;
}

public void setID(int iD) {
    ID = iD;
}

@JsonProperty(value="name")
public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

@JsonProperty(value="description")
public String getDescription() {
    return description;
}

public void setDescription(String description) {
    this.description = description;
}

}

The reason i use the @JsonAutoDetect annotation in TelephoneType is first to customize the json property names (i needed to deactivate the default jsonautodetect) and also because if I don't, I get an error when fetching the Queue

No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: my.package.Patient["phoneType"]->my.package.TelephoneType_$$_jvste17_13["handler"])

So without the @JsonAutoDetect annotation i get the error and with the annotation no Lazy Loading occurs and the TelephoneType is always loaded in the json response.

I use Criteria to make the query:

return this.entityManager.find(Patient.class, primaryKey);

I also added, as I read in different posts on so, the following in the web.xml of my application (Jersey API):

<filter>
    <filter-name>OpenEntityManagerInViewFilter</filter-name>
    <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>OpenEntityManagerInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Now somehow I surely missed something in my configuration but can't figure out what and we have many @ManyToOne relationships in the db that are slowing down the api considerably (some heavier objects than the one I showed in the example) so I would really appreciate to find a way to activate this lazy loading thing...

jon
  • 910
  • 3
  • 12
  • 34
  • Could you remove your OpenEntityManagerInViewFilter filter and see what happened? – xsalefter May 11 '16 at 05:55
  • if I remove it, this is the classic error I get: "failed to lazily initialize a collection of role: ca.chronometriq.commons.cmqmodel.Patient.TelephoneType, could not initialize proxy - no Session (through reference chain: ca.chronometriq.commons.cmqmodel...." – jon May 11 '16 at 14:10

3 Answers3

9

If you are using JSON then I presume that you are supplying the results through a REST endpoint. What is happening then is you are passing the Patient entity back to the REST service. When the REST service, Jersey in this case, serializes the Patient entity it touches all of the properties, and even walks through them, so as to build as complete a tree as possible. In order to do this, every time Jersey hits a property that's not yet initialized, Hibernate makes another call back to the database. This is only possible if the EntityManager is not yet closed.

This is why you have to have the OpenEntityManagerInViewFilter installed. Without it, the EntityManager is closed when you exit the service layer and you get a LazyInitializationException. The OpenEntityManagerInViewFilter opens the EntityManager at the view level and keeps it open until the HTTP request is complete. So, while it seems like a fix, it's not really because, as you see, when you lose control over who is accessing the properties of your entities, in this case Jersey, then you end up loading things you didn't want to load.

It's better to remove the OpenEntityManagerInViewFilter and figure out what exactly you want Jersey to serialize. Once you have that figured out, there are at least two ways to go about handling it. IHMO, the "best practice" is to have DTO, or Data Transfer Objects. These are POJOs that are not entities but have pretty much the same fields. In the case, the PatientDTO would have everything except the phoneType property (or maybe just the Id). You would pass it a Patient in the constructor and it would copy the fields you want Jersey to serialize. Your service layer would then be responsible for returning DTO's instead of Entities, at least for the REST endpoints. Your clients would get JSON graphs that represent these DTOs, giving you better control over what goes into the JSON because you write the DTOs separate from the Entities.

Another option is to use JSON annotations to prevent Jersey from attempting to serialize properties you don't want serialized, such as phoneType, but that ultimately becomes problematic. There will be conflicting requirements and you never get it sorted out well.

While making DTO's at first seems like a horrible pain, it's not as bad as it seems and it even helps when you want to serialize values that are more client friendly. So, my recommendation is to lose the OpenEntityManagerInViewFilter and construct a proper service layer that returns DTOs, or View Objects as they are sometimes called.

References: What is Data Transfer Object?

REST API - DTOs or not?

Gson: How to exclude specific fields from Serialization without annotations

Community
  • 1
  • 1
K.Nicholas
  • 10,956
  • 4
  • 46
  • 66
  • This answer is close to the solution we adopted while trying to figure the lazy loading out. We are using @JsonView with different levels to tell jersey what to serialize or not depending on what we want. But, with this solution or the DTO solution, we can't benefit from the performance advantage that Lazy Loading would give us... – jon May 13 '16 at 21:22
  • 1
    Sure you can. In your entities set everything to lazy loaded and in your service layer fetch only the things you want serialized. – K.Nicholas May 13 '16 at 21:26
  • Ok I'll test that on monday – jon May 13 '16 at 21:48
3

To understand what is happening here you have to understand how lazy loading works in Hibernate.

When a list is declared as "lazy loaded" the Hibernate framework implements a "lazy loaded" JavassistLazyInitializer object with Javassist. Hence, the phoneType on your patient object is not an implementation of your TelephoneType class. It is a proxy towards it. When getPhoneType() on this object is called however, the proxy on patient is replaced by the real object. Unfortunately, @JsonAutoDetect uses reflection on the proxy object without ever calling getPhoneType() and tries to actually serialise the JavassistLazyInitializer object which of course is impossible.

I think the most elegant solution for this is to implement a query that fetches the patients with their telephoneType.

So instead of:

return this.entityManager.find(Patient.class, primaryKey);

Implement something like:

EntityManager em = getEntityManager();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Patient> query = cb.createQuery(Patient.class);
Root<Patient> c = query.from(Patient.class);
query.select(c).distinct(true);
c.fetch("phoneType");
TypedQuery<Patient> typedQuery = em.createQuery(query);
List<Patient> allPatients = typedQuery.getResultList();

Adapting the query to your needs as required.

Integrating Stuff
  • 5,253
  • 2
  • 33
  • 39
  • i'm not sure i understand what the 'fetch' operation does exactly here. My problem is that the phoneType is always loaded even if Lazy loading is configured. I want to be able to remove it from certain calls (so i guess this is done using hibernate.initialize() – jon May 09 '16 at 16:16
  • The fetch makes sure it is loaded non lazily - it adds a join -, while it remains lazy loaded for all other calls. If you want to serialize it, you have to fetch it. Otherwise, the solution is to completely exclude it from serialization. – Integrating Stuff May 09 '16 at 16:22
  • By the way, Patient patient = this.entityManager.find(Patient.class, primaryKey); patient.getPhoneType(); return patient; will also fix your problem, but is less elegant imho. – Integrating Stuff May 09 '16 at 16:41
  • Another thing I did not explicitly mention: if you need the lazy loading, you would still use "this.entityManager.find(Patient.class, primaryKey)". Typically, if you would use a classic dao pattern, you would add the 2 implementations: one findPatient and one findPatientWithPhoneType, and call them depending on what you require. – Integrating Stuff May 09 '16 at 17:32
  • but my problem is that TelephoneType never gets lazy loaded even if i've annotated it with a fetch type lazy. – jon May 09 '16 at 17:48
  • Your lazy config is ok. I believe the object is still fetched because the JsonAutoDetect triggers the fetch of the object. In cases where you use the patient object internally and serialization to json is not necessary you would use this.entityManager.find(Patient.class, primaryKey) -> lazy loading, in case you need to send it to the client you use the one with the query that I gave as an example that immediately fetches the object which is what you need in this case -> no lazy loading. What is the thing you do not expect? Is your requirement that phoneType is not sent back to the client? – Integrating Stuff May 09 '16 at 18:02
  • well as i understand Lazy loading, if it is activated for TelephoneType inside a Patient, when I fetch a Patient object from db with hibernate, I will receive the Patient's with all it's properties except the TelephoneType in my json entity response and if I keep the hibernate session open with the 'OpenEntityManagerInViewFilter', i will be able to fetch the TelephoneType if I need it – jon May 09 '16 at 18:05
  • No, it does not work like this. When the object serialized to json, all getters are called and the lazy loading triggers for everything unless you exclude it. There is no knowledge of the lazy loading on your client. – Integrating Stuff May 09 '16 at 18:14
0

Have a look at jackson-datatype-hibernate It makes the json serialization with jackson 'aware' of the hibernate proxy

benbenw
  • 723
  • 7
  • 20