Reputation: 1373
I'm trying to print all posts from one user, but set of posts won't load and I get this exception (Stacktrace below).
Controller
@RequestMapping(value = "/mainPage", method = RequestMethod.GET)
public ModelAndView getMainPage(Authentication authentication, /*@ModelAttribute("post") Post post, */ModelMap modelMap)
{
ModelAndView modelAndView = new ModelAndView("mainPage", "command", new Post());
modelAndView.addObject(ERROR_ATTRIBUTE, modelMap.get(ERROR_ATTRIBUTE));
//TODO what if don't find?
//User user = userManager.findByUsername(authentication.getName());
//modelAndView.addObject("user", user);
//modelAndView.addObject("posts", userManager.getUsersPosts(user.getUsername()));
// //modelAndView.addObject("user", user);
modelAndView.addObject("posts", userManager.getUsersPosts(authentication.getName()));
return modelAndView;
//return new ModelAndView("mainPage", "command", new Post());
}
UserManager
@Service
@Transactional
public class DefaultUserManager implements UserManager{
@Override
public User findByUsername(String username) {
return userDao.findByUsername(username);
}
@Override
public Set<Post> getUsersPosts(String username) {
User user = findByUsername(username);
return user.getPosts();
}
@Override
public List<Post> getUsersPosts(String username) {
return userDao.findPostsByUsername(username);
}
}
User
@Entity
@Table(name = "Users")
public class User extends BaseObject implements UserDetails {
/** User's posts */
private Set<Post> posts;
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
//@BatchSize(size = 5) /* Na základě odhadu, že průměrný uživatel bude mít max 5-10 šablon */
//@OrderBy(" DESC")
@JoinColumn(name="createdBy_ID")
public Set<Post> getPosts() {
return posts;
}
}
I'm accessing posts through service layer in @Transactional
class, so I'm not sure what exactly is wrong. At first I used code in comment and so I thought it was because I was getting user
in Controller
first and later posts but change to calling it in one line doesn't help. Can you give me hint how to resolve this ?
I would like to avoid using FetchType.EAGER
.
Stacktrace
root cause
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: sk1x1.domain.User.posts, could not initialize proxy - no Session
org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:575)
org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:214)
org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:554)
org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:142)
org.hibernate.collection.internal.PersistentSet.iterator(PersistentSet.java:180)
org.apache.taglibs.standard.tag.common.core.ForEachSupport.toForEachIterator(ForEachSupport.java:348)
org.apache.taglibs.standard.tag.common.core.ForEachSupport.supportedTypeForEachIterator(ForEachSupport.java:224)
org.apache.taglibs.standard.tag.common.core.ForEachSupport.prepare(ForEachSupport.java:155)
javax.servlet.jsp.jstl.core.LoopTagSupport.doStartTag(LoopTagSupport.java:256)
org.apache.jsp.WEB_002dINF.pages.mainPage_jsp._jspx_meth_c_005fforEach_005f0(mainPage_jsp.java:451)
org.apache.jsp.WEB_002dINF.pages.mainPage_jsp._jspService(mainPage_jsp.java:157)
org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:432)
org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:390)
org.apache.jasper.servlet.JspServlet.service(JspServlet.java:334)
javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
org.springframework.web.servlet.view.InternalResourceView.renderMergedOutputModel(InternalResourceView.java:168)
org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:303)
org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1244)
org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1027)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:971)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:317)
org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:127)
org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:115)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:169)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:121)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:100)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:66)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:214)
org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:177)
org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
edit.
I tried to use Kayaman approach, so edited my code and now I'm getting this:
org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=null,role=sk1x1.domain.User.posts,tableName=Posts,tableAlias=posts1_,origin=Users user0_,columns={user0_.id ,className=sk1x1.domain.Post}}]
and here is my UserDaoJpa
after change
public List<Post> findPostsByUsername(String username)
{
User user = findByUsername(username);
if(user == null) {
throw new UsernameNotFoundException(username + " was not found.");
}
TypedQuery<Post> query = em.createQuery("select u.posts from User u left join fetch u.posts where u.username = :username", Post.class);
query.setParameter("username", username);
try {
return query.getResultList();
}
catch (NoResultException e)
{
return null;
}
}
do you have any advice?
Upvotes: 4
Views: 3839
Reputation: 26522
In my opinion there is something wrong with your mappings.
You specified the Post
as the owning side of the relationship, and in that case the User would need to have additional attributes specified:
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="createdBy_ID", insertable = false, updatable = false)
public User getAuthor()
But ideally your mappings should be as follows in my opinion:
On Post
:
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="createdBy_ID")
public User getAuthor()
and User
:
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL
, orphanRemoval = true, mappedBy="author")
public Set<Post> getPosts()
Update
Regarding the query that returns only the posts based on user. It should be without a fetch:
TypedQuery<Post> query = em.createQuery(
"select p from User u left join u.posts p where u.username = :username"
, Post.class);
Upvotes: 0
Reputation: 73558
When you return user.getPosts();
you're returning the uninitialized lazy proxy. When it's accessed in the JSP, the transactional context is long gone, and you don't want to use the Open Session In View antipattern to keep the transactional context open until then.
I'd write a separate query for getting just the posts and using JOIN FETCH
to get them eagerly, without making the relationship eager.
Something like below, with two options depending on whether you want the User
too or not.
public Set<Post> getUsersPosts(String username) {
return userDao.findPostsByUsername(username);
}
// Only select posts from a user, without user entity. No JOIN needed.
@Query("SELECT u.posts FROM User u WHERE u.username = :username")
Set<Post> findPostsByUsername(@Param("username") String username);
// A Left join brings us the user even if it doesn't have posts, and
// FETCH gets the posts eagerly, so no lazy loading or performance hit
@Query("SELECT u FROM User u LEFT JOIN FETCH u.posts WHERE u.username = :username")
User findUserAndPostsByUsername(@Param("username") String username);
Upvotes: 4
Reputation: 10163
Force lazy collection initialization while still in transaction:
@Override
public Set<Post> getUsersPosts(String username) {
User user = findByUsername(username);
Hibernate.initialize(user.getPosts());
return user.getPosts();
}
Upvotes: 0
Reputation: 24271
Your transaction spans only around DefaultUserManager
, but not where it's called.
You wrote you didn't want FetchType.EAGER
. So what happens if you force fetching them manually still during your transaction?
e.g. naive approach:
@Override
public Set<Post> getUsersPosts(String username) {
Set<Post> ret = new HashSet<Post>();
User user = findByUsername(username);
ret.addAll(user.getPosts());
return ret;
}
Upvotes: 0