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