1

I'm building an application using Spring MVC. The flow is very basic, i.e., the Controller --> Service --> DAO and back.

Now, I'm creating a RestController, which will call the service and perform whatever I want. I am stuck with an issue which is strange, and I need help.

I have two entities User and Role. One User can have many Roles, and a Role can be assigned to many Users - basically a ManyToMany mapping. I have given FetchType.LAZY for both these dependencies.

Everything works fine in the Web application when I log-in via a browser. But, when I invoke the RESTController, I get the following error

org.springframework.http.converter.HttpMessageNotWritableException: Could not write content: failed to lazily initialize a collection of role: com.xx.yyy.entity.Role.users, could not initialize proxy - no Session (through reference chain: com.xx.yyy.entity.User["roles"]->org.hibernate.collection.internal.PersistentBag[0]->com.xx.yyy.entity.Role["users"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.xx.yyy.entity.Role.users, could not initialize proxy - no Session (through reference chain: com.xx.yyy.entity.User["roles"]->org.hibernate.collection.internal.PersistentBag[0]->com.xx.yyy.entity.Role["users"])
org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:238)
org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:208)
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:161)
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:101)
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:202)
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:71)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:126)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:777)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:706)
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:943)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:877)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:966)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:857)
javax.servlet.http.HttpServlet.service(HttpServlet.java:620)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:118)
org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:84)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:113)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:103)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:113)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:154)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:45)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilter(BasicAuthenticationFilter.java:150)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:199)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344)
org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261)

The strange thing here is, this works perfectly fine when I'm on the UI (i.e, on my web application)

Following are my Entities

User.java

@Entity
@Table(name="USERS")

public class User {
    @Id
    @Column (name="USER_ID")
    private String id;

    @NotEmpty(message = "First Name is mandatory")
    @Column (name="FIRST_NAME")
    private String firstName;

    @Column (name="LAST_NAME")
    private String lastName;

    @NotEmpty(message= "Email ID is mandatory")
    @Column (name="EMAIL_ID")
    private String emailId;

    @NotEmpty(message="Primary Phone Number is mandatory")
    @Column (name="PHONE_1")
    private String primaryPhone;

    @Column (name="PHONE_2")
    private String secondaryPhone;

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

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable( name="ROLESMAPPING", joinColumns={@JoinColumn(name="USER_ID")}, inverseJoinColumns={@JoinColumn(name="GROUP_ID")})
    private List<Role> roles = new ArrayList<Role>();


    @Transient
    private List<String> roleIDs = new ArrayList<String>();

    //Getters and Setters

}

Role.java

@Entity
@Table( name="ROLES" )
public class Role {

    @Id
    @Column( name="GROUP_ID")
    private String id;

    @Column( name="GROUP_DESC" )
    private String description;

    @ManyToMany(mappedBy="roles")
    @LazyCollection(LazyCollectionOption.TRUE)
    private List<User> users = new ArrayList<User>();

    //Getters and Setters go here
}

I added the LazyCollection annotation just before posting this question (after referring to many other posts), but it din't help.

I figured there's something to do with sending the response back to my client, because I tried to put a breakpoint in my RestController on the line after it fetches the user, and to my surprise, the User is fetched. The error comes after that, when the return user; statement is executed. Here's my Rest controller

@RestController
@RequestMapping("/api/user")
public class GenericController {

    private static final Logger logger = LoggerFactory.getLogger(GenericController.class);

    @Autowired
    private UserService userService;

    @RequestMapping("/get/{userid}")
    public User get(@PathVariable(value="userid") String userId) {
        try{
            if(StringUtils.isEmptyString(userId)){
                userId = "admin";
            }
            User user = userService.getUserById(userId);
            return user;
        }catch(Exception e){
            logger.error(e.getMessage(),e);
            return null;
        }
    }
}

Here's what I tried doing

I referred to some other posts on the same error, and I tried making the Fetch types EAGER (i.e, fetch=FetchType.EAGER) on both my entities. What happened next was, the User details are fetched along with Roles, the Roles in-turn have the Users fetched along with them, and this happens in an indefinite loop. The JSON i get as output is never-ending.

Please help me understand where I'm going wrong.

Sriram Sridharan
  • 720
  • 18
  • 43
  • You need to use a "open session in view" pattern. It seems that your `User` is pulled from the database, then the session is closed, _then_ you try and render your view (JSON or whatever) - at this point the exception is thrown. – Boris the Spider Jan 27 '16 at 15:22
  • Thanks, but how do I do that? – Sriram Sridharan Jan 27 '16 at 15:22
  • Google "Spring JPA open session in view". – Boris the Spider Jan 27 '16 at 15:23
  • Thanks very much, I did that. One of the posts in Spring Forums about this said about making my PersistenceContext ExTENDED. I did that by modifying my annotation as @PersistenceContext(type=PersistenceContextType.EXTENDED). It works now, however, as I mentioned in my post, it goes to an indefinite loop (Users fetching all roles, which inturn fetch the users and vice-versa and so on).. Am I missing something? – Sriram Sridharan Jan 27 '16 at 15:31
  • Yes. JSON does not support any sort of object reference. You **cannot** have bi-directional relationships in JSON. That should be obvious. – Boris the Spider Jan 27 '16 at 15:32
  • I'm sorry. Being new I had no idea. So, do I have to build the JSON on my own in order to return to the client? I can do that if that's the solution, but I just wonder if it's the right thing to do. All I want is to return a JSON representing my User entity. The question is, do I build it on my own, or let Spring handle it. – Sriram Sridharan Jan 27 '16 at 15:35
  • See [this question/answer](https://stackoverflow.com/questions/3325387/infinite-recursion-with-jackson-json-and-hibernate-jpa-issue). – Boris the Spider Jan 27 '16 at 15:38
  • Perfect! Thank you very much. – Sriram Sridharan Jan 27 '16 at 15:52

1 Answers1

0

Thanks to @Boris the Spider, I looked at this post, and the answer was right there!

All I did to solve this issue was to add @JsonIgnore to the field in my Child class which refers to the Parent entity, like this

@Entity
@Table( name="ROLES" )
public class Role {

    @Id
    @Column( name="GROUP_ID")
    private String id;

    @Column( name="GROUP_DESC" )
    private String description;

    @ManyToMany(mappedBy="roles")
    @JsonIgnore
    private List<User> users = new ArrayList<User>();

    //Getters and Setters go here
}

This solved the indefinitely looping issue.

Sriram Sridharan
  • 720
  • 18
  • 43