web-dev-qa-db-fra.com

JSON sérialisant et désérialisant avec hibernate jpa pour avoir un objet parent dans l'enfant dans une réponse JSON

Je suis en train de développer une application Web de repos avec Spring Framework, Hibernate et JSON. Veuillez supposer que j'ai deux entités comme ci-dessous:

BaseEntity.Java

@MappedSuperclass
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = "id" )
public abstract class BaseEntity implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    public long getId() {
        return id;
    }
}

Université.Java

 public class University extends BaseEntity {

      private String uniName;

       @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER,orphanRemoval = true)
      @JoinColumn(name = "university_id")
        private List<Student> students=new ArrayList<>();
    // setter an getter
    }

Student.Java

    public class Student extends BaseEntity{

        private String stuName;

        @ManyToOne(fetch = FetchType.EAGER)
        @JoinColumn(name = "university_id",updatable = false,insertable = false)   
        private University university;

    // setter an getter
        }

lorsque j'appelle mon api de repos pour répertorier l'Université, tout fonctionne comme prévu, mais lorsque j'appelle mon api de repos pour répertorier les étudiants avec impatience, ma réponse JSON est

[
   {
    "id": 1,
    "stuName": "st1",
    "university": {
        "id": 1,
        "uniName": "uni1"
                 }
    },
    {
        "id": 2,
        "stuName": "st2",
        "university": 1
    }
]

mais ma réponse désirée est:

[
    {
        "id": 1,
        "stutName": "st1",
        "university": 
        {
         "id": 1,
        "uniName": "uni1"
        }
    },
    {
        "id": 2,
        "stutName": "st2",
        "university": 
        {
         "id": 1,
        "uniName": "uni1"
        }
    }

Mise à jour 1: mon annotation d'hibernation fonctionne correctement J'ai un problème avec JSON

Exigences :

  1. J'ai besoin d'aller chercher les deux côtés avec impatience (l'université est ok)

  2. J'ai besoin d'un objet universitaire côté étudiant pour chaque étudiant (quand je vais chercher l'étudiant avec impatience)

Quel type de sérialisation ou de configuration JSON est nécessaire pour faire correspondre la réponse souhaitée?

_ {Update 2:} _ 

en supprimant @JsonIdentityInfo et en modifiant le côté étudiant comme ci-dessous:

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "university_id",updatable = false,insertable = false)
@JsonIgnoreProperties(value = "students", allowSetters = true)
private University university;

la réponse json reste identique J'ai besoin de ma réponse souhaitée mentionnée ci-dessus.

merci

8
Generic

Je comprends que vous ne souhaitiez pas inclure University.students dans votre JSON. 

  1. Supprimer @JsonIdentityInfo

    @MappedSuperclass
    //@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = "id" )
    public abstract class BaseEntity implements Serializable {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private long id;
        public long getId() {
            return id;
        }
    }
    
  2. Ajoutez @JsonIgnore aux étudiants pour éviter les cercles

    public class University extends BaseEntity {
    
        private String uniName;
    
        @JsonIgnore
        @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER,orphanRemoval = true)
        @JoinColumn(name = "university_id",foreignKey = @ForeignKey(name = "university_id"))
        private List<Student> students=new ArrayList<>();
        // setter an getter
    }
    

Si vous avez besoin que University.students soit sérialisé dans d'autres contextes, donnez http://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion a read. D'autres options pour traiter les relations bidirectionnelles y sont expliquées.

1
Chris

La manière dont vous tracez votre relationip, même si elle "fonctionne correctement", ne respecte pas les spécifications de JPA pour les relations bidirectionnelles. Voir cette question et les réponses connexes.

Pour résumer généralement, le propriétaire de la relation est le côté plusieurs-à-un et le côté un-à-plusieurs est annoté avec mappedBy. Votre solution de mappage, dans laquelle la relation un à plusieurs est propriétaire, n'est pas habituelle/recommandée (comme décrit dans les réponses ci-dessus) mais techniquement possible. (@manyToOne side manque quelques attributs comme "updatable = false" dans votre exemple)

Ensuite, avec JPA et la version récente de Hibernate , la politique de chargement paresseux est la suivante:

 OneToMany: LAZY
 ManyToOne: EAGER
 ManyToMany: LAZY
 OneToOne: EAGER

Je vous suggère donc d'utiliser cette politique de chargement paresseux par défaut et de changer le propriétaire de votre relation manyToOne car il ne semble pas judicieux de demander à tous les étudiants via une seule demande de ressource Université. (Avez-vous entendu parler de la pagination?)

Faire ainsi, et exclure également la collection des étudiants de Marshalling, en utilisant par exemple @JsonIgnore, devrait faire l'affaire.

1
Rémi Bantos

Ajouter @jsonignore pour la méthode getter
et ajoutez @jsonProperty au champ like 

@JsonProperty(access = Access.READ_ONLY)
private String password;

Récemment, certaines fonctionnalités de Jackson comme Readonly et writeonly ont été ajoutées
vous pouvez vous référer à ceci:
http://fasterxml.github.io/jackson-annotations/javadoc/2.6/com/fasterxml/jackson/annotation/JsonProperty.Access.html

1
vishal paalakurthi

Vous pouvez ajouter ceci et vérifier

Université

public class University {

@Fetch(value = FetchMode.SELECT)
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "university_id")
@JsonIgnore 
 private List<Student> students;

}

Étudiant

public class Student{
@ManyToOne
@JoinColumn(name = "university_id", insertable = true, updatable = true, nullable = true)
private University university;
}
1
Hema

Pouvez-vous ajouter @JoinColumn à l'entité Student également

@ManyToOne(fetch = FetchType.EAGER)  
@JoinColumn(name = student_id")

Vérifiez également la clé étrangère de votre classe d'entité universitaire. La clé étrangère doit provenir d'une autre entité, n'est-ce pas? @ JoinColumn (name = "nom_université", foreignKey = @ForeignKey (name = "nom_étudiant")) ??

Sinon, vous pouvez également utiliser le "mappéPar".

@JoinColumn(name = "university_id", mappedBy="university")
        private List<Student> students=new ArrayList<>();
1
Tharsan Sivakumar

Supprimez @JsonIdentityInfo de la classe de base, car l'objet universitaire ne sérialise que l'id.

1
Sachin Gupta

Vous voudrez peut-être essayer d'utiliser @JsonRawValue comme annotation pour votre propriété university. Le comportement que vous rencontrez est dû au regroupement des références - puisqu'il s'agit de la même variable University, le sérialiseur tente d'être intelligent et renvoie simplement une référence lors de sa deuxième rencontre.

EDIT: La toString():

@Override
public String toString() {
    ObjectMapper mapper = new ObjectMapper();
    return mapper.writeValueAsString(this);
}
1
Piotr Wilkin

J'ai eu le même problème. Hibernate (ou eclipselink) n'est pas le problème . La seule contrainte dans JPA est le FetchType.EAGER .

Dans la BaseEntity, j'ai ajouté une méthode standard

public String getLabel(){
   return "id:"+this.getId();
}

cette méthode serait abstraite, mais j'avais beaucoup de cours et je ne voulais pas tout changer, alors j'ai ajouté une valeur par défaut.

Dans l'entité mère, dans ce cas l'Université, remplace la méthode

@Override
public String getLabel{
    return this.uniName;
}

Pour chaque classe parente, utilisez un champ particulier comme étiquette pour votre entité.

Définir un MyStandardSerializer:

public class StandardJsonSerializer extends JsonSerializer<EntityInterface> {

@Override
public void serializeWithType(EntityInterface value, JsonGenerator jgen, SerializerProvider provider, TypeSerializer typeSer) throws IOException, JsonProcessingException {
        serialize(value, jgen, provider); 
}

@Override
public void serialize(EntityInterface value, JsonGenerator jgen, SerializerProvider provider) 
  throws IOException, JsonProcessingException {
    jgen.writeStartObject();
    jgen.writeNumberField("id", (long) value.getId());
    jgen.writeStringField("label", value.getLabel());
    jgen.writeEndObject();
}

}

Dans la classe des étudiants, à l’université, ajouter:

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "university_id",updatable = false,insertable = false)   
@JsonSerialize(using=StandardJsonSerializer.class)
private University university;

Maintenant, vous avez résolu la circularité. Lorsque vous avez besoin d'une étiquette, remplacez la méthode dans l'entité parente . Lorsque vous avez besoin de champs particuliers, créez un sérialiseur spécifique.

1
Daniele Licitra