PersistenceContext和EJB事务边界

时间:2017-12-09 18:42:33

标签: java jpa java-ee transactions ejb

我正在努力解决以下问题:

我有一个无状态bean作为实体的存储库,这个bean有一个实体管理器声明。

当我从另一个无状态bean调用这个bean时,返回一个实体,然后如果在这个新返回的实体中调用一个关系,就会抛出异常" org.hibernate.LazyInitializationException:无法懒惰地初始化一个集合角色"。据我所知,持久化上下文附加到事务,或者如果事务不存在则创建一个新事务,但在这种情况下,事务存在并且它在调用存储库bean的客户端无状态bean中启动。

这是一个简单的例子:

@Entity
public class Config{

     Long id;
     String description;

     @ManyToOne(fetch=FetchType.LAZY)
     @JoinColumn(name="equipment")
     private Equipment equipment;
}

@Entity
public class Equipment{
    Long id;
    String name;
    @OneToMany(mappedBy = "equipment")
    Config config;
}

@Stateless
public class EquipmentRepo{

    @PersistenceContext(type=PersistenceContextType.TRANSACTION)
    EntityManager em;

    public Equipment find(Long id) {
        return em.find(Equipment.class, id);    
    }

}

@Stateless
public class ServiceFacade {

    @Inject
    EquipmentRepo repo;

    public List<Config> findEquipmentConfig(Long id) {
        Equipment element = repo.find(id);
        List<Config> configurations = element.getConfig();
        return configurations;
    }
}`

2 个答案:

答案 0 :(得分:0)

延迟初始化还需要活动事务。如果您仔细检查,异常的原因将与缺少事务有关。

当无状态方法返回时,活动事务结束,并且由于事务是容器管理的,因此当您调用实体方法加载引用的延迟加载属性时,hibernate需要活动事务。

通常通过确保您的实体域未在事务/服务级别之外使用来解决此问题。当服务返回时,它应该返回映射实体的dto,带有必填字段。因此,在这种情况下,如果您需要延迟加载的属性,它仍将在同一事务中执行并映射到您自己的dto。

答案 1 :(得分:0)

首先,我假设你的Equipment课程应该是这样的:

@Entity
public class Equipment{
    Long id;
    String name;
    @OneToMany(mappedBy = "equipment")
    List<Config> config;
}

config应该是一个集合。

@OneToMany关系默认是懒惰地初始化。

执行时:

Equipment element = repo.find(id);

它返回一个Equipment个对象,该对象具有Config代理集合,而不是实体本身。

如果您在element.config方法中迭代findEquipmentConfig,那么JPA实现将在引用时加载每个Config实体。 这是有效的,因为实体管理器和事务仍处于活动状态。

但是你从调用findEquipmentConfig的方法开始迭代,我想这是一个servlet或其他一种没有事务上下文活动的web控制器。 因此,您获得了org.hibernate.LazyInitializationException

此时您可能正在考虑添加一个虚拟加载循环来预加载所有配置。

但这是一个坏主意,因为它会导致臭名昭着的N + 1 SELECT问题和一个难以扩展的应用程序。如果设备实体有1000个与之关联的配置项,那么您的应用程序将执行1001个SELECT语句来加载该对象(额外的一个是加载初始的Equipment实体)。

这实际上是JPA QL中提供的join fetch查询语法的动机之一。

您可以更改find方法,使其看起来像:

public Equipment find(Long id) {
    TypedQuery<Equipment> query = em.createQuery(
    "SELECT e FROM Equipment e LEFT JOIN FETCH e.config WHERE e.id = :id", Equipment.class);
     return query.setParameter("id", id).getSingleResult();
}

这将确保您的所有设备配置立即加载。