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 :
J'ai besoin d'aller chercher les deux côtés avec impatience (l'université est ok)
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
Je comprends que vous ne souhaitiez pas inclure University.students
dans votre JSON.
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;
}
}
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.
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.
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
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;
}
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<>();
Supprimez @JsonIdentityInfo
de la classe de base, car l'objet universitaire ne sérialise que l'id.
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);
}
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.