为什么 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 实现的工作方式 - 但由于这是特定于实现的,一些不寻常的框架可能会以不同的方式做事。
嗨,
我在 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 实现的工作方式 - 但由于这是特定于实现的,一些不寻常的框架可能会以不同的方式做事。