使用hibernate和jersey JSP viewables实现session-per-request + lazy抓取

时间:2012-05-22 15:59:38

标签: java hibernate jsp jersey lazy-loading

让我们在一个应用程序中说你有一个名为User的实体。每个用户都可以使用延迟抓取加载许多文章。

@Entity
@Table(name = "Users")
public class User {
  private Integer id;
  private Set<Article> articles = new HashSet<Article>();

  ...

  @OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = CascadeType.ALL)
  public Set<Article> getArticles() {...}
}

在Web服务端,我想通过Id加载用户并将其传递给可返回和显示的泽西Viewable JSP。我还想为每个请求使用一个会话。

@GET
public Response getUser() {
  Session session = getSessionFactory().openSession();
  session.beginTransaction();

  User user = (User) session.getNamedQuery("getUserById").setInteger("id", 1).uniqueResult();
  Map<String, Object> dataMap = new HashMap<String, Object>();
  dataMap.put("user", user);
  Viewable v = new Viewable("/user", dataMap);
  Response r = Response.ok(v).build();

  session.getTransaction().commit();
  session.close();
  return r;
}

在user.jsp中,我想访问文章并将其打印出来。

<c:forEach var="a" items="${it.user.articles}">
  <span>${a.title}</span>
</c:forEach>

运行此代码并尝试请求JSP最终会抛出异常:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: my.package.User.articles, no session or session was closed

这是因为显然创建Viewable,将其传递给ResponseBuilder并调用build()实际上并不创建JSP。在我的方法返回并且会话已经关闭之后,JSP就会在某个地方的某个地方创建。

一个明显的选择是将文章的获取类型设置为渴望,但这并不理想,因为在大多数情况下,当我使用User对象时,我不需要加载或显示文章。

在我的方法返回之前,有没有办法强制泽西解释和构造jsp?是否有某种onResponse或postResponse监听器我可以通过我的代码来关闭会话?

我对处理打开/关闭hibernate会话的方式有什么不同吗?从谷歌搜索,每个请求一个会话似乎是大多数人提倡使用,所以我想我会尝试。

3 个答案:

答案 0 :(得分:0)

听起来你感兴趣的是the Open Session In View pattern,虽然有些人consider it an anti-pattern.

答案 1 :(得分:0)

"open session in view"方法确实是解决此问题的方法。

另一种方法是使用EJB或Spring。然后他们将接管交易处理。当与JPA EntityManager一起使用时,EJB完全透明地执行它,而Spring为此提供了org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter过滤器。

如果您的容器支持EJB,那么只需创建一个@Stateless

@Stateless
public class UserService {

    @PersistenceContext
    private EntityManager em;

    public User find(Integer id) {
        return em.createNamedQuery("getUserById", User.class)
            .setParameter("id", id)
            .getSingleResult();
    }

}

并在您的网络服务中使用它,如下所示:

@EJB
private UserService userService;

@GET
public Response getUser() {
    User user = userService.find(1);

    Map<String, Object> dataMap = new HashMap<String, Object>();
    dataMap.put("user", user);
    Viewable v = new Viewable("/user", dataMap);
    Response r = Response.ok(v).build();
    return r;
}

不再需要担心交易和延迟提取。

答案 2 :(得分:0)

你说“在大多数情况下,当我使用User对象时,我不需要加载或显示文章”,但在这种情况下,你显然确实需要这些数据。这是一个很好的例子,说明如何在每个用例中更好地处理获取策略而不是映射中的静态。

例如,在这里,大概是您的getUserById命名查询类似于:

select u from User u where u.id = :id

而是使用以下查询(创建新的命名查询getUserByIdWithArticles

select u from User u join fetch u.articles where u.id = :id

每个用例所需的数据几乎总是因用例而异。获得恰当数量的数据始终是提高性能的最佳途径。