这个问题是从这个 ObjectDB 论坛帖子中产生的,希望更广泛的 JPA 社区能够提供一些见解。某些方面可能特定于 JPA 的 ObjectDB 实现
objectdb-2.6.3_04 JDK1.7 VM 运行时选项:-javaagent:lib/objectdb.jar
这个问题似乎只发生在特定的大型 Web 应用程序中。我有一个与大型 Web 应用程序平行的较小的测试 Web 应用程序,但问题不会发生在较小的 Web 应用程序中(正如我将在下面演示的那样),因此在这里提供它没有意义。我一直无法找到差异点。我从下面显示了选定的代码部分。
我在 em.find(id) 之后使用预分离加载,因为仅仅依靠 JPA 注释,这是一种“一刀切”的方法,并不能满足我对所有情况的需求。我可以使用特定的加载器配置实体查询@EJB - 在执行 em.find(id) 之后 - 执行选定的操作(同时“访问”加载的托管实体)以加载和获取所需的值。
除了持久性字段之外,我的实体还具有瞬态计算方法,并且在预分离加载期间调用这些方法应该(并且通常会)加载计算瞬态专家系统值所需的所有内容,然后一旦分离和在 JSF Web 界面中使用(此处未进一步显示)。
关于实体类我不会给出太多细节,我将首先演示我遇到的问题,但您需要了解以下内容:
LightingZone 是实体 Block 的子类
Block 有一个属性“present”,它是一个“深”的布尔值包装器 BooleanValue 实体。这是带有显式 LAZY 提取的 @OneToOne。
BooleanValue 实体包装了一个简单的布尔属性“值”。
LightingZone 有一个属性“v_NLA”,它是一个“深度”浮点值包装器 FloatQuantity 实体。这是带有显式 LAZY 提取的 @OneToOne。
FloatQuantity 实体包装了一个简单的 Float 属性“值”。
(另外,下面的 getL_LightingZone() 是一个通用的 List 包装实体,LightingZone 的列表通过 getL_LightingZone().getEls() 访问。这对遇到的问题没有任何作用。)
Block 实体的 @OneToOne 实体变量 'present' 是一个 BooleanValue,具有:
@Entity
public class BooleanValue extends Value
{
public BooleanValue() {
}
private Boolean value;
public Boolean getValue() {
return value;
}
public void setValue(Boolean value) {
this.value = value;
}
...
(我不会在这里详细说明为什么要使用这种值包装器,但我们有很好的理由,包括能够轻松引用专家系统中的“深度”值。)
Block 实体具有:
private BooleanValue present;
@OneToOne(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
public BooleanValue getPresent() {
return present;
}
public void setPresent(BooleanValue present) {
this.present = present;
}
它不一定是 fetch = FetchType.LAZY (如果您将其设置为默认 fetch = FetchType.EAGER,则此处报告的问题将消失),但出于性能原因(并且为了演示此处描述的问题),它是 fetch = FetchType.LAZY(暗示 ObjectDB 尊重)。
我的真实网络中的以下内容无法预先分离加载,它无法在指示的 if 语句中加载测试:
@Transient
public Float getNLA_m2_LightingZone() {
Float sum = 0f;
if (getL_LightingZone() != null && getL_LightingZone().getEls() != null) {
for (LightingZone lz : getL_LightingZone().getEls()) {
if (lz.getPresent().getValue() != null && lz.getPresent().getValue()) { //FAILS TO LOAD getPresent().getValue()
//THIS IS NEVER REACHED, ALTHOUGH IN FACT lz.present.value IS true IN THE DATABASE
//lz.getPresent().getValue has NOT loaded ok for the test.
Float area = lz.getV_NLA().getValue();
if (area != null) {
sum += area;
} else {
return null;//POLICY
}
}
}
return sum;
} else {
return null;
}
}
但奇怪的是(至少对我而言)这是可行的,将测试存储在一个临时变量中:
test = lz.getPresent().getValue() != null && lz.getPresent().getValue();
if (test) {
// TEST NOW PASSES FINE: lz.getPresent().getValue has INDEED loaded ok for the test.
Float area = lz.getV_NLA().getValue();
如果在 if (lz.getPresent().getValue() != null && lz.getPresent().getValue()) 测试之前使用,还有一些其他的东西也可以工作:
记录 lz.getPresent().getValue() 的值(或将其发送到 System.out)。
否则,在 if 语句之前对 lz.getPresent().getValue() 进行一些人为的使用 - 编译器不会删除。
这在 if 语句测试之前不起作用(还不够):
只是在某处调用 lz.getPresent().getValue() ,但不以某种方式使用结果(编译器只是将其删除,为了触发加载,它必须以某种方式存储在最终使用或仅用于记录的变量中或输出,使用 javap -c 确认)。
在 if 语句测试之前用 lz.getPresent().getId() “触摸” id。
它必须是包装的布尔值,并且必须在 if 语句测试指示的外部和之前。
我已经非常仔细地(此处未显示)还在有问题的 if 语句之前和之后使用 Persistence.persistenceUtil() 调查了 ObjectDB 负载状态,这与上面给出的一致。'lz.present.value' 和 'lz.v_NLA.value' 为 null 且未在有问题的 if 语句之前加载,并且在通过if 语句(因为,例如,使用了临时的“测试”持有者)。
我尝试使用具有完全相同的预分离加载策略的更简单的测试 Web 应用程序来隔离它,但无法重现该问题。以下工作无需将测试存储在临时变量中:
@Transient
public Float getComputed() {
Float val = 0f;
if (getL_InnerBlock() != null && getL_InnerBlock().getEls() != null) {
for (InnerBlock ib : getL_InnerBlock().getEls()) {
//ObjectDB PersistenceUtil claims neither ib.present.value nor ib.present.id are loaded.
if (ib.getPresent().getValue() != null && ib.getPresent().getValue()) {
//ObjectDB PersistenceUtil claims present.value now loaded.
if (f == null) {
return null;
} else {
val += f;
}
}
}
return val;
} else {
return null;
}
}
这个测试版本没有遇到同样的问题,它运行良好,没有任何“负载接触”技巧,尽管相关的 if 语句显然是相同的。
因此,对这个问题/问题的完整答案将解释为什么在一种情况下可能需要在 if 语句之前预加载和存储 lz.present.value 的技巧,但在另一种情况下可能不需要(换句话说,这有什么意义不同之处可能是我应该测试)。
理想情况下,我想在测试应用程序中重现问题以完全理解它。
我花了很长时间才最终找到/识别这个问题,因为我没想到在 if 语句中执行加载访问会产生影响(毕竟,这不会在迷你测试应用程序中造成问题)。我徒劳地试图找到真正的网络应用程序和迷你测试应用程序之间的任何区别,我现在真的被迷惑了(因此这个详细的论坛帖子)。
对于勇敢的人,请注意,对于这种类型的问题,使用调试器不是答案(至少,使用 NetBeans 调试器遍历并没有帮助我,因为每次检查变量时都会触发加载它,从而阻止发现真正的问题。)同样,使用调试日志也可以触发加载。这是 JPA 的薛定谔猫。