Многие приложения JPA используют аннотации Java в своих классах модели домена для определения правил проверки. Среды исполнения используют отражение, чтобы обнаружить эти правила проверки и применять их во время выполнения. Это все работает хорошо, пока вы не поймете, что вам нужен FetchType.LAZY для некоторых ваших ассоциаций @ManyToOne. После указания отложенной выборки вы можете обнаружить, что некоторые из ваших аннотаций исчезают, но не всегда. Так что здесь происходит? В этой статье обсуждаются последствия FetchType.LAZY и как преодолеть эту периодически возникающую проблему.
Чтобы реализовать ленивую выборку под одеялом, Hibernate расширяет ваши классы. Что это значит? По сути, Hibernate расширяет ваши классы и переопределяет свои методы во время выполнения. Это делается с помощью одной из двух библиотек манипулирования байт-кодом: Javassist или CGLib . Если вы спросите у одного из этих расширенных классов его имя, вы получите что-то вроде этого:
// JPA entity class name greensopinion.Person // CGLib enhanced class name greensopinion.Person$$EnhancerByCGLIB$$4ad0592d // Javassist enhanced class name greensopinion.Person_$$_javassist_0
Hibernate делает это только при необходимости. Поэтому, когда вы получаете экземпляр вашей сущности, он может быть улучшен, а может и нет — в зависимости от того, как он был загружен. Это может сбивать с толку, поскольку экземпляры Person не всегда могут быть одного и того же класса. Например, такой код может быть проблематичным:
Person person1 = entityManager.find(Person.class,id1);
Person person2 = entityManager.find(Person.class,id2);
// bad: contrary to what we'd expect, this comparison may
// be true sometimes, but not always
if (person1.getClass() == person2.getClass()) {
}
// bad: this may not work as expected either
if (person1.getClass().isAssignableFrom(person2.getClass())) {
}
// bad: again, sometimes true sometimes false
if (person1.getClass().isAssignableFrom(Person.class)) {
}
// this is ok: comparison will always be true (assuming person1 is not null)
if (person1 instanceof Person) {
}
В приведенном выше примере сравнения, которые выглядят прямолинейно, могут вести себя по-разному в зависимости от того, как и если эти объекты уже были загружены одним и тем же менеджером объекта. Это может быть сложно при реализации hashCode и equals .
Кроме того, у нас возникнут дополнительные проблемы, если мы объявим Person следующим образом:
@Entity
public class Person {
...
@Required
public String getFirstName() { ... }
}
Здесь мы используем аннотацию, чтобы указать нашей платформе проверки, что свойство firstName является обязательным полем (см. JSR-303 , Проверка гибернации , Spring Modules , OVal ). Наша структура проверки может сделать что-то вроде этого:
Class clazz = entity.getClass();
// iterate on class accessors
...
if (method.getAnnotation(Required.class) != null) {
...
}
Это может хорошо работать большую часть времени и может работать во всех наших модульных тестах — однако в работающем приложении это может выглядеть так, как будто наш класс потерял свои аннотации! Почему это происходит? Не волнуйтесь, ваша JVM не теряет голову или ваши аннотации. Этот код может работать не так, как ожидалось, если наша сущность является расширенной версией, так как улучшители Hibernate ProxyFactory не учитывают аннотации при переопределении методов вашей сущности.
Так зачем вообще использовать FetchType.LAZY? Большинству нетривиальных приложений JPA придется использовать FetchType.LAZY, чтобы избежать ассоциаций @ManyToOne, вызывающих каскадные нагрузки. Использование FetchType.LAZY также может уменьшить количество объединений при извлечении коллекций, что в некоторых случаях значительно повышает производительность.
Так как же нам решить эту проблему? Наш код отражения должен знать о HibernateProxy. Вот пример того, как это можно исправить:
Class clazz = PersistenceUtil.getEntityClass(entity); ... now do reflection ... // in PersistenceUtil.java public static Class getEntityClass(Object entity) { if (entity instanceof HibernateProxy) { return ((HibernateProxy)entity).getHibernateLazyInitializer().getPersistentClass(); } return entity.getClass(); }
Прерывистый и в некоторых случаях случайный характер вовлеченных симптомов затрудняет воспроизведение этой проблемы. Ленивое извлечение может показаться панацеей, однако это еще один случай, когда утечка абстракций делает нашу жизнь более сложной. Как и в большинстве случаев при разработке программного обеспечения, важно понимать детали реализации, чтобы эффективно работать с JPA.