Question

With a JEE 7 setup using JSF, CDI, EJB + JPA I ran into the issue "failed to lazily initialize a collection". I found a solution but would like to confirm that it is a proper solution and that I correctly understood the problem.

The setup basically is:

JSF -> CDI Bean -> EJB3 -> JPA

In the JSF page I basically do:

<ui:repeat value="#{dishService.dishes}" var="dish">
    <ui:repeat value="#{dish.ingredients}" var="ingredient">
        #{ingredient.name}
    </ui:repeat>
</ui:repeat>

The CDI Bean:

@Dependent
@Named
public class DishService {

    @Inject
    private DatabaseServiceLocal databaseService;

    public List<Dish> getDishes() {
        return databaseService.getDishes();
    }

}

The EJB:

@Stateless
public class DatabaseService implements DatabaseServiceLocal, DatabaseServiceRemote {

    @PersistenceContext(unitName = "dishlist")
    private EntityManager entityManager;

    public List<Dish> getDishes() {
        final Query query = entityManager.createQuery("SELECT d FROM Dish d");
        return query.getResultList();
    }
}

The JPA entity Dish:

@OneToMany(mappedBy = "dish", 
           cascade = CascadeType.PERSIST, 
           orphanRemoval = true)
private List<Ingredient> ingredients = Collections.emptyList();

and Ingredient:

@ManyToOne
private Dish dish;

Running this produces the error:

failed to lazily initialize a collection of role: entity.Dish.ingredients, could not initialize proxy - no Session

To my understanding, this happens because the JTA container managed transaction ends when the public method getDishes() of the EJB completes. The JPA entities get detached and the session closed, and it is not possible any more to lazily load the ingredients in the JSF page.

After some research, it seemed that the most commonly accepted solution for this is JOIN FETCH. So I changed my JPA query to

entityManager.createQuery("SELECT DISTINCT d from Dish d JOIN FETCH d.ingredients");

And the problem is solved. I had to add the DISTINCT since without it, the query produces the cartesian product of all Dishes and Ingredients, which is expected I suppose.

So finally the question: Did I understand the problem correctly and is JOIN FETCH the proper solution?

Was it helpful?

Solution

Yes, setting the join fetch on the query is the most appropriate way.

You can also use entity graphs, a new feature in JPA 2.1 to do the fetching.

The reason this occurs is that the transaction starts in your CDI managed bean. JSF will call the managed bean, but there are several wrappers around it. It's trying to fetch the values outside of the transaction.

OTHER TIPS

For @Many type of relationship, the default FetchType is LAZY, which means the List<Ingredient> won't be loaded until the 1st time it's accessed.

FetchType fetch (Optional) Whether the association should be lazily loaded or must be eagerly fetched. The EAGER strategy is a requirement on the persistence provider runtime that the associated entities must be eagerly fetched. The LAZY strategy is a hint to the persistence provider runtime.

Default value: javax.persistence.FetchType.LAZY

Since: JPA 1.0

And yes, for your case, the JOIN FETCH solution looks good.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top