Could not write JSON: failed to lazily initialize a collection of role
In my project I came across the same problem as yours. The problem is that by the time of reading the data "one to many" the session has already been closed. To get all the data, you need to explicitly initialize or use the transaction. I used an explicit initialization. You need to add a line in the DAO:
Hibernate.initialize(supplier.getIngredients());
After that, Hibernate will load all the data from the database. To avoid generating an exception when serializing to JSON, I add the @JsonIgnore
annotation in the one-to-many model field.
Here is an example of my code:
1.Model
@OneToMany(mappedBy = "commandByEv", fetch = FetchType.LAZY)
@JsonIgnore
private Set<Evaluation> evaluations;
2. DAO
public Command getCommand(long id) {
Session session = sessionFactory.getCurrentSession();
Evaluation evaluation = session.get(Evaluation.class, id);
Hibernate.initialize(evaluation.getCommand());
return evaluation.getCommand();
}
You have some solutions to resolve this issue:
- You can use
@ManyToMany(fetch = FetchType.LAZY)
But EAGER fetching is very bad from a performance perspective. Moreover, once you have an EAGER association, there is no way you can make it LAZY.
- You can use
@ManyToMany @Fetch(FetchMode.JOIN)
More information: https://docs.jboss.org/hibernate/orm/3.2/api/org/hibernate/FetchMode.html
Edit: It can occur when you have the following line in yout application.properties
file:
spring.jpa.open-in-view = false
This is the normal behaviour of Hibernate and Jackson Marshaller Basically you want to have the following: a JSON with all Supplier object details... included the Ingredients.
Please note that in this case you must be very carefull because you can have a cyclic reference when you try to create the JSON itself so you should use also JsonIgnore
annotation
The first thing you must do is to load the Supplier and all its details (ingredients included).
How can you do it? By using several strategies... let's use the Hibernate.initialize
. This must be used before the closing of hibernate session that is in the DAO (or repository) implementation (basically where you use the hibernate session).
So in this case (I assume to use Hibernate) in my repository class I should write something like this:
public Supplier findByKey(Long id)
{
Supplier result = (Supplier) getSession().find(Supplier.class, id);
Hibernate.initialize(result.getIngredients());
return result;
}
Now you have the Supplier
object with all its own details (Ingredients
too)
Now in your service you can do what you did that is:
@RequestMapping(value = "/{supplierId:[0-9]+}", method = RequestMethod.GET)
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
public SupplierObject get(@PathVariable Long supplierId)
{
Supplier supplier = supplierService.get(supplierId);
SupplierObject supplierObject = new SupplierObject (supplier);
return SupplierObject;
}
In this way Jackson is able in writing the JSON but
let's give a look to the Ingredient
object.. it has the following property:
@ManyToMany(mappedBy = "ingredients")
@OrderBy("created DESC")
@BatchSize(size = 1000)
private List<Supplier> suppliers = new ArrayList<>();
What will happen when Jackson tries to create the JSON? It will access to the each element inside the List<Ingredient>
and it will try to create a JSON for this one too.... also for the suppliers list and this is a cyclic reference... so you must avoid it and you can avoid it by using the JsonIgnore annotation. For example you may write your Ingredient
entity class in this way:
@JsonIgnoreProperties(value= {"suppliers"})
public class Ingredient implements Serializable
{
......
}
In this way you:
- load the supplier object with all the related ingredient
- avoid a cyclic reference when you try to create the JSON itself
In any case I would suggest to you to create specific DTO (or VO) object to use for marshalling and unmarshalling JSONs
I hope this is usefull
Angelo