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.