JSF,CDI,EJB + JPA和Transaction

时间:2014-04-21 13:27:08

标签: jsf jpa ejb cdi

使用JSF,CDI,EJB + JPA进行JEE 7设置我遇到了问题"未能懒惰地初始化集合"。我找到了一个解决方案,但我想确认这是一个正确的解决方案,并且我正确地理解了这个问题。

设置基本上是:

JSF - > CDI Bean - > EJB3 - > JPA

在JSF页面中我基本上做了:

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

CDI Bean:

@Dependent
@Named
public class DishService {

    @Inject
    private DatabaseServiceLocal databaseService;

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

}

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

JPA实体菜:

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

和成分:

@ManyToOne
private Dish dish;

运行它会产生错误:

未能懒惰地初始化角色集合:entity.Dish.ingredients,无法初始化代理 - 无会话

据我了解,这是因为当EJB的公共方法getDishes()完成时,JTA容器管理的事务结束。 JPA实体被分离并且会话关闭,并且不可能再懒得加载JSF页面中的成分。

经过一些研究,似乎最普遍接受的解决方案是JOIN FETCH。所以我将JPA查询更改为

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

问题解决了。我不得不添加DISTINCT,因为没有它,查询会产生所有菜肴和配料的笛卡尔积,我想这是预期的。

最后一个问题是:我是否正确理解了问题并且是JOIN FETCH正确的解决方案?

2 个答案:

答案 0 :(得分:3)

是的,在查询上设置连接提取是最合适的方式。

您还可以使用实体图表(JPA 2.1中的新功能)来进行提取。

发生这种情况的原因是事务在CDI托管bean中启动。 JSF将调用托管bean,但它周围有几个包装器。它试图获取交易之外的值。

答案 1 :(得分:1)

对于@Many类型的关系,默认FetchTypeLAZY,这意味着List<Ingredient>在第一次访问之前不会加载。

  

FetchType fetch(可选)关联是否应该是懒惰的   装载或必须急切地取出。 EAGER战略是一项要求   在关联实体必须的持久性提供程序运行时上   急切地抓住。 LAZY策略暗示了持久性   提供者运行时

     

默认值:javax.persistence.FetchType.LAZY

     

自:JPA 1.0

是的,对于您的情况,JOIN FETCH解决方案看起来不错。