简体   繁体   中英

JPA all referenced relations are retrieved (ManyToMany - ManyToOne - OneToMany)

Working on this 'twitter' application where a user can have posts @OneToMany and can have followers @ManyToMany .

While retrieving a user all it's posts and followers get retrieved as well.

This is all correct but it's also retrieving every 'poster' for each post (which is the user itself) and for each follower, all it's posts and followers.

I can't figure out how to limit this to the user itself.

User

@Entity
@Table(name = "User")
@NamedQueries({
  @NamedQuery(name = "User.findAll", query = "SELECT u FROM User u"),
  @NamedQuery(
    name = "User.auth",
    query = "SELECT u FROM User u WHERE u.username = :username AND u.password = :password"
  )
})
public class User {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

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

  @Column(nullable = false)
  private String password;

  @ManyToMany
  @OneToMany(mappedBy = "poster", cascade = CascadeType.ALL)
  private List<Post> posts = new ArrayList<>();

  @JoinTable(name = "Followers",
    joinColumns = {
      @JoinColumn(name = "USER_ID", referencedColumnName = "ID")
    },
    inverseJoinColumns = {
      @JoinColumn(name = "FOLLOWER_ID", referencedColumnName = "ID")
    }
  )
  private List<User> followers = new ArrayList<>();
 .... constructor, getters and setters

Post

@Entity
@Table(name = "Post")
public class Post {

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

  @ManyToOne
  private User poster;
  .... constructor, getters and setters

Outcome I get vs what I want

{
    "id": 1,
    "username": "jim",
    "posts": [
        {
            "id": 1,
            "content": "Post 1 by jim",
            "poster": {
            // ^ this is the user itself (I don't need this one)
              "id": 1,
              "username": "jim",
              "posts": [
                // keeps recurse
              ]
            }
        }
    ],
    "followers": [
        {
            "id": 2,
            "username": "follower1",
            "posts": [
                {
                    "id": 4,
                    "content": "Post 2 by follower 1",
                    "poster": {
                      // ^ this is the follower itself (I don't need this one)
                      "id": 2,
                      "username": "follower1",
                      "posts": [
                        // Same issue
                      ]
                    }
                }
            ],
            "followers": [],    // <-- I don't need this one either
        }
    ]
}

Well it's pretty clear that fetching one user fill keeps fetching all it's relations which are recursive.

Is this a designer's fault or can this be ignored/limited?

Note: I am using Gson to serialise objects to JSON format

Update

Tried to use:

@ManyToOne(fetch = FetchType.LAZY)
private User poster;

Which works but still gets the following extra prop in JSONso not sure if this is a neath solution:

"_persistence_poster_vh": {
                "sourceAttributeName": "poster",
                "isInstantiated": false,
                "row": {
                    "Post.ID": 3,
                    "Post.CONTENT": "Post 3 by jim",
                    "Post.DATETIME": "2018-01-22",
                    "Post.POSTER_ID": 1
                },
                "isCoordinatedWithProperty": false
            }

And

  @ManyToMany(fetch = FetchType.LAZY)
  @JoinTable(
    ...
  )
  private List<User> followers = new ArrayList<>();

Which still returns all followers (which I want!) I just don't want the followers.followers and followers.posts..

You can specify fetch=FetchType.LAZY in the annotation you don't want to fetch immediately. The downside is, that if you need the data later you have to access it in the scope of the still open session.

Best guess: it's not actually fetching these objects until you try to dereference them.

Be default, JPA will eager fetch @OneToOne and @OneToMany relations, but not @ManyToOne or @ManyToMany. What happens is that when you reference these fields, it will then go and fetch the actual contents of the list.

How can you tell this is happening? Check the list's class using getFollowers().getClass() What you see won't be a LinkedList or an ArrayList but a class from your JPA provider, probably with “Lazy” somewhere in the name. When you call Size or Get on the list, it will perform the fetch.

You can set OneToOne and OneToMany relations to be lazy as well, and use EntityGraphs to determine what entities you want to eagerly fetch as well. JPA has a number of gotchas like this.

I've seen GSON mentioned, and just a warning: it won't be aware of the lazy loading lists, so you MUST tell It to avoid the properties you don't want it to follow.

Typically with JSON marshaling, you'll want it to ignore the parent object, so in Post, User should be ignored for example. Additionally links to same types should typically be ignored (followers) or else mapped specially, such that it doesn't Marshall the entire object, but only produces an array of usernames. You can tell it to ignore the actual followers field, and have it marshal a getter which returns an array of usernames to implement this.

There are two ways to handle this -

  1. You can either use @JsonIgnoreProperties(ignoreUnknown=true) anotation on attributes you want to skip while serializing the object.

  2. Or you change your FetchType to FetchType.LAZY so that you can get the required data on need basis while preparing your JSON , rather than getting all records at once.

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