This question is spawned from this ObjectDB forum posting, in the hope that the wider JPA community may be able to offer some insights. Some aspects may be specific to the ObjectDB implementation of JPA
objectdb-2.6.3_04 JDK1.7 VM option at runtime: -javaagent:lib/objectdb.jar
This problems only seems to occur in a specific large web application. I have a smaller test web application parallel to the large web application, but the problem does not happen in the smaller web application (as I will demonstrate below), so there is no point making that available here. I have been unable to find the point of difference. I showing selected portions of code from each below.
I am using pre-detach loading after em.find(id), because just relying on JPA-annotations, which is a "one size fits all" approach, does not meet my needs for all situations. I can configure an entity-query @EJB with a specific loader that - after the em.find(id) is performed - executes selected operations (while "visiting" the loaded managed entity) to load and fetch desired values.
In addition to the persistent fields my entities have transient computation methods, and calling these during the pre-detach loading should (and usually does) load everything needed to compute a transient expert system value, which should then be consistently re-computated once detached and used in a JSF web interface (not further shown here).
I won't give too many details about the entity classes, I will demonstrate the problem I am experiencing first, but you need to know that below:
A LightingZone is a subclass of entity Block
A Block has a property 'present' which is a "deep" Boolean value wrapper BooleanValue entity. This is @OneToOne with explicit LAZY fetch.
A BooleanValue entity wraps a simple Boolean property 'value'.
A LightingZone has a property 'v_NLA' which is a "deep" Float value wrapper FloatQuantity entity. This is @OneToOne with explicit LAZY fetch.
A FloatQuantity entity wraps a simple Float property 'value'.
(Also, the getL_LightingZone() below is a a generic List wrapping entity, with the list of LightingZone accessed via getL_LightingZone().getEls(). This plays no role in the problem encountered.)
The @OneToOne entity variable 'present' of a Block entity is a BooleanValue with:
@Entity
public class BooleanValue extends Value
{
public BooleanValue() {
}
private Boolean value;
public Boolean getValue() {
return value;
}
public void setValue(Boolean value) {
this.value = value;
}
...
(I won't go here into details about why such value wrappers are being used, but we have very good reasons, including being able to easily reference the "deep" value in an expert system.)
And the Block entity has:
private BooleanValue present;
@OneToOne(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
public BooleanValue getPresent() {
return present;
}
public void setPresent(BooleanValue present) {
this.present = present;
}
It need not necessarily be fetch = FetchType.LAZY (if you set leave it as default fetch = FetchType.EAGER the problem reported here vanishes), but for performance reasons (and for the sake of demonstrating the problem described here) it is fetch = FetchType.LAZY (which hint ObjectDB respects).
The following from my real web fails to pre-detach load, it is failing to load the test within the indicated if statement:
@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;
}
}
But strangely (to me at least) this works, storing the test in a temporary variable:
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();
There are some other things that also work if used before the if (lz.getPresent().getValue() != null && lz.getPresent().getValue()) test:
Logging the value of lz.getPresent().getValue() (or sending it to System.out).
Otherwise making some contrived usage of lz.getPresent().getValue() - before the if statement - that the compiler will not remove.
This does not work (is not enough) before the if statement test:
Just calling lz.getPresent().getValue() somewhere, but not using the result somehow (the compiler just removes it, for the loading to be triggered it has to be somehow stored in a variable that is eventually used OR just used for logging or output, confirmed using javap -c).
"Touching" the id with lz.getPresent().getId() before the if statement test.
It has to be the wrapped Boolean value, and it has to be outside and before that indicated if statement test.
I have very carefully (not shown here) also investigated the ObjectDB load states using Persistence.persistenceUtil() before and after that problematic if statement, and it is consistent with what is given above. The 'lz.present.value' and 'lz.v_NLA.value' are null and NOT loaded before the problematic if statement, and 'lz.present.value' is loaded (and true) afterwards, in those cases where it passes the if statement at all (because, for example, the temporary 'test' holder is used).
I tried to isolate it using a simpler test web app with exactly the same pre-detach loading strategy, but could not reproduce the problem. The following works WITHOUT storing the test in a temporary variable:
@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;
}
}
This test version does not suffer from the same problem, it runs fine without any "load touching" tricks, although the relevant if statement is apparently identical.
Therefore, a complete answer to this question/problem would explain why the trick with pre-loading and storing lz.present.value before the if statement might possibly be required in one case, but not the other (in other words, what the point of difference might be that I should test).
Ideally, I want to reproduce the problem in the test app to understand it fully.
It has taken me ages to finally find/identify this problem, as I did not imagine that performing the load access inside an if statement could make a difference (and after all, this did not cause problems in the mini test app). I have tried in vain to find any point of difference between the real web app and the mini test app, I am now truly bamboozled (hence this detailed forum posting).
For the brave, be warned that for this type of problem using a debugger is not the answer (at least, walking through with NetBeans debugger did not help me, as every time you inspect a variable it triggers loading of it, preventing discovery of the real problem.) Similarly, using debug logging can also trigger loading. It's the Schrödinger's cat of JPA.