7

I've found oodles of resources on this, such as this one - Infinite Recursion with Jackson JSON and Hibernate JPA issue. I've tried to implement all of the various suggestions described there (including basic @JsonIgnore), but to no avail. I can't get anything other than infinite recursion errors regardless of what I try. I think I have a pretty similar/typical setup, but obviously with something wrong since despite using @JsonManagedReference, @JsonBackReferencere and @JsonIdentityInfo annotations, I keep getting the error.

My tables are "exchange" and "stock", with a manytoMany between them, and I've been testing by via ExchangeEndpoint. I've confirmed that if I completely remove "stock" from "exchange" entity, the service works fine, but for some reason, the json annotations do not seem to have any affect. Below is what I think is the solution based on the second (but more popular) answer in the aforementioned Infinite Recursion with Jackson JSON and Hibernate JPA issue.

Exchange.java

@Entity
@Table(name = "exchange", schema = "public")
@XmlRootElement
@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")
public class Exchange implements java.io.Serializable {
...
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "exchange")
    @JsonManagedReference
    public Set<Stock> getStocks() {
        return this.stocks;
    }
...

Stock.java

@Entity
@Table(name = "stock", schema = "public")
@XmlRootElement
@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")
public class Stock implements java.io.Serializable {
...
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "exchangeid", nullable = false)
    @JsonBackReference
    @JsonIgnore
    public Exchange getExchange() {
        return this.exchange;
    }
...

ExchangeEndpoint.java

@Stateless
@Path("/exchanges")
public class ExchangeEndpoint {
    @PersistenceContext(unitName = "postgresql-ss4")
    private EntityManager em;
...
    @GET
    @Produces("application/json")
    public List<Exchange> listAll(@QueryParam("start") Integer startPosition,
            @QueryParam("max") Integer maxResult) {
        TypedQuery<Exchange> findAllQuery = em
                .createQuery(
                        "SELECT DISTINCT e "
                        + "FROM Exchange e "
                        + "LEFT JOIN FETCH e.stocks "
                        + "ORDER BY e.exchangeid",
                        Exchange.class);
        if (startPosition != null) {
            findAllQuery.setFirstResult(startPosition);
        }
        if (maxResult != null) {
            findAllQuery.setMaxResults(maxResult);
        }
        final List<Exchange> results = findAllQuery.getResultList();
        return results;
    }

edit:

some of the error output to be sure i'm not misinterpreting something;

15:35:16,406 ERROR [org.jboss.resteasy.resteasy_jaxrs.i18n] (http-/0.0.0.0:8080-1) RESTEASY000100: Failed executing GET /exchanges/: org.jboss.resteasy.spi.WriterException: org.codehaus.jackson.map.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: net.hb.forge2RestServices.model.Exchange["stocks"]->org.hibernate.collection.internal.PersistentSet[0]->net.hb.forge2RestServices.model.Stock["exchange"]->net.hb.forge2RestServices.model.Exchange["stocks"]->org.hibe ... ... Exchange["stocks"]->org.hibernate.collection.internal.PersistentSet[0]->net.hb.forge2RestServices.model.Stock["exchange"]->net.hb.forge2RestServices.model.Exchange["stocks"]) at org.jboss.resteasy.core.ServerResponse.writeTo(ServerResponse.java:262)

Please let me know what additional information I can provide to help explain my debacle. tiy.

Community
  • 1
  • 1
swv
  • 691
  • 1
  • 12
  • 28

4 Answers4

3

The issue is you have reference to Stocks and back from Stock you have reference to Exchange. Therefore JSON goes to stock, back to Exchange and then for Exchange it will generate to all Stocks and so on.

To break the cycle it's best to put JsonIgnore annotation to the field which references the parent object - ManyToOne - in this case it's Stock.exchange field (or the getter for that).

Zbynek Vyskovsky - kvr000
  • 18,186
  • 3
  • 35
  • 43
  • I tried adding the JsonIgnore annotation in a couple of different ways, but it still doesn't look like anything is behaving differently. I updated the original question with one of them, where i just tacked it on, but I also tried replacing the JsonBackReference, and moving the annotations to the field definition (instead of the getter), but I seem to get the same results. Is there some way that these annotations may be getting negated? – swv Feb 11 '16 at 20:26
3

This kind of issues is much more easily avoided if you don't expose your JPA entities through REST.

You should consider using DTOs on REST side and mapping entities to DTOs and the reverse either by coding a mapper manually or by generating one using MapStruct.

As a bonus you'll avoid security issues.

Gaël Marziou
  • 16,028
  • 4
  • 38
  • 49
  • MapStruct does seem interesting. I've been hoping that this project wouldn't get sophisticated enough to really mandate adding another layer like this, but I can envision plenty of other projects that will. Thank you for the reference. – swv Feb 12 '16 at 15:19
2

It seems that the problem was with imports. I'm not sure why, but org.codehaus.jackson.annotate.JsonIgnore works for me, and com.fasterxml.jackson.annotation.JsonIgnore doesn't.

swv
  • 691
  • 1
  • 12
  • 28
2

Use @JsonManagedReference, @JsonBackReference

public class User {
   public int id;
   public String name;

   @JsonBackReference
   public List<Item> userItems;
}


public class Item {
    public int id;
    public String itemName;

    @JsonManagedReference
    public User owner;
}

Note that:

@JsonManagedReference is the forward part of reference – the one that gets serialized normally. @JsonBackReference is the back part of reference – it will be omitted from serialization.

http://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion

Naami
  • 349
  • 3
  • 9