I try to implement a server REST with java - hibernate - spring, which returns a json.
I have map a many to many relation.
I explain better, I have a supplier that have a list of ingredients, and each ingredient has a list of supplier.
I created the table:
CREATE TABLE supplier_ingredient (
supplier_id BIGINT,
ingredient_id BIGINT
)
ALTER TABLE supplier_ingredient ADD CONSTRAINT supplier_ingredient_pkey
PRIMARY KEY(supplier_id, ingredient_id);
ALTER TABLE supplier_ingredient ADD CONSTRAINT
fk_supplier_ingredient_ingredient_id FOREIGN KEY (ingredient_id)
REFERENCES ingredient(id);
ALTER TABLE supplier_ingredient ADD CONSTRAINT
fk_supplier_ingredient_supplier_id FOREIGN KEY (supplier_id) REFERENCES
supplier(id);
Then I have Ingredient model:
.....
.....
@ManyToMany(mappedBy = "ingredients")
@OrderBy("created DESC")
@BatchSize(size = 1000)
private List<Supplier> suppliers = new ArrayList<>();
....
....
Then I have Supplier model:
....
@ManyToMany
@JoinTable( name = "supplier_ingredient ",
joinColumns = @JoinColumn(name = "supplier_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "ingredient_id", referencedColumnName = "id"),
foreignKey = @ForeignKey(name = "fk_supplier_ingredient_supplier_id"))
@OrderBy("created DESC")
@Cascade(CascadeType.SAVE_UPDATE)
@BatchSize(size = 1000)
private List<Ingredient> ingredients = new ArrayList<>();
....
Endpoint:
@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;
}
Service
....
public Supplier get(Long supplierId) {
Supplier supplier = supplierDao.getById(supplierId); (it does entityManager.find(entityClass, id))
if (supplier == null) throw new ResourceNotFound("supplier", supplierId);
return supplier;
}
....
SupplierObject
@JsonIgnoreProperties(ignoreUnknown = true)
public class SupplierObject extends IdAbstractObject {
public String email;
public String phoneNumber;
public String address;
public String responsible;
public String companyName;
public String vat;
public List<Ingredient> ingredients = new ArrayList<>();
public SupplierObject () {
}
public SupplierObject (Supplier supplier) {
id = supplier.getId();
email = supplier.getEmail();
responsible = supplier.getResponsible();
companyName = supplier.getCompanyName();
phoneNumber = supplier.getPhone_number();
ingredients = supplier.getIngredients();
vat = supplier.getVat();
address = supplier.getAddress();
}
}
And IdAbstractObject
public abstract class IdAbstractObject{
public Long id;
}
My problem is, when I call the endpoint:
http://localhost:8080/supplier/1
I got an error:
"Could not write JSON: failed to lazily initialize a collection of role: myPackage.ingredient.Ingredient.suppliers, could not initialize proxy - no Session; nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: myPackage.ingredient.Ingredient.suppliers, could not initialize proxy - no Session (through reference chain: myPackage.supplier.SupplierObject[\"ingredients\"]->org.hibernate.collection.internal.PersistentBag[0]->myPackage.ingredient.Ingredient[\"suppliers\"])"
I followed this:
Avoid Jackson serialization on non fetched lazy objects
Now I haven't the error but in json returned, the ingredients field is null:
{
"id": 1,
"email": "[email protected]",
"phoneNumber": null,
"address": null,
"responsible": null,
"companyName": "Company name",
"vat": "vat number",
"ingredients": null
}
but in debug I can see ingredients....
You have some solutions to resolve this issue:
@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.
@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:
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With