Java: JPA: ObjectDB: загрузка перед отсоединением: почему поиск по "навигации и доступу" иногда не работает в операторе if
Этот вопрос возник из публикации на форуме ObjectDB, в надежде, что более широкое сообщество JPA сможет предложить некоторые идеи. Некоторые аспекты могут быть специфическими для реализации ObjectAB JPA
objectdb-2.6.3_04 JDK1.7 опция VM во время выполнения: -javaagent:lib/objectdb.jar
Эта проблема возникает только в конкретном крупном веб-приложении. У меня есть тестовое веб-приложение меньшего размера, параллельное большому веб-приложению, но проблема не возникает в меньшем веб-приложении (как я продемонстрирую ниже), поэтому нет смысла делать это доступным здесь. Я не смог найти разницу. Я показываю выбранные части кода из каждого ниже.
Я использую предварительную загрузку после em.find(id), потому что использование только JPA-аннотаций, которое является подходом "один размер подходит всем", не отвечает моим потребностям во всех ситуациях. Я могу настроить запрос сущности @EJB с помощью специального загрузчика, который - после выполнения em.find (id) - выполняет выбранные операции (при "посещении" загруженного управляемого объекта) для загрузки и извлечения нужных значений.
В дополнение к постоянным полям мои сущности имеют переходные методы вычисления, и вызов их во время загрузки перед отсоединением должен (и обычно делает) загружать все необходимое для вычисления временного значения экспертной системы, которое затем должно последовательно пересчитываться после отсоединения и используется в веб-интерфейсе JSF (здесь далее не показано).
Я не буду давать слишком много подробностей о классах сущностей, сначала я продемонстрирую проблему, с которой я столкнулся, но вы должны знать это ниже:
LightingZone является подклассом сущности Block
Блок имеет свойство 'present', которое является "глубокой" сущностью BooleanValue-обёртки логического значения. Это @OneToOne с явной LAZY выборкой.
Сущность BooleanValue оборачивает простое логическое свойство 'value'.
У LightingZone есть свойство 'v_NLA', которое является "глубокой" оболочкой значения Float сущности FloatQuantity. Это @OneToOne с явной LAZY выборкой.
Сущность FloatQuantity оборачивает простое свойство Float 'value'.
(Кроме того, getL_LightingZone() ниже представляет собой универсальный объект обтекания List со списком LightingZone, доступ к которому осуществляется через getL_LightingZone(). GetEls(). Это не играет роли в возникшей проблеме.)
Переменная сущности @OneToOne 'present' сущности Block представляет собой 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;
}
...
(Я не буду здесь вдаваться в подробности о том, почему используются такие оболочки значений, но у нас есть очень веские причины, в том числе возможность легко ссылаться на "глубокое" значение в экспертной системе.)
И у блока есть:
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).
В противном случае, сделав искусственное использование lz.getPresent (). GetValue() - перед оператором if - этот компилятор не будет удален.
Это не работает (не достаточно) перед проверкой оператора if:
Просто вызывая lz.getPresent (). GetValue () где-то, но не используя результат каким-либо образом (компилятор просто удаляет его, для запуска загрузки его необходимо каким-то образом сохранить в переменной, которая в конечном итоге используется ИЛИ просто используется для ведения журнала или вывод, подтвержденный с помощью javap -c).
"Прикосновение" к id с помощью lz.getPresent (). GetId() перед проверкой оператора if.
Это должно быть логическое значение, заключенное в оболочку, и оно должно быть снаружи и до того, как указано, если оператор test.
Я очень тщательно (не показан здесь) также исследовал состояния загрузки ObjectDB с помощью Persistence.persistenceUtil() до и после этого проблемного оператора if, и это согласуется с тем, что приведено выше. 'Lz.present.value' и 'lz.v_NLA.value' являются нулевыми и НЕ загружаются перед проблемным оператором if, а lz.present.value'загружается (и имеет значение true) впоследствии, в тех случаях, когда он проходит оператор if вообще (потому что, например, используется временный держатель 'test').
Я попытался изолировать его с помощью более простого тестового веб-приложения с точно такой же стратегией загрузки перед отсоединением, но не смог воспроизвести проблему. Следующие работы БЕЗ хранения теста во временной переменной:
@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, очевидно, идентичны.
Следовательно, полный ответ на этот вопрос / проблему объясняет, почему в одном случае может потребоваться хитрость с предварительной загрузкой и сохранением lz.present.value перед оператором if, но не в другом (другими словами, в чем смысл Разница может быть, что я должен проверить).
В идеале я хочу воспроизвести проблему в тестовом приложении, чтобы полностью ее понять.
Мне потребовались целую вечность, чтобы наконец найти / идентифицировать эту проблему, поскольку я не представлял, что выполнение доступа к загрузке внутри оператора if может иметь значение (и в конце концов, это не вызывает проблем в мини-тестовом приложении). Я тщетно пытался найти какую-то разницу между реальным веб-приложением и мини-тестовым приложением, теперь я действительно ошеломлен (отсюда и это подробное сообщение на форуме).
Для смелых, имейте в виду, что для такого типа проблем использование отладчика не является ответом (по крайней мере, обход с отладчиком NetBeans не помог мне, так как каждый раз, когда вы проверяете переменную, она запускает ее загрузку, предотвращая обнаружение реальная проблема.) Точно так же использование журнала отладки также может инициировать загрузку. Это кот Шредингера из JPA.
1 ответ
Я считаю, что это обходной путь, а не полностью удовлетворительный ответ.
Служба поддержки ObjectDB тщательно рассмотрела этот случай и предложила попробовать использовать усовершенствование после компиляции в качестве задачи Ant в build.xml, а не полагаться на расширение времени загрузки (Java-агент) с помощью этой опции виртуальной машины:
-javaagent:lib/objectdb.jar
Этот агент не расширяет скомпилированные файлы классов обратно в /build/web/WEB-INF/classes, он просто расширяет их при загрузке в JVM (в памяти).
Это не должно (в соответствии с поддержкой ObjectDB) иметь значение, но это определенно имеет значение в моей операционной среде (при развертывании веб-приложения на Glassfish4.1), и я встречал другие тонкие различия в других местах в других случаях. В любом случае проблема, о которой сообщалось, исчезла, когда я использовал усовершенствование задачи Ant после компиляции.
Должна быть точка различия, но это еще предстоит определить.