Поведение прокси JPA и Hibernate

я пытался наблюдать за поведением прокси-сервера JPA2/Hibernate4 ниже,

// Круговая сущность с ленивой загрузкой:

@Entity
public class Employee {

 @Id@Generated
 int id;
 String name;
 @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
 Employee boss;

 public String toString() {
  return id + "|" + name + "|" + boss;
 }

 //getters and setters ...

}

// Сохраняемые сущности:

// Outer entity:
Employee employee = new Employee();
employee.setName("engineer");
// Inner entity:
Employee boss = new Employee();
boss.setName("manager");
employee.setBoss(boss);

entityTransaction.begin();
entityManager.persist(employee);
entityTransaction.commit();
System.out.println(employee);

// Выход:

Hibernate: insert into Employee (id, boss_id, name) values (default, ?, ?)
Hibernate: insert into Employee (id, boss_id, name) values (default, ?, ?)

2|engineer|1|manager|null

// Загружаем внешний объект:

String queryString = "select e from Employee e where e.id=" + employee.getId();
Query query = entityManager.createQuery(queryString);
Object loadedEmployee = query.getSingleResult();
System.out.println(loadedEmployee.getClass().getSimpleName());

// Выход:

Hibernate: select employee0_.id as id2_, employee0_.boss_id as boss3_2_, employee0_.name as name2_ from Employee employee0_ where employee0_.id=2 limit ?

Employee

К моему удивлению, загруженный внешний объект выше все еще обычный, но я ожидал, что он будет Hibernate proxy в результате lazy loading. Я мог что-то пропустить здесь, так как это сделать правильно? Простой, но конкретный пример приветствуется!

@РЕДАКТИРОВАТЬ

Согласно ответу от @kostja, я адаптировал код и отладил его в режиме SE ниже, ни LazyInitializationException не удалось создать, ни boss property проксировать. Любые дополнительные подсказки?

окно кода

окно отладки

@ИЗМЕНИТЬ 2

Наконец, я бы подтвердил, что ответ от @kostja, несомненно, великолепен.

Я тестировал в режиме EE, поэтому proxied boss property наблюдался ниже,

// LazyInitializationException брошено:

public Employee retrieve(int id) {
 Employee employee = entityManager.find(Employee.class, id);
 // access to the proxied boss property outside of persistence/transaction ctx
 Employee boss = employee.getBoss();
 System.out.println(boss instanceof HibernateProxy);
 System.out.println(boss.getClass().getSimpleName());
 return boss;
}

// Зеленый свет после установки Spring Tx на место:

@Transactional
public Employee retrieve(int id) ...

// Выход:

true
Employee_$$_javassist_0

Также можно обратиться к 20.1.4. Инициализация коллекций и прокси из документов Hibernate.


person sof    schedule 14.11.2012    source источник


Ответы (1)


Это ожидаемое поведение JPA. Нет никаких причин для проксирования объекта из вашего запроса — это обычный результат запроса. Однако свойство boss этого объекта должно быть прокси. Однако он не скажет, если его спросят - когда вы выполняете какие-либо операции с лениво загружаемым свойством управляемого объекта, он запускает выборку.

Таким образом, вы должны получить доступ к свойству босса вне транзакции. Если он не был извлечен, вы получите LazyInitializationException.

То, как вы это сделаете, зависит от вида EntityManager и PersistenceContext.

  • работает только с JPA 2.0 - вызовите em.detach(loadedEmployee), а затем получите доступ к свойству boss.

Для JPA 1:

  • Если вы работаете в среде Java EE, пометьте метод @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED), чтобы приостановить транзакцию.

  • В среде SE с пользовательскими транзакциями вызовите transaction.commit() перед доступом к свойству boss.

  • Если используется РАСШИРЕННЫЙ PersistenceContext, который переживет транзакцию, вызовите em.clear().

EIDT: я полагаю, что причина, по которой вы не получаете исключение, заключается в том, что FetchType.LAZY — это просто подсказка для провайдера JPA, поэтому нет гарантии, что свойство будет загружаться лениво. В отличие от этого, FetchType.EAGER гарантирует нетерпеливый выбор. Я полагаю, ваш провайдер JPA предпочитает загружаться с нетерпением.

Я воспроизвел пример, хотя и немного по-другому, и я воспроизводимо получаю LazyInitializationException в операторе журнала. Тест представляет собой тест Arquillian, работающий на JBoss 7.1.1 с JPA 2.0 поверх Hibernate 4.0.1:

@RunWith(Arquillian.class)
public class CircularEmployeeTest {
    @Deployment
    public static Archive<?> createTestArchive() {
        return ShrinkWrap
                .create(WebArchive.class, "test.war")
                .addClasses(Employee.class, Resources.class)
                .addAsResource("META-INF/persistence.xml",
                        "META-INF/persistence.xml")
                .addAsResource("testSeeds/2CircularEmployees.sql", "import.sql")
                .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");
    }

    @PersistenceContext
    EntityManager em;

    @Inject
    UserTransaction tx;

    @Inject
    Logger log;

    @Test
    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    public void testConfirmLazyLoading() throws Exception {
        String query = "SELECT e FROM Employee e WHERE e.id = 1";

        tx.begin();
        Employee employee = em.createQuery(query,
                Employee.class).getSingleResult();
        tx.commit();
        log.info("retrieving the boss: {}", employee.getBoss());
    }
}
person kostja    schedule 14.11.2012
comment
Спасибо. Не могли бы вы проверить мою версию выше в соответствии с вашими намеками? - person sof; 14.11.2012
comment
@sof - провел некоторое тестирование - возможно, вы тоже сможете это воспроизвести - person kostja; 15.11.2012
comment
Большое спасибо. я преуспел в другом тесте выше в режиме EE. - person sof; 15.11.2012
comment
@sof Пожалуйста. Рад, что вы можете подтвердить. Я уже начал сомневаться в своих рассуждениях ;) - person kostja; 15.11.2012