5

我试图观察下面的 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 proxylazy 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 文档初始化集合和代理。

4

1 回答 1

8

这是预期的 JPA 行为。您的查询中的实体没有理由被代理 - 它是查询的常规结果。然而,这个实体的boss属性应该是一个代理。但它不会告诉您是否被询问 - 当您对托管实体的延迟加载属性执行任何操作时,它将触发获取。

所以你应该在事务之外访问boss属性。如果尚未提取,您将获得一个LazyInitializationException.

你将如何去做取决于EntityManagerand的类型PersistenceContext

  • 仅从 JPA 2.0 开始工作 - 调用em.detach(loadedEmployee)然后访问该boss属性。

对于 JPA 1:

  • 如果您在 Java EE 环境中,请将该方法标记为@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)以暂停事务。

  • 在具有用户事务的 SE 环境中,transaction.commit()在访问boss属性之前调用。

  • 如果使用PersistenceContext将超过事务的 EXTENDED,请调用em.clear().

EIDT:我想你没有得到异常的原因是这FetchType.LAZY只是对 JPA 提供者的一个提示,所以不能保证属性被延迟加载。与此相反,FetchType.EAGER保证渴望获取。我想,您的 JPA 提供者选择急切地加载。

我已经复制了这个例子,虽然有点不同,我可以重现地得到LazyInitializationExceptionon log 语句。该测试是在 JBoss 7.1.1 上运行的 Arquillian 测试,使用 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());
    }
}
于 2012-11-14T16:05:42.560 回答