为什么 JPA 实体在会话之外被这样对待?

Why JPA entities are treated like this outside a session?

嗨,

我在 jpa 中有一个 "Solve failed to lazily initialize a collection of role.... exception"。我知道在会话之外,当你想检索一个惰性集合时,如果没有会话绑定,你会得到这个错误,很好。但我不明白的是,如果我有这个 spring 控制器(代码不是 100% 正确,只是为了解释我的情况):

@控制器

@Autowired
EnterpriseService enterpriseService ; 

public List<Enterprise> getAll(){


    List<Enterprise> enterprises = enterpriseService.getAll();      

    for(Enterprise enterprise:enterprises){

        enterprise.getEmployees();

    }
    return enterprises;

}

当我调用 "enterprise.getEmployees()" 时,我知道不再有会话,但为什么当我尝试调用 "enterprise.getEmployees()" 时,为什么企业被视为一个 jpa 实体而不是像一个普通的 bean?,我的意思是;据我所知,一个 jpa 实体在会话中被这样对待,但在这种情况下,它应该像普通的 java bean 一样被对待,所以对 enterprise.getEmployees() 的调用应该像调用java bean 的 get 方法,不应抛出任何惰性异常....

可能是因为 spring 控制器将企业对象视为 jpa 实体,而不仅仅是 java beans?此行为特定于 spring 个控制器?

谢谢

它不能做任何其他事情。唯一的选择是 return 一个空的员工集合,这会更糟:您会错误地假设企业有 0 名员工,这是一个有效但完全错误的结果。

要了解这样做会有多糟糕,让我们想象一个 HospitalAnalysis 实体,它有一个 DetectedDisease 实体集合。假设您尝试显示分析结果但忘记初始化集合。该页面会告诉您,您非常健康,可以安全回家,而实际上,您得了癌症,而且程序有错误。我更希望程序因异常而崩溃并得到修复,而不是不开始我的治疗。

在没有初始化集合的情况下尝试访问员工,因此不知道实际的员工集合,这只是一个错误。此错误通过抛出运行时异常发出信号。

EntityManager 编辑的实体 return 不一定是实体 class 的实例,而是扩展 class 的代理 class .在许多情况下,此类实体的持久属性也是如此(尤其是那些用 (One/Many)To(One/Many)) 注释的实体。

例如,如果您使用基于字段的访问

@Entity
public class Enterprise {
    @OneToMany
    private List<Employee> employees = new ArrayList<>();

    public List<Employee> getEmployees() {
        return employees;
    }
}

此处 JPA 提供程序将创建一个代理 class 扩展 Enterprise 并以某种方式记住数据库中的先前状态。此外,它会将 employees 列表更改为它自己的 List 实现(不需要扩展 ArrayList)。

因为代理 class 可以 "overwrite" 您的方法,它可以知道您何时调用 getEmployees() 并检查会话。但我认为这不会发生在这里,因为该方法没有使用任何 JPA 特定注释进行注释。

此外,一些框架如 Hibernate 确实支持 字节码增强 字节码检测 。这改变了 class 的实现(以字节代码形式),并用一些提供者特定的代码替换了对 employees 的每次访问。我不知道 Spring JPA 是否提供了这个,但这可能会导致检查会话。

否则,对 enterprise.getEmployees() 的任何调用都应该 return employees 的当前值——无需对会话进行任何检查,也无需 LazyInitializationException.

但是调用 enterprise.getEmployees().size() 将尝试初始化列表并检查会话 - 这可能会导致上述异常。

如果您使用的是基于 属性 的访问,情况会有些不同:

@Entity
public class Enterprise {
    private List<Employee> employees = new ArrayList<>();

    @OneToMany
    public List<Employee> getEmployees() {
        return employees;
    }
}

此处代理 class 将 委托给您的实现,而是覆盖 getEmployees() 方法和 return 它自己的 List 实现,不改变 employees。因此这里可以获得 LazyInitalizationException for enterprise.getEmployees().

备注:这描述了大多数 JPA 实现的工作方式 - 但由于这是特定于实现的,一些不寻常的框架可能会以不同的方式做事。