Sk1X1
Sk1X1

Reputation: 1373

org.hibernate.LazyInitializationException: even with @Transactional

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

Answers (4)

Maciej Kowalski
Maciej Kowalski

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

Kayaman
Kayaman

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

hoaz
hoaz

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

Grzegorz Oledzki
Grzegorz Oledzki

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

Related Questions