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.
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.
em.detach(loadedEmployee) and then access the boss 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 the boss property.
If using an EXTENDED PersistenceContext that will outlive the transaction, call em.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());
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With