web-dev-qa-db-fra.com

Impossible d'écrire JSON: échec de l'initialisation paresseuse d'une collection de rôles

Je réalise un serveur REST avec Java - hibernate - spring, qui renvoie un json

J'ai carte une relation plusieurs à plusieurs. 

J'explique mieux, j'ai un fournisseur qui a une liste d'ingrédients, et chaque ingrédient a une liste de fournisseur.

J'ai créé la 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);

Ensuite, j'ai le modèle Ingredient:

.....
.....
@ManyToMany(mappedBy = "ingredients")
@OrderBy("created DESC")
@BatchSize(size = 1000)
private List<Supplier> suppliers = new ArrayList<>();
....
....

Ensuite, j'ai le modèle Supplier:

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

}

Un 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();


    }
}

Et IdAbstractObject

public abstract class IdAbstractObject{

    public Long id;

}

Mon problème est, quand j'appelle le noeud final:

http://localhost:8080/supplier/1

J'ai une erreur:

"Impossible d’écrire JSON: impossible d’initialiser paresseusement une collection de Rôle: myPackage.ingredient.Ingredient.suppliers, impossible d’initialiser Proxy - aucune Session; l’exception imbriquée correspond à Com.fasterxml.jackson.databind .JsonMappingException: échec paresseux Initialiser une collection de rôles: MyPackage.ingredient.Ingredient.suppliers, impossible d'initialiser le proxy - no Session (via la chaîne de référence: myPackage.supplier.SupplierObject [\ "ingredients\"] -> org.hibernate.collection.internal.PersistentBag [0] -> myPackage.ingredient.Ingredient [\" providers\"])"

Qu'est-ce que je me trompe?

@Nikhil Yekhe

J'ai suivi ceci:

Évitez la sérialisation de Jackson sur des objets paresseux non récupérés

Maintenant, je n'ai pas l'erreur mais dans json retourné, le champ des ingrédients est nul:

{
"id": 1,
"email": "[email protected]",
"phoneNumber": null,
"address": null,
"responsible": null,
"companyName": "Company name",
"vat": "vat number",
"ingredients": null
}

mais dans debug je peux voir les ingrédients ....

 enter image description here

3
Gjord83

C’est le comportement normal d’Hibernate et de Jackson Marshaller Vous souhaitez en principe disposer des éléments suivants: un JSON contenant tous les détails de l’objet Fournisseur, y compris les ingrédients. 

Notez que dans ce cas, vous devez être très prudent, car vous pouvez avoir une référence cyclique lorsque vous essayez de créer le JSON lui-même. Vous devez donc également utiliser JsonIgnore annotation.

La première chose à faire est de charger le fournisseur et tous ses détails (ingrédients compris). 

Comment peux-tu le faire? En utilisant plusieurs stratégies ... utilisons le Hibernate.initialize . Ce doit être utilisé avant la fermeture de la session hibernate qui se trouve dans l'implémentation DAO (ou du référentiel) (vous utilisez essentiellement la session hibernate). 

Donc, dans ce cas (je suppose utiliser Hibernate) dans ma classe de référentiel, je devrais écrire quelque chose comme ceci:

public Supplier findByKey(Long id)
{
    Supplier result = (Supplier) getSession().find(Supplier.class, id);
    Hibernate.initialize(result.getIngredients());
    return result;
}

Maintenant, vous avez l'objet Supplier avec tous ses détails (Ingredients également) Maintenant, dans votre service, vous pouvez faire ce que vous avez fait, c'est-à-dire:

@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;
}

De cette façon, Jackson est capable d'écrire le JSON but regardons l'objet Ingredient .. il a la propriété suivante:

@ManyToMany(mappedBy = "ingredients")
@OrderBy("created DESC")
@BatchSize(size = 1000)
private List<Supplier> suppliers = new ArrayList<>();

Que se passera-t-il lorsque Jackson tentera de créer le JSON? Il va accéder à chaque élément à l'intérieur du List<Ingredient> et essayer de créer un JSON pour celui-ci aussi .... également pour la liste de fournisseurs et c'est une référence cyclique ... vous devez donc l'éviter et vous pouvez l'éviter en utilisant l'annotation JsonIgnore. Par exemple, vous pouvez écrire votre classe d'entité Ingredient de cette façon:

@JsonIgnoreProperties(value= {"suppliers"})
public class Ingredient implements Serializable
{
......
}

De cette façon vous:

  • charge l'objet fournisseur avec tous les ingrédients associés
  • éviter une référence cyclique lorsque vous essayez de créer le JSON lui-même

Dans tous les cas, je vous suggérerais de créer un objet DTO (ou VO) spécifique à utiliser pour le JS marshalling et unmarshalling.

J'espère que c'est utile

Angelo

4
Angelo Immediata

Dans mon projet, j'ai rencontré le même problème que le vôtre. Le problème est qu’au moment de la lecture des données "un à plusieurs", la session a déjà été fermée. Pour obtenir toutes les données, vous devez explicitement initialiser ou utiliser la transaction. J'ai utilisé une initialisation explicite. Vous devez ajouter une ligne dans le DAO:

Hibernate.initialize(supplier.getIngredients());

Après cela, Hibernate chargera toutes les données de la base de données. Pour éviter de générer une exception lors de la sérialisation en JSON, j'ajoute l'annotation @JsonIgnore dans le champ de modèle un-à-plusieurs.

Voici un exemple de mon code:

1. modèle

@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();
}
2
conacry

Ajoutez simplement @JsonIgnore après @oneToMany dans votre classe de modèle.

0
Majid

Cela est dû au fait que la session d'hibernation est fermée avant l'initialisation paresseuse. 

La solution est bien expliquée à cette réponse ci-dessous . Évitez la sérialisation de Jackson sur des objets paresseux non extraits

0
Nikhil Yekhe