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 ....
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:
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
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();
}
Ajoutez simplement @JsonIgnore
après @oneToMany
dans votre classe de modèle.
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