Java: JPA: ObjectDB: pre-detach load: 为什么通过"导航和访问"进行检索有时在 if 语句中不起作用



这个问题源于ObjectDB论坛的帖子,希望更广泛的JPA社区能够提供一些见解。某些方面可能特定于JPA的ObjectDB实现

对象数据库-2.6.3_04JDK1.7运行时的VM选项:-javaagent:lib/objectdb.jar

这种问题似乎只发生在特定的大型web应用程序中。我有一个与大型web应用程序并行的较小的测试web应用程序,但问题不会发生在较小的web应用程序中(正如我将在下面演示的那样),所以在这里提供它没有意义。我一直找不到不同的地方。我展示了从下面每个代码中选择的部分。

我在em.find(id)之后使用预分离加载,因为仅仅依靠JPA注释,这是一种"一刀切"的方法,并不能满足我在所有情况下的需求。我可以用一个特定的加载器配置实体查询@EJB,在执行em.find(id)之后,该加载器执行选定的操作(同时"访问"加载的托管实体)以加载和获取所需的值。

除了持久字段之外,我的实体还有瞬态计算方法,在预分离加载期间调用这些方法应该(通常确实)加载计算瞬态专家系统值所需的一切,一旦分离并在JSF web界面中使用,就应该一致地重新计算该值(此处未进一步显示)。

我不会给出太多关于实体类的细节,我将首先演示我遇到的问题,但你需要知道以下内容:

  • LightingZone是实体块的子类

  • 块具有属性"present",该属性是"深层"布尔值包装BooleanValue实体。这是带有显式LAZY提取的@OneToOne。

  • BooleanValue实体包装一个简单的布尔属性"value"。

  • LightingZone具有属性"v_NLA",该属性是"深度"浮点值包装FloatQuantity实体。这是带有显式LAZY提取的@OneToOne。

  • FloatQuantity实体包装一个简单的Float属性"value"。

  • (此外,下面的getL_LightingZone()是一个通用的List包装实体,LightingZone的列表通过getL_Lighting Zone(。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尊重)。

以下来自我的真实web未能预分离加载,它未能在指示的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()!=空&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.spresent.value"one_answers"lz.v_NLA.value"在有问题的if语句之前为null且未加载,并且在之后加载(且为true)"lz.sepresent.vvalue",在这些情况下,它完全通过if语句(例如,因为使用了临时"test"持有者)。

我试图使用一个更简单的测试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的薛定谔猫

我认为这是一个变通办法,不是一个完全令人满意的答案

ObjectDB支持已经对这个案例进行了有益的审查,并建议我尝试在build.xml中使用编译后增强作为Ant任务,而不是使用VM选项依赖加载时间(Java Agent)增强:

-javaagent:lib/objectdb.jar

该代理不会将编译后的类文件增强回/build/web/web-INF/classes,它只是在加载到JVM(内存中)时增强它们。

(根据ObjectDB的支持)它不应该有什么不同,但在我的操作环境中(在将web应用程序部署到Glassfish4.1时)它确实有作用,而且我在其他场合也遇到了其他细微的差异。无论如何,当我使用编译后的Ant任务增强时,报告的问题就消失了。

肯定有一点不同,但还有待确定

最新更新