I have an error using Hibernate with SparkJava in the lazy-loading mode.
It is working correctly without SparkJava, but when using SparkJava it is trying to force eager-loading for a OneToMany relationship.
- Model
@Entity
@Table(name = "KU_SUPPLIER")
public class Supplier {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@NotEmpty(message = "Please provide a name")
private String name;
@OneToMany(mappedBy = "supplier")
private List<Item> items; // Should be lazy-loaded
// Constructor / Getters / Setters
}
- DAO
public class SupplierDao implements Dao<Supplier> {
private final SessionFactory sessionFactory;
public SupplierDao(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
@Override
@SuppressWarnings("unchecked")
public List<Supplier> findAll() {
try (Session session = sessionFactory.openSession()) {
return session.createQuery("FROM com.seafrigousa.model.Supplier").getResultList();
}
}
}
- Main
// Working perfectly and lazy-load Items as desired
supplierDao.findAll();
// The method will be called when a web browser goes to "localhost/suppliers"
// It throws org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: model.Supplier.items, could not initialize proxy - no Session
get("/suppliers", "application/json", supplierDao::findAll);
I checked by not closing the session from the DAO and I saw that Hibernate was executing the query as if it was in EAGER loading mode, so it was executing two selects, one for Supplier and one for Item.
Is there a reason for this behavior ?
Thank you!
I guess that here: get("/suppliers", "application/json", supplierDao::findAll);
you are serializing Supplier object into json. Items
field is not marked as excluded from serialization, so getting its value cause lazy initialization out of session (or redundand second query for items, if session is not closed).
If my guess is correct, make your serializer ignore items field or fetch them in your query
session.createQuery("FROM com.seafrigousa.model.Supplier s join fetch s.items").getResultList();
Using gson as serializer you have following options:
@Expose
annotation on fields which you want to serialize.
@Entity
@Table(name = "KU_SUPPLIER")
public class Supplier {
@Expose
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Expose
@NotEmpty(message = "Please provide a name")
private String name;
@OneToMany(mappedBy = "supplier")
private List<Item> items; // Should be lazy-loaded
// Constructor / Getters / Setters
}
With following gson initiation
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
ExclusionStrategy with custom annotation f.e.
public class IgnoreFieldExclusionStrategy implements ExclusionStrategy {
@Override
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
return fieldAttributes.getAnnotation(GsonIgnore.class) != null;
}
@Override
public boolean shouldSkipClass(Class<?> aClass) {
return false;
}
}
with custom annotation @GsonIgnore
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface GsonIgnore {}
and gson initiation
Gson gson = new GsonBuilder().addSerializationExclusionStrategy(new IgnoreFieldExclusionStrategy()).create();
your class would look like this
@Entity
@Table(name = "KU_SUPPLIER")
public class Supplier {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@NotEmpty(message = "Please provide a name")
private String name;
@GsonIgnore
@OneToMany(mappedBy = "supplier")
private List<Item> items; // Should be lazy-loaded
// Constructor / Getters / Setters
}
If you would have need serialize Supplier
with items
in different api, you can create DTO object for Supplier
and map it from results like this:
package com.seafrigousa.dto
public class SupplierDTO {
private int id;
private String name;
public SupplierDTO(int id, String name) {
this.id = id;
this.name = name;
}
// Getters / Setters
}
and query:
session.createQuery("select new com.seafrigousa.dto.SupplierDTO(s.id, s.name) FROM com.seafrigousa.model.Supplier s").getResultList();