objectdb-2.6.3_04
JDK1.7
VM option at runtime: -javaagent:lib/objectdb.jar
Experimental mode: objectdb.temp.no-detach NOT USED
This problems only seems to occur in my large web application. I know that mini demonstrations of problems are preferred on this forum, and 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 (yet). I have been unable to find the point of difference. So I will try first just showing selected portions of code from each and see whether ObjectDB support or your readers can figure out what is happening and why.
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 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 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).
- [EDIT: 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 statemetn test:
- Just calling lz.getPresent().getValue() somewhere, but not using the result somehow (I guess 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) [EDIT: confirmed using javap -c].
- "Touching" the id with lz.getPresent().getId() before the test. [EDIT: See results of text in next post below.]
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 'present.value' and 'v_NLA.value' are null and NOT loaded before the problematic if statement, and '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.
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.
Darren Kelly, Webel IT Australia