0

I have two Entities(Group and User) which have some one to many and many to many relations and I have marked these as fetch type lazy, but while retrieving just a single Group entity I am getting all the entities which are present in the Group class which is leading to StackOverflow error.

Group.java

package com.bezkoder.springjwt.models;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Entity
@Table(name = "grp")
public class Group {

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

    @Column
    private String groupName;

    @Column
    private String createdBy;

    public Group(String groupName, String createdBy ) {
        this.groupName = groupName;
        this.createdBy = createdBy;
    }


    public Group() {

    }

    public String getCreatedBy() {
        return createdBy;
    }

    public void setCreatedBy(String createdBy) {
        this.createdBy = createdBy;
    }

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "user_group",
            joinColumns = @JoinColumn(name = "user_id",referencedColumnName = "id"),
            inverseJoinColumns = @JoinColumn(name = "group_id",referencedColumnName = "id")
    )
    private Set<User> users = new HashSet<>();

    @OneToMany(
            cascade = CascadeType.ALL,
            orphanRemoval = true
    ,fetch = FetchType.LAZY,mappedBy="group")
    private List<Post> postList =  new ArrayList<>();

    public List<Post> getPostList() {
        return postList;
    }

    public void setPostList(List<Post> postList) {
        this.postList = postList;
    }

    public Set<User> getUsers() {
        return users;
    }

    public void setUsers(Set<User> users) {
        this.users = users;
    }


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

    public String getGroupName() {
        return groupName;
    }

    public void setGroupName(String groupName) {
        this.groupName = groupName;
    }

    public Long getId() {
        return id;
    }
}

User.java

package com.bezkoder.springjwt.models;

import com.fasterxml.jackson.annotation.JsonIgnore;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

@Entity
@Table(name = "users", 
    uniqueConstraints = { 
      @UniqueConstraint(columnNames = "username"),
      @UniqueConstraint(columnNames = "email") 
    })
public class User {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;


  @ManyToMany(fetch = FetchType.LAZY)
  @JoinTable(  name = "user_roles", 
        joinColumns = @JoinColumn(name = "user_id"), 
        inverseJoinColumns = @JoinColumn(name = "role_id"))
  private Set<Role> roles = new HashSet<>();


  @ManyToMany(fetch = FetchType.LAZY,
          cascade = {
                  CascadeType.PERSIST,
                  CascadeType.MERGE
          },
          mappedBy = "users")
  @JsonIgnore
  private Set<Group> groups = new HashSet<>();

  public Set<Group> getGroups() {
    return groups;
  }

  public void setGroups(Set<Group> groups) {
    this.groups = groups;
  }
  @NotBlank
  @Size(max = 20)
  private String username;

  @NotBlank
  @Size(max = 50)
  @Email
  private String email;

  @NotBlank
  @Size(max = 120)
  private String password;
  public User() {
  }

  public User(String username, String email, String password) {
    this.username = username;
    this.email = email;
    this.password = password;
  }

  public Long getId() {
    return id;
  }

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

  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public String getEmail() {
    return email;
  }

  public void setEmail(String email) {
    this.email = email;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  public Set<Role> getRoles() {
    return roles;
  }

  public void setRoles(Set<Role> roles) {
    this.roles = roles;
  }
}

Error

java.lang.StackOverflowError: null
    at java.base/jdk.internal.misc.Unsafe.allocateUninitializedArray(Unsafe.java:1380) ~[na:na]
    at java.base/java.lang.StringConcatHelper.newArray(StringConcatHelper.java:494) ~[na:na]
    at java.base/java.lang.StringConcatHelper.simpleConcat(StringConcatHelper.java:421) ~[na:na]
    at java.base/java.lang.String.concat(String.java:2768) ~[na:na]
    at java.base/java.net.URLStreamHandler.parseURL(URLStreamHandler.java:265) ~[na:na]
    at java.base/sun.net.www.protocol.file.Handler.parseURL(Handler.java:67) ~[na:na]
    at java.base/java.net.URL.<init>(URL.java:703) ~[na:na]
    at java.base/java.net.URL.<init>(URL.java:569) ~[na:na]
    at java.base/jdk.internal.loader.URLClassPath$FileLoader.getResource(URLClassPath.java:1246) ~[na:na]
    at java.base/jdk.internal.loader.URLClassPath.getResource(URLClassPath.java:322) ~[na:na]
    at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:757) ~[na:na]
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:681) ~[na:na]
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:639) ~[na:na]
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) ~[na:na]
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521) ~[na:na]
    at com.fasterxml.jackson.databind.JsonMappingException.prependPath(JsonMappingException.java:445) ~[jackson-databind-2.13.3.jar:2.13.3]

Controller class debug(Group has infinite users and as users has groups there is some recursion happening)

enter image description here

Jens Schauder
  • 77,657
  • 34
  • 181
  • 348
Saurabh Kumar Singh
  • 303
  • 2
  • 6
  • 14
  • 1
    The ResponseEntity will call your getters in order to serialize the response values, so even if marked as lazy, response flush will end up retrieving the values. You can check if this post and see if it helps https://stackoverflow.com/questions/26657259/hibernate-and-json-is-there-a-definitive-solution-to-circular-dependencies – Jhilton Nov 09 '22 at 01:54

1 Answers1

1

What you are seeing is no indication that eager loading is happening. @Jhilton is close, although it is not the ResponseEntity that loads all the references, it is your debugger in the screenshot and Jackson in the stack trace.

Lazy loading is build to transparently load entities when needed and this is exactly what is happening when you try to display an entity in the debugger. And it is also what happens when a serializer tries to navigate the object graph.

The link given by Jhilten in the comments should help you solve the problem.

Note that there isn't an infinite number of users or groups, it is the same user and the same group over and over again.

Jens Schauder
  • 77,657
  • 34
  • 181
  • 348
  • 1
    The debugger may have loaded the references for the picture, but it was not involved in the StackOverflowError error - it shows JsonMappingException.prependPath, so it is JSON serialization traversing and fetching the graph. Ideally you'd highlight the solution aspect of the problem (Json annotations describing how to handle the serialization) as it is so darn common a problem, not just the that relationships might get fetched as you interact with the model in a debugger (which is hibernate specific. Others JPA providers will show the collection as unfetched in a debugger) – Chris Nov 10 '22 at 14:24
  • Yes, you are correct. – Jens Schauder Nov 11 '22 at 07:43