问题
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 ...
}
// Persist entities:
// 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);
// Output:
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
// Load the outer entity:
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());
// Output:
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
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
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
Finally, i'd confirm that the answer from @kostja
is undoubtly great.
I tested in EE mode, so the proxied boss property
was observed below,
// LazyInitializationException
thrown:
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;
}
// Green light after put Spring Tx
in place:
@Transactional
public Employee retrieve(int id) ...
// Output:
true
Employee_$$_javassist_0
Also, one can refer to 20.1.4. Initializing collections and proxies from Hibernate docs.
回答1:
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.
So you should access the boss property outside the transaction. If it has not been fetched, you will get a LazyInitializationException
.
How you would go about it depends on the kind of the EntityManager
and PersistenceContext
.
- works only since JPA 2.0 - call
em.detach(loadedEmployee)
and then access theboss
property.
For JPA 1:
If you are in a Java EE environment, mark the method with
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
to suspend the transaction.In an SE environment with user transactions, call
transaction.commit()
before accessing theboss
property.If using an EXTENDED
PersistenceContext
that will outlive the transaction, callem.clear()
.
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.
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());
}
}
来源:https://stackoverflow.com/questions/13382187/jpa-and-hibernate-proxy-behavior