1

Lets say in an application you have an entity called User. Each User can have many Articles which are loaded using lazy fetching.

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

On the web service end, I want to load a user by Id and pass it to a jersey Viewable JSP to be returned and displayed. I also want to use a single session for every request.

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

In user.jsp, I want to access the articles and print them out.

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

Running this code and trying to request the JSP ends up throwing an exception:

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

This happens because apparently creating the Viewable, passing it to the ResponseBuilder and calling build() doesn't actually create the JSP. The JSP is created somewhere further down the line, after my method has returned and the session has been closed.

One obvious option would be to set the fetch type for articles to eager, but that's not ideal as in most cases I will not need to load or display the articles when I'm using a User object.

Is there a way to force jersey to interpret and construct the jsp before my method returns? Is there some kind of onResponse or postResponse listener I could pass my code to close the session to?

Is there anything I should be doing differently with the way I'm handling opening/closing hibernate sessions in general? From googling around, one-session-per-request seems to be what most people advocate using so I thought I'd try that.

ziggy
  • 35
  • 1
  • 5

3 Answers3

0

It sounds like what you are interested in is the Open Session In View pattern, although some folks consider it an anti-pattern.

Community
  • 1
  • 1
matt b
  • 132,562
  • 64
  • 267
  • 334
0

The "open session in view" approach is indeed the way to solve this problem.

Another way is to use EJB or Spring. They will then take over the transaction handling. EJB does it fully transparently when using with JPA EntityManager and Spring provides the org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter filter for this.

If your container supports EJB, then just create a @Stateless:

@Stateless
public class UserService {

    @PersistenceContext
    private EntityManager em;

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

}

and use it in your web service as follows:

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

No need to worry about transactions and lazy fetching anymore.

BalusC
  • 992,635
  • 352
  • 3,478
  • 3,452
0

You say that "in most cases I will not need to load or display the articles when I'm using a User object", but in this case you obviously do in fact need that data. This is a prime example of how fetching strategies are almost always better handled per-use-case rather than static in mappings.

For example, here, presumably your getUserById named query looks something like:

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

Instead, use the following query (either creating a new named query getUserByIdWithArticles:

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

How much data you need for each use case is almost always going to vary by use-case. Getting just the right amount of data is always the best path to performance gains.

Steve Ebersole
  • 8,735
  • 2
  • 42
  • 44