简体   繁体   中英

Hibernate and TomEE+ bidirectional association causes json recursiveness

I'm using TomEE 9.0.12 with Hibernate 5.3.7 and I am currently getting a stackoverflow exception due to json recursiveness. Note that I am not using jackson, and ideally don't want to, but the standard that comes with TomEE+: org.apache.johnzon.

The exception:

Caused by: java.lang.StackOverflowError
    at org.apache.johnzon.mapper.Mappings.findOrCreateClassMapping(Mappings.java:340)
    at org.apache.johnzon.mapper.MappingGeneratorImpl.doWriteObjectBody(MappingGeneratorImpl.java:240)

Here is my User.java class:

import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

/**
 * A user
 */
@Entity
@Table(name = "users")
public class User implements Serializable {

    private static final long serialVersionUID = -9012412584251217L;

    // TODO: 16/12/2018 they keep nesting each other: { user { posts { user { posts } } }  }

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

    @Column(name = "username")
    private String username;

    @Column(name = "email")
    private String email;

    @Column(name = "password")
    private String password;

    @Column(name = "salt")
    private String salt;

    @OneToMany(mappedBy = "user")
    private Set<Post> posts = new HashSet<>();

    /**
     * Constructs an empty user (for persistence purposes)
     */
    public User() {}

    // Setters and getters removed for convenience
}

And here is my Post.java

import javax.persistence.*;
import java.io.Serializable;

/**
 * Project created by ExpDev
 */
@Entity
@Table(name = "posts")
public class Post implements Serializable {

    private static final long serialVersionUID = -9887234238952234L;

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

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;

    @Column(name = "data")
    private String data;

    /**
     * Constructs an empty post (for persistence purposes)
     */
    public Post() {}

    // Setters and getters removed for convenience
}

The issue

The issue is that they will keep referring back to each other. User will refer to posts, which will refer back to the user, which will again refer to posts-- and so the evil circle continues.

I know that I can use org.apache.johnzon's annotation @JsonbTransient on "user" in User.java to make it not serialize it, but that makes so that it won't ever show (which I want, but conditionally). Why do I want it like this?

Because, let's say GET /users/1 is accessed, 1 being the id of the user, I want to show all the user's posts (or at least the ID of them), but don't want it to refer back to the user (nested user inside post inside user).

However, if GET /api/posts/3, 3 being the id, I want it to show the user which made the post, but not the posts again inside the user.

So basically, the annotations must be conditional in a way, and specific. Currently, this is how I am retrieving and showing the user (framework automatically converts returned object to JSON when the method is accessed).

@GET
@Path("/{id}")
public User get(@PathParam("id") Long id) {
    // Find the user and return them
    return HiberUtil.getSession().get(User.class, id);
}

Thank you in advance! I have searched for 5 hours but no solutions or reading material have solved my problem.

I fixed this by just moving away from TomEE+ and to Spring Framework (with an embedded Tomcat Server), which uses Jackson as default instead of "org.apache.johnzon. With this, I was able to use the useful annotations:

@JsonIdentityInfo(
  generator = ObjectIdGenerators.PropertyGenerator.class,
  property = "id")
@JsonIdentityReference(alwaysAsId=true)
private List<Post> posts;

This did so the JSON would look like this:

{
    "username":"test",
    "posts": [2, 5, 6, 8, 2]
}

And so alike for User inside Post too, granted it's also marked with those annotations. This is exactly what I wanted and how the front-end web framework I use, EmberJS, and many alike, expect the data to be returned. So this is perfect for me. Also, after playing around with Spring for a little while with an embedded Tomcat server, I like it more too (I've been through Glassfish also, but didn't like it).

It's important to note that the fix wasn't because I moved to Spring, but rather that I used Jackson instead. If you still want to use TomEE+, you can, just see https://stackoverflow.com/a/38915278/7120095 for how to use jackson instead of johnzon with TomEE.

For reference, johnzon supports references handling through deduplicateObjects option on the mapper (tomee 7) and johnzon.deduplicateObjects property (tomee 8, meecrowave etc...). This solves that issue replacing already seen references by a jsonpointer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM