【问题标题】:PersistenceContext and EJB transaction boundaryPersistenceContext 和 EJB 事务边界
【发布时间】:2018-05-23 17:38:23
【问题描述】:

我正在为以下事情苦苦挣扎:

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

当我从另一个无状态 bean 调用这个 bean 时,会返回一个实体,然后如果在这个新返回的实体中调用一个关系,则会为“org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role”引发异常.据我了解,持久性上下文附加到事务,或者如果事务不存在则创建一个新的,但在这种情况下,事务存在并且它在调用存储库 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;
    }
}`

【问题讨论】:

    标签: java jpa jakarta-ee transactions ejb


    【解决方案1】:

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

    当无状态方法返回时,活动事务结束,由于事务是容器管理的,所以当您调用实体方法加载引用的延迟加载属性时,hibernate 期望活动事务。

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

    【讨论】:

    • 您好,谢谢您的回复。我理解,但是我说的是,有一个无状态 bean (beanA) 通过注入调用另一个无状态 bean (beanB),然后 beanB 检索一个实体。事务按照规范在beanA上开始,当beanB被调用时,他将自己附加到当前事务上并将实体返回给beanA,所以一路上事务仍在运行。
    【解决方案2】:

    首先,我假设您的 Equipment 类应该如下所示:

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

    config 应该是一个集合。

    @OneToMany 关系默认是延迟初始化的。

    当你执行时:

    Equipment element = repo.find(id);
    

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

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

    但是您正在从调用 findEquipmentConfig 的方法进行迭代,我想它是一个 servlet 或其他某种没有事务上下文活动的 Web 控制器。 因此你会得到org.hibernate.LazyInitializationException

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

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

    这实际上是 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();
    }
    

    这将确保您的所有设备配置一次加载。

    【讨论】:

    • 嗨,不是这样的……调用findEquipmentConfig的方法是ejb,所以事务必须是存活的。
    • 请添加堆栈跟踪。有些事你没有告诉我们。另外,您是否尝试过更改您的查询?你会得到更好的表现
    猜你喜欢
    • 2015-08-18
    • 1970-01-01
    • 1970-01-01
    • 2017-03-26
    • 2017-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多