JSON Infiito when using GET @Manytomany

Asked

Viewed 919 times

0

I have two classes, of which you have a @Manytomany relationship generating the third table described in the code below:

Card


package br.com.rpgnext.deck.critical.model;

import com.fasterxml.jackson.annotation.*;
import javax.persistence.*;
import java.io.Serializable;
import java.util.*;

@Entity
@Table(name = "cards")
public class Card implements Serializable{
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @JoinColumn(name = "id")
    private Long id;
    private String image;

    @ManyToMany(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
    @JoinTable(name = "cards_items", joinColumns = @JoinColumn(name = "card_id"), inverseJoinColumns = @JoinColumn(name = "item_id"))
    private Set items;

    @Temporal(TemporalType.DATE)
    @JsonFormat(pattern = "YYYY-MM-dd")
    private Date onCreate;

    @PrePersist
    public void prePersist(){
        this.onCreate = new Date();
    }

    public Card() {
    }

    public Card(String image) {
        this.image = image;
    }

    public Card(String image, Set items) {
        this.image = image;
        this.items = items;
    }

    public Card(String image, Set items, Date onCreate) {
        this.image = image;
        this.items = items;
        this.onCreate = onCreate;
    }

    public Card(Long id, String image, Set items, Date onCreate) {
        this.image = image;
        this.items = items;
        this.onCreate = onCreate;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image;
    }

    public Set getItems() {
        return items;
    }

    public void setItems(Set items) {
        this.items = items;
    }

    public Date getOnCreate() {
        return onCreate;
    }

    public void setOnCreate(Date onCreate) {
        this.onCreate = onCreate;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Card card = (Card) o;
        return Objects.equals(id, card.id) &&
                Objects.equals(image, card.image) &&
                Objects.equals(items, card.items) &&
                Objects.equals(onCreate, card.onCreate);
    }

    @Override
    public int hashCode() {

        return Objects.hash(id, image, items, onCreate);
    }
}

Item


package br.com.rpgnext.deck.critical.model;

import com.fasterxml.jackson.annotation.*;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
import java.util.Set;
import java.util.Objects;

@Entity
@Table(name = "items")
public class Item implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @JoinColumn(name = "id")
    private Long id;
    private String title;
    private String description;

    @OneToOne
    @JoinColumn(name="type_id", referencedColumnName = "id")
    private Type type;

    @ManyToMany(fetch = FetchType.EAGER, mappedBy = "items" )
    private Set cards;

    @Temporal(TemporalType.DATE)
    @JsonFormat(pattern = "YYYY-MM-dd")
    private Date onCreate;

    @PrePersist
    void prePersist(){
        this.onCreate = new Date();
    }

    public Item() {
    }

    public Item(Long id){
        this.id = id;
    }

    public Item(Long id, String title, String description, Type type, Set cards, Date onCreate) {
        this.title = title;
        this.description = description;
        this.type = type;
        this.cards = cards;
        this.onCreate = onCreate;
    }

    public Item(String title, String description, Type type, Set cards) {
        this.title = title;
        this.description = description;
        this.type = type;
        this.cards = cards;
    }

    public Item(Set cards) {
        this.cards = cards;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Type getType() {
        return type;
    }

    public void setType(Type type) {
        this.type = type;
    }

    public Set getCards() {
        return cards;
    }

    public void setCards(Set cards) {
        this.cards = cards;
    }

    public Date getOnCreate() {
        return onCreate;
    }

    public void setOnCreate(Date onCreate) {
        this.onCreate = onCreate;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Item item = (Item) o;
        return Objects.equals(id, item.id) &&
                Objects.equals(title, item.title) &&
                Objects.equals(description, item.description) &&
                Objects.equals(type, item.type) &&
                Objects.equals(cards, item.cards) &&
                Objects.equals(onCreate, item.onCreate);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, title, description, type, cards, onCreate);
    }
}

Type


package br.com.rpgnext.deck.critical.model;

import com.fasterxml.jackson.annotation.JsonFormat;

import java.io.Serializable;
import java.util.Date;
import java.util.Objects;

import javax.persistence.*;

@Entity
@Table(name = "types")
public class Type implements Serializable{
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, nullable = false)
    private String text;
    private String image;

    @Temporal(value = TemporalType.DATE)
    @JsonFormat(pattern = "YYYY-MM-dd")
    private Date onCreate;

    @PrePersist
    void prePersists(){
        this.onCreate = new Date();
    }

    public Type() {
    }

    public Type(String text) {
        this.text = text;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Type type = (Type) o;
        return Objects.equals(id, type.id) &&
                Objects.equals(text, type.text) &&
                Objects.equals(image, type.image) &&
                Objects.equals(onCreate, type.onCreate);
    }

    @Override
    public int hashCode() {

        return Objects.hash(id, text, image, onCreate);
    }
}

It has been attempted to use several ways to get Infinite Loop out of JSON, since both classes have a List, but to no avail.

@Jsonignore does not solve, because it will not show the content I need.

Example of JSON Inserted


{
  "image": "Carta-Critica-001.jpg",
  "items": [
    {"id": 1},
    {"id": 2},
    {"id": 3},
    {"id": 4}
  ]
}

How I wish you’d come back


{
  "image": "Carta-Critica-01.jpg",
  "items": [
    {
      "id": 1,
      "title": "traqueia esmagada",
      "type": {
        "id": 1,
        "text": "contusão",
        "image": "contution.png"
      },
      "description": "Danos Crítico, e o alvo fica incapacitadoe não pode falar e respirar até receber tratamento."
    },
    {
      "id": 2,
      "title": "mão perfurada",
      "type": {
        "id": 2,
        "text": "perfurante",
        "image": "piercing.png"
      },
      "description": "Danos Crítico, e o alvo solta o que estiver segurando (1 item) e não pode usar aquele membro até o início de seu próximo turno."
    },
    {
      "id": 3,
      "title": "testa cortada",
      "type": {
        "id": 3,
        "text": "cortante",
        "image": "sharp.png"
      },
      "description": "Danos Crítico, e o alvo fica cego até o início de seu próximo turno."
    },
    {
      "id": 4,
      "title": "vulnerabilidade mágica",
      "type": {
        "id": 4,
        "text": "mágico",
        "image": "magic.png"
      },
      "description": "Danos Crítico, e o alvo tem vulnerabilidade a dano mágico até o início de seu próximo turno."
    }
  ]
}
}

Controller


package br.com.rpgnext.deck.critical.controller;

import java.beans.Transient;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import br.com.rpgnext.deck.critical.service.ItemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import br.com.rpgnext.deck.critical.model.Card;
import br.com.rpgnext.deck.critical.model.Item;
import br.com.rpgnext.deck.critical.model.Type;
import br.com.rpgnext.deck.critical.repository.CardRespository;
import br.com.rpgnext.deck.critical.repository.ItemRepository;
import br.com.rpgnext.deck.critical.repository.TypeRepository;
import br.com.rpgnext.deck.critical.service.CardService;

@RestController
public class CardController {
    @Autowired
    private CardRespository cardRespository;

    @Autowired
    private TypeRepository typeRepository;

    @Autowired
    private ItemRepository itemRepository;

    @Autowired
    private CardService cardService;

    @Autowired
    private ItemService itemService;

    @RequestMapping(value = "card/save", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE )
    @ResponseBody
    public ResponseEntity save(@RequestBody Card card){
        Card cardSaved = cardRespository.save(card);
        return new ResponseEntity(cardSaved, HttpStatus.CREATED);
    }

    @RequestMapping(value = "cards/save", method = RequestMethod.POST)
    @ResponseBody
    public ResponseEntity> saveAll(@RequestBody List cards){
        return new ResponseEntity((List) cardRespository.findAll(), HttpStatus.FOUND);
    }

    @RequestMapping(value = "cards", method = RequestMethod.GET)
    @ResponseBody
    public ResponseEntity> findAll(){
        return new ResponseEntity((List) cardRespository.findAll(), HttpStatus.FOUND);
    }

    @RequestMapping(value = "card/{id}", method = RequestMethod.GET)
    @ResponseBody
    public ResponseEntity findById(@PathVariable Long id){
        return new ResponseEntity((cardRespository.findById(id).get()), HttpStatus.FOUND);
    }

    @RequestMapping(value = "card/random", method = RequestMethod.GET)
    @ResponseBody
    public ResponseEntity randomCard(){
        List cards = (List) cardRespository.findAll();
        Card card = cardService.randomCard(cards);
        return new ResponseEntity(card, HttpStatus.OK);
    }

    @RequestMapping(value = "card/new", method = RequestMethod.GET)
    @ResponseBody
    public ResponseEntity randomNewCard(){
        Card card = new Card();

        for(Long i = 1L; i  allItemsByType = itemRepository.findAllByType(type);
            Item item = itemService.randomItem(allItemsByType);
            //item.setCard(card);
            //card.addItem(item);
        }

        Card cardSaved = cardRespository.save(card);
        return new ResponseEntity(cardSaved, HttpStatus.CREATED);
    }
}
  • Include an example of JSON that you expect as a result in order to make your problem easier to understand.

  • Include the class Type in your question

2 answers

1

Use the annotations JsonManagedReference and JsonBackReference. They are used to indicate that such properties are part of a bidirectional relationship. The first should be used in the class "father", while the second in the class "daughter".

In your class Card add the annotation JsonManagedReference:

//Algum motivo para não parametrizar seu Set (Set<Item>)?
@JsonManagedReference
private Set items;

In your class Item add the annotation JsonBackReference:

//Algum motivo para não parametrizar seu Set (Set<Card>)?
@JsonBackReference
private Set items;
  • I have the habit of putting the Annotation in the properties, but it is just custom. This can cause some unexpected behavior?

  • When performing the suggested modification, and trying to save the Item using the controller’s 'Save' method, this error appears below: "Type Definition error: [simple type, class br.com.rpgnext.deck.Critical.model.Card]; nested Exception is com.fasterxml.Jackson.databind.exc.Invaliddefinitionexception: Cannot Handle Managed/back Reference 'defaultReference': back Reference type (java.util.Set) not compatible with Managed type (br.com.rpgnext.deck.Critical.model.Card) n at [Source: (Pushbackinputstream); line: 1, column: 1]"

  • @Douglasdreer Are you using Spring boot? If so, which version? Otherwise, which version of Jackson?

  • I am using Springboot, and the Jackson version I did not define the version, but I put "Jackson-databind"

  • @Douglasdreer As for the annotations in the properties: yes, you can do, not the problem at all. The important thing is to keep a pattern, if you put the annotations in the properties, try to do so at all. Avoid mixing annotations in the Setters and Getters annotated in the properties.

  • @Douglasdreer What version of Spring boot?

  • Springboot 2.0-RELESE

Show 3 more comments

1


Add reference back in

    @Entity
    @Table(name = "items")
    public class Item implements Serializable {
        /**
         * 
         */
        @JsonBackReference("items")
        @ManyToMany(fetch = FetchType.EAGER, mappedBy = "items" )
        private Set cards;

    }

On both sides

    @Entity
    @Table(name = "cards")
    public class Card implements Serializable{

        @JsonBackReference("cards")
        @ManyToMany(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
        @JoinTable(name = "cards_items", joinColumns = @JoinColumn(name = "card_id"), inverseJoinColumns = @JoinColumn(name = "item_id"))
        private Set items;

    }
  • It worked, but the list of items inside the card is not showing.

  • When the card is in the items list, it should not appear, and vice versa, then to appear in both, you have to fetch the query

  • Could you give me an example?

  • To recover the cards after third level, I suggest you make a separate selection

  • His doubt was in relation to the infinite loop between multiple objects, now what he is talking about comes into another context

Browser other questions tagged

You are not signed in. Login or sign up in order to post.