即使使用 @Transactional,仍会出现 LazyInitializationException

7

在获取用户评论时,我很难让简单的OneToMany映射到ManyToOne实体正常工作。其他答案建议您使用entityManager自己创建查询,但那似乎太可怕了。如果您甚至不能做一些简单的事情而不硬编码内联SQL,那么ORM有什么意义呢?看起来更可能是我做错了什么。

似乎与我使用模型从jsp访问user.getComments()方法有关。不确定最好的方法是什么。

架构:

CREATE TABLE users (
    id INTEGER AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(255) UNIQUE NOT NULL,
    created_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE comments (
    id INTEGER AUTO_INCREMENT PRIMARY KEY,
    comment_text VARCHAR(255) NOT NULL,
    photo_id INTEGER NOT NULL,
    user_id INTEGER NOT NULL,
    created_at TIMESTAMP DEFAULT NOW(),
    FOREIGN KEY(user_id) REFERENCES users(id)
);

用户控制器方法:

@RequestMapping("/user")
public ModelAndView getUser(@RequestParam int id) {
    return new ModelAndView("user", "message", userService.getUser(id));
}

用户服务:

@Service
public class UserService {

    private UserDAO userDAO;

    @Autowired
    public UserService(UserDAO userDAO) {
        this.userDAO = userDAO;
    }

    @Transactional
    public User getUser(int id) {
        return userDAO.getUser(id);
    }

}

UserDAO:

@Repository
public class UserDAO {

    @PersistenceContext
    EntityManager entityManager;

    @Nullable
    public User getUser(int id)
    {
        return entityManager.find(User.class, id);
    }
}

用户实体:

@Entity
@Table(name="users")
public class User {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="id")
    private int id;

    @Column(name="username")
    private String userName;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name="created_at")
    private Date createdAt;

    @OneToMany(mappedBy="user")
    private List<Comment> comments;

    public int getId() {
        return id;
    }

    public String getUserName() {
        return userName;
    }

    public Date getCreatedAt() {
        return createdAt;
    }

    @Transactional
    public List<Comment> getComments() {
        return comments;
    }
}

评论实体:

@Entity
@Table(name="comments")
public class Comment {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="id")
    private int id;

    @Column(name="comment_text")
    private String commentText;

    @Column(name="photo_id")
    private int photoId;

    @ManyToOne
    @JoinColumn(name="user_id")
    private User user;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "created_at")
    private Date createdAt;

    @Override
    public String toString() {
        return "Comment [id=" + id + ", commentText=" + commentText + ", photoId=" + photoId + ", userId=" + user.getId()
                + ", createdAt=" + createdAt + "]";
    }

    public int getId() {
        return id;
    }

    public String getCommentText() {
        return commentText;
    }

    public int getPhotoId() {
        return photoId;
    }

    public User getUser() {
        return user;
    }

    public Date getCreatedAt() {
        return createdAt;
    }
}

堆栈跟踪:

严重: 在上下文中为servlet [dispatcher]的Servlet.service()处理抛出异常[/test]路径下的[WEB-INF/jsp/user.jsp]处理异常发生了异常,行号[34]。堆栈跟踪: org.hibernate.LazyInitializationException:无法延迟初始化角色的集合:com.instagramviewer.entity.User.comments,无法初始化代理 - 没有会话 在org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:602)处 在org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:217)处 在org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:581)处 在org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:148)处 在org.hibernate.collection.internal.PersistentBag.iterator(PersistentBag.java:303)处 在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.jsp.user_jsp._jspx_meth_c_005fforEach_005f0(user_jsp.java:285)处 在org.apache.jsp.WEB_002dINF.jsp.user_jsp._jspService(user_jsp.java:172)处 at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70) 在javax.servlet.http.HttpServlet.service(HttpServlet.java:741)处 在org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:476)处 在org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:385)处 at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:329) 在javax.servlet.http.HttpServlet.service(HttpServlet.java:741)处 在org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)处 在org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)处 在org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)处 在org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)处 在org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)处 在org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:712)处 在org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:459)处 在org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:384)处 在org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:312)处 在org.springframework.web.servlet.view.InternalResourceView.renderMergedOutputModel(InternalResourceView.java:170)处 at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:316) 在org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1370)处 在org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1116)处 在org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1055)处 在org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)处 在org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)处 在org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)处 在javax.servlet.http.HttpServlet.service(HttpServlet.java:634)处 在org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)处 在javax.servlet.http.HttpServlet.service(HttpServlet.java:741)处 在org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)处 在org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)处 在org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)处 在org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)处 在org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)处 在org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:200)处 在org.apache.catalina.core.StandardContextVal

尝试将我的服务方法更改为初始化getComments。现在Hibernate.initialize()抛出了LazyInitializationException。@Transactional public User getUser(int id) { User user = userDAO.getUser(id); Hibernate.initialize(user.getComments()); return user; } - b15
尝试在@OneToMany(mappedBy="user",fetch = FetchType.LAZY)@ManyToOne(fetch = FetchType.LAZY)中使用。 - SRy
没有运气,结果相同。 - b15
4个回答

4

我犯了两个错误:

第一个错误:我忘记在我的Spring Java配置类中启用事务管理。

@Configuration
@EnableTransactionManagement
public class AppConfig {

  @Bean
  public LocalContainerEntityManagerFactoryBean factoryBean() {
      LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
      factory.setPersistenceProviderClass(HibernatePersistenceProvider.class);

      return factory;
  }

  @Bean
  public PlatformTransactionManager transactionManager(){
     JpaTransactionManager transactionManager = new JpaTransactionManager();
     transactionManager.setEntityManagerFactory(factoryBean().getObject() );
     return transactionManager;
  }
}

第二步:正如Vlad所说,在我的服务层中,我需要将评论列表初始化为交易的一部分。
@Transactional
public User getUser(int id) {
    User user = userDAO.getUser(id);
    Hibernate.initialize(user.getComments());
    return user;
}

现在我可以避免使用join fetch解决方案,只需在我想要检索评论列表的任何服务层中添加一行Java代码即可。

1
开始处理一个旧项目,结果遇到了同样的问题。浪费了很多时间,现在终于得到了你的答案。非常感谢! - Mukit09

2
<最初的回答> 因为当您尝试在JSP级别加载评论时,@Transactional将无法起作用(它在getUser方法开始时打开并在完成时关闭事务)。 可能的方法是在事务内初始化您的评论。
@Transactional
public User getUser(int id) {
    User user = userDAO.getUser(id);
    user.getComments().size(); //initializing
    return user;
}

这是最原始的方法,但应该适用于您。如果它有效,您可以稍后了解“命名实体图”,这是初始化所需的延迟关联的更高级方法。最初的回答。

那么 Hibernate.initialize(user.getComments()); 代替 .size() 都抛出了 LazyInitializationException 异常。真的很令人困惑。 - b15
1
使用javax包中的@Transactional。其次,您需要在每个代理上调用Hibernate#initialize,而不是在列表上调用。 - Andronicus
@Andronicus 为什么要使用 @Transactional?每个代理的初始化是什么样子的? - b15
你需要在foreach中这样做,这会导致n+1个查询问题。看看我的解决方案,它会主动获取所有评论。 - Andronicus
所以 'proxy' = Comment 的实例?我不知道如何循环遍历一个无法获取而又会引发异常的列表。 - b15
显示剩余4条评论

2

@Vlad已经注意到,您正在事务之外引用Comment。由于您正在使用jpql,因此可以立即获取User及其评论:

原始答案:最初的回答

public List<User> getAllUsers()
{
    TypedQuery<User> query = entityManager.createQuery("SELECT e from User e left join fetch e.comments", User.class);
    return (List<User>) query.getResultList();
}

我不应该在代码中包含getAllUsers,因为它没有被jsp调用。getUser(id)是我想要显示评论的那个。我将尝试将此解决方案适应通过id检索单个用户的情况。 - b15
我暂时接受这个。我真的不喜欢这是必要的想法,也不确定我是否相信它。 - b15
1
我认为这很棒,你有疑虑。如果你找到更好的解决方案,可以在这里发布,我很乐意学习。干杯,伙计。 - Andronicus
是的,谢谢,我很感激。目前我只需要尝试理解为什么@Transactional无法处理会话本身。 - b15

0

我一开始试图避免这样做,但我想保持实体的获取类型为lazy的最佳方法是在DAO层创建一个使用join fetch进行急切获取的方法。

@Nullable
    public User getUser(int id)
    {
        TypedQuery<User> query = entityManager.createQuery("SELECT e from User e left join fetch e.comments c where e.id = :id", User.class);
        query.setParameter("id", id);
        return query.getSingleResult();
    }

如果有其他方法不涉及这种丑陋的硬编码查询,我会接受答案。 - b15
你使用了 left join fetch 复制了我的答案。请接受任意一个以帮助其他遇到同样问题的人。 - Andronicus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接