【问题标题】:JPA and Hibernate proxy behaviorJPA 和 Hibernate 代理行为
【发布时间】:2012-11-03 03:32:42
【问题描述】:

我试图观察下面的 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

令我惊讶的是,上面加载的外部实体仍然是普通的,但我希望它是由lazy loading 产生的Hibernate proxy。我可能在这里错过了一些东西,那么如何让它正确呢?非常感谢一个简单而具体的例子!

@EDIT

根据@kostja的回答,我修改了代码并在下面的SE模式下调试,既不能生成LazyInitializationException,也不能代理boss property。还有什么提示吗?

@EDIT 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

另外,可以参考 Hibernate 文档中的20.1.4. Initializing collections and proxies

【问题讨论】:

    标签: hibernate jpa jpa-2.0 hibernate-4.x


    【解决方案1】:

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

    所以你应该在事务之外访问 boss 属性。如果还没有被抓取,你会得到一个LazyInitializationException

    您将如何处理取决于EntityManagerPersistenceContext 的类型。

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

    对于 JPA 1:

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

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

    • 如果使用的 EXTENDED PersistenceContext 会比事务更有效,请调用 em.clear()

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

    我已经复制了这个例子,虽然有点不同,我在日志语句中重复地得到了LazyInitializationException。该测试是在 JBoss 7.1.1 上运行的 Arquillian 测试,使用 JPA 2.0 over 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());
        }
    }
    

    【讨论】:

    • 谢谢。你能根据你的提示检查我上面的修改吗?
    • @sof - 已经做了一些测试 - 也许你也可以重现它
    • 非常感谢。我在EE模式下成功完成了上面的另一个测试。
    • @sof 不客气。很高兴你能确认。我已经开始怀疑我的推理了;)
    猜你喜欢
    • 2015-06-05
    • 2012-07-18
    • 1970-01-01
    • 2013-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-06
    • 1970-01-01
    相关资源
    最近更新 更多