Could not write JSON: failed to lazily initialize a collection of role

Gjord83 :

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

enter image description here

Angelo Immediata :

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

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=465058&siteId=1