问题描述
我试图观察下面的JPA2/Hibernate4代理行为,
i tried to observe JPA2 / Hibernate4 proxy behavior below,
//具有延迟加载的循环实体:
// Circular entity with lazy loading:
@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
产生的结果.我可能在这里错过了一些东西,那么如何正确处理呢?一个简单而具体的示例将不胜感激!
To my surprise the loaded outer entity above is still the plain one, but i'd expected it to be Hibernate proxy
resulting from lazy loading
. I could have missed something here, so how to get it right? A simple yet concrete example is much appreciated!
@EDIT
根据来自@kostja
的回答,我修改了代码并在下面的SE模式下对其进行了调试,因此既无法生成LazyInitializationException
,也无法对其进行代理.还有其他提示吗?
According to the answer from @kostja
i adapted the code and debugged it in SE mode below, neither could LazyInitializationException
be produced, nor was boss property
proxied. Any further hints?
@EDIT 2
最后,我将确认@kostja
的答案无疑是很棒的.
Finally, i'd confirm that the answer from @kostja
is undoubtly great.
我在EE模式下进行了测试,因此在下面观察到了proxied boss property
,
I tested in EE mode, so the proxied boss property
was observed below,
//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文档的集合和代理.
推荐答案
这是预期的JPA行为.您的查询中的实体没有必要被代理-这是查询的常规结果.但是,此实体的boss
属性应该是代理.它不会告诉您是否要询问-在对托管实体的延迟加载的属性执行任何操作时,都会触发提取.
This is the expected JPA behaviour. There is no reason for the entity from your query to be proxied - it is a regular result of a query. The boss
property of this entity however should be a proxy. It will not tell if asked though - when you execute any operations on the lazily loaded property of a managed entity, it will trigger the fetch.
因此,您应该在事务外部访问boss属性.如果尚未获取,则会显示LazyInitializationException
.
So you should access the boss property outside the transaction. If it has not been fetched, you will get a LazyInitializationException
.
如何处理取决于EntityManager
和PersistenceContext
的类型.
How you would go about it depends on the kind of the EntityManager
and PersistenceContext
.
- 仅从JPA 2.0开始有效-调用
em.detach(loadedEmployee)
,然后访问boss
属性.
- works only since JPA 2.0 - call
em.detach(loadedEmployee)
and then access theboss
property.
对于JPA 1:
-
如果您在Java EE环境中,请用
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
标记该方法以暂停事务.
If you are in a Java EE environment, mark the method with
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
to suspend the transaction.
在具有用户事务的SE环境中,在访问boss
属性之前调用transaction.commit()
.
In an SE environment with user transactions, call transaction.commit()
before accessing the boss
property.
如果使用的扩展PersistenceContext
会使交易失效,请致电em.clear()
.
If using an EXTENDED PersistenceContext
that will outlive the transaction, call em.clear()
.
EIDT:我想您未得到例外的原因是FetchType.LAZY
只是JPA提供程序的提示,因此不能保证延迟加载该属性.与此相反,FetchType.EAGER
保证了热切的获取.我想,您的JPA提供商选择急于加载.
EIDT: I suppose that the reason you are not getting the exception is that FetchType.LAZY
is just a hint for the JPA provider, so there is no guarantee for the property to be loaded lazily. Contrary to that, FetchType.EAGER
guarantees eager fetching. I suppose, your JPA provider chooses to load eagerly.
我重现了该示例,尽管有所不同,并且可复制地在log语句中获取了LazyInitializationException
.该测试是一项Arquillian测试,该测试在基于Hibernate 4.0.1的JBoss 7.1.1和JPA 2.0上运行:
I have reproduced the example, though a bit differently and I am reproducibly getting the LazyInitializationException
on the log statement. The test is an Arquillian test running on JBoss 7.1.1 with 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());
}
}
这篇关于JPA和Hibernate代理行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!