【问题标题】:org.hibernate.LazyInitializationException: even with @Transactionalorg.hibernate.LazyInitializationException:即使使用@Transactional
【发布时间】:2018-06-20 03:17:25
【问题描述】:

我正在尝试打印一个用户的所有帖子,但一组帖子无法加载,并且出现此异常(下面的 Stacktrace)。

控制器

@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());
}

用户管理器

@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);
    }
}

用户

@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;
    }
}

我正在通过@Transactional 类中的服务层访问帖子,所以我不确定到底出了什么问题。起初我在评论中使用了代码,所以我认为这是因为我在Controller 的第一个和以后的帖子中得到了user,但是改为在一行中调用它并没有帮助。你能给我提示如何解决这个问题吗? 我想避免使用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)

编辑。

我尝试使用 Kayaman 方法,因此编辑了我的代码,现在我得到了这个:

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}}]

这是我更改后的UserDaoJpa

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;
    }
}

你有什么建议吗?

【问题讨论】:

  • 用户是否在 Post 实体中映射为 ManyToOne?
  • posts 在退出事务边界时被延迟加载。您可以急切地获取它们,也可以在边界内移动序列化
  • @MaciejKowalski 是的,是@ManyToOne(fetch = FetchType.LAZY) public User getAuthor() { return author; }

标签: java hibernate spring-mvc jpa


【解决方案1】:

当您return user.getPosts(); 时,您将返回未初始化的惰性代理。当在 JSP 中访问它时,事务上下文早已不复存在,您不希望使用 Open Session In View 反模式来保持事务上下文处于打开状态。

我会编写一个单独的查询来获取帖子,并使用JOIN FETCH 急切地获取它们,而不会使关系急切。

如下所示,有两个选项,具体取决于您是否也想要User。

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);

【讨论】:

  • 感谢您的回复。我试过你的方法,但现在我得到了QueryException
  • @Sk1X1 啊,我的错,我举了一个例子,JOIN FETCH 是必要的,但如果你只是对帖子感兴趣,你甚至不需要从数据库中检索用户。
【解决方案2】:

您的事务仅跨越 DefaultUserManager,但不在调用它的位置。

你写了你不想要的FetchType.EAGER。那么,如果您在交易期间仍强制手动获取它们会发生什么? 例如天真的方法:

@Override
public Set<Post> getUsersPosts(String username) {
    Set<Post> ret = new HashSet<Post>();
    User user = findByUsername(username);
    ret.addAll(user.getPosts());
    return ret;
}

【讨论】:

    【解决方案3】:

    在事务中强制延迟集合初始化:

    @Override
    public Set<Post> getUsersPosts(String username) {
        User user = findByUsername(username);
        Hibernate.initialize(user.getPosts());
        return user.getPosts();
    }
    

    【讨论】:

    • 不是很理想。当您可以一步完成时,您在这里执行了两个步骤。
    【解决方案4】:

    我认为您的映射有问题。 您将 Post 指定为关系的拥有方,在这种情况下,用户需要指定其他属性:

    @ManyToOne(fetch = FetchType.LAZY) 
    @JoinColumn(name="createdBy_ID", insertable = false, updatable = false)
    public User getAuthor()
    

    但在我看来,理想情况下,您的映射应该如下所示:

    在Post:

    @ManyToOne(fetch = FetchType.LAZY) 
    @JoinColumn(name="createdBy_ID")
    public User getAuthor()
    

    和User:

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL
             , orphanRemoval = true, mappedBy="author")
    public Set<Post> getPosts()
    

    更新

    关于仅返回基于用户的帖子的查询。它应该没有提取:

    TypedQuery<Post> query = em.createQuery(
        "select p from User u left join u.posts p where u.username = :username"
       , Post.class);
    

    【讨论】:

    • 感谢您的回复。我用mappedBy。它不能解决我的问题,但如果我做对了,那就是正确的方法
    • 您可以查看我的更新。您也可以考虑进行内部连接
    猜你喜欢
    • 2019-07-11
    • 1970-01-01
    • 1970-01-01
    • 2018-04-21
    • 2013-11-15
    • 2021-10-28
    • 1970-01-01
    • 2022-11-30
    • 1970-01-01
    相关资源
    最近更新 更多