Edit5:
The code used to explore the solution for this question is up on bitbucket: https://bitbucket.org/jcalleja/lazy-init-exception
I realise there are many other LazyInitializationException posts on SO, but I couldn't get the information I need from them.
The projects involved:
- memdrill-data
- data-use
memdrill-data is a Spring Roo managed project. There is no web component here. I have simply:
persistence setup --provider HIBERNATE --database HYPERSONIC_PERSISTENT
and added some entities. For instance:
@RooJavaBean
@RooToString
@RooEntity
public class Item {
@NotNull
@Column(unique = true)
@Size(min = 1, max = 200)
private String itemId;
@NotNull
@Lob
@OneToOne(cascade = javax.persistence.CascadeType.ALL, fetch = FetchType.LAZY)
private LobString content;
}
@RooJavaBean
@RooEntity
public class LobString {
@Lob
@Basic(fetch=FetchType.LAZY)
private String data;
public LobString() {
super();
}
public LobString(String data) {
this.data = data;
}
}
It shouldn't matter, but for the record, I am using Roo 1.1.4. Probably, the applicationContext.xml is not needed in this discussion but here it is anyway (untouched after generated by Roo):
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<context:property-placeholder location="classpath*:META-INF/spring/*.properties"/>
<context:spring-configured/>
<context:component-scan base-package="com.memdrill.data">
<context:exclude-filter expression=".*_Roo_.*" type="regex"/>
<context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
</context:component-scan>
<bean class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" id="dataSource">
<property name="driverClassName" value="${database.driverClassName}"/>
<property name="url" value="${database.url}"/>
<property name="username" value="${database.username}"/>
<property name="password" value="${database.password}"/>
<property name="testOnBorrow" value="true"/>
<property name="testOnReturn" value="true"/>
<property name="testWhileIdle" value="true"/>
<property name="timeBetweenEvictionRunsMillis" value="1800000"/>
<property name="numTestsPerEvictionRun" value="3"/>
<property name="minEvictableIdleTimeMillis" value="1800000"/>
</bean>
<bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/>
<bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory">
<property name="persistenceUnitName" value="persistenceUnit"/>
<property name="dataSource" ref="dataSource"/>
</bean>
data-use depends on memdrill-data and I'm just test driving memdrill-data through it for now. This is all that's going on in data-use:
public static void main(String[] args) {
ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext(
"classpath*:META-INF/spring/applicationContext*.xml");
Item item = new Item();
item.setItemId("abc123");
item.setContent(new LobString("this is the content of the item"));
item.persist();
System.out.println("persist - OK");
List<Item> items = Item.findAllItems();
Item i = items.get(0);
System.out.println("i.getId() = " + i.getId());
System.out.println("i.getItemId() = " + i.getItemId());
System.out.println("i.getContent() = " + i.getContent());
System.out.println("i.getContent().getId() = " + i.getContent().getId());
System.out.println("i.getContent().getData() = " + i.getContent().getData());
appContext.close();
}
findAllItems() is the default one generated by Roo in Item_Roo_Entity.aj:
public static List<Item> Item.findAllItems() {
return entityManager().createQuery("SELECT o FROM Item o", Item.class).getResultList();
}
This is what I get:
persist - OK
i.getId() = 1
i.getItemId() = abc123
2014-07-05 13:23:30,732 [main] ERROR org.hibernate.LazyInitializationException - could not initialize proxy - no Session
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:167)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:215)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:190)
at com.memdrill.data.util.LobString_$$_javassist_0.toString(LobString_$$_javassist_0.java)
at java.lang.String.valueOf(String.java:2826)
at java.lang.StringBuilder.append(StringBuilder.java:115)
at com.memdrill.prototypes.datause.Main.main(Main.java:34)
So when I try to access the lazily loaded data to fill in the LobString POJO, there's no session in progress and it blows up.
The closest I can get to (in my code i.e. not the libraries I'm depending on) putting in the LobString access code (item.getContent().getId() for e.g.) is something like pushing in the findAllItems() or creating a new method like this just to illustrate the point:
public static List<Item> findAllItemsWithRefs() {
List<Item> items = entityManager().createQuery("SELECT o FROM Item o", Item.class).getResultList();
for(Item item : items) {
System.out.println("item.getContent().getId() = " + item.getContent().getId());
}
return items;
}
That still gives me the same exception implying that there is no session going on exactly after .getResultList() returns the List.
I have tried, as other SO posts have suggested, to put @Transactional over the method but I still get the LazyInitializationException. But even if that did work... why do I need to start a transaction to read data from the db? Aren't transactions meant for preserving data integrity while writing?
It seems to me that one way around this would be to somehow specify a different query from "SELECT o FROM Item o"... one which also gets the LobString data... but this doesn't seem quite right even if it would fly in this particular case.
I was expecting Hibernate to start a new session when I access data it hasn't loaded yet... but obviously, it doesn't work as I expect.
Can someone familiar with Hibernate explain what's going on in this case and maybe suggest what options are available to deal with this?
Thanks
Edit1:
Btw, if I add the following log4j.propeties to the classpath:
log4j.rootLogger=error, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# Print the date in ISO 8601 format
log4j.appender.stdout.layout.ConversionPattern=%d [%t] %-5p %c - %m%n
log4j.logger.org.hibernate.SQL=DEBUG
log4j.logger.org.hibernate.type=TRACE
I get:
persist - OK
2014-07-05 16:17:35,707 [main] DEBUG org.hibernate.SQL - select item0_.id as id1_, item0_.content as content1_, item0_.item_id as item2_1_, item0_.version as version1_ from item item0_
2014-07-05 16:17:35,711 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [id1_]
2014-07-05 16:17:35,713 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [content1_]
2014-07-05 16:17:35,713 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [abc123] as column [item2_1_]
2014-07-05 16:17:35,713 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [0] as column [version1_]
i.getId() = 1
i.getItemId() = abc123
2014-07-05 16:17:35,717 [main] ERROR org.hibernate.LazyInitializationException - could not initialize proxy - no Session
So as the line "found 1 as column [content1_] shows, I at least have the foreign key of the row in the table housing the LobString data... but I can't access that id without getting a LazyInitializationException. Maybe I could fetch the LobString by id if I could get hold of it... but i.getContent() and i.getContent().getId() give the exception. The foreign key should match the primary key id of the LobString table i.e. i.getContent().getId().. but there seems to be no way of accessing the foreign key from i... which as the logs show, was actually fetched from the db.
In any case, even if I did get the foreign key... having to stay LobString.find(id) or something like that is not a nice solution. Hibernate is supposed to populate your object graph...
Edit2:
2 points to make in this edit. Execution passes through the following: org.springframework.orm.jpa.SharedEntityManagerCreator.DeferredQueryInvocationHandler.invoke(Object, Method, Object[])
:
// Invoke method on actual Query object.
try {
Object retVal = method.invoke(this.target, args);
return (retVal == this.target ? proxy : retVal);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
finally {
if (method.getName().equals("getResultList") || method.getName().equals("getSingleResult") ||
method.getName().equals("executeUpdate")) {
EntityManagerFactoryUtils.closeEntityManager(this.em);
}
}
First of all, the EntityManager is being closed if "getResultList" is used. I'll assume that the EntityManager is a JPA construct that, without going into details, maps to Hibernate's Session.
Second... if I drill down enough while debugging I can see what should be lazily loaded data "this is the content of the item"... so I actually have that data loaded in memory... 1) since it's lazy it shouldn't 2) even though it's there I cannot access it since I get the LazyInitializationException.
While debugging, I can see the data is loaded in the following two places:
1) My main method (see above: Item i = items.get(0) in the main method)
com.memdrill.prototypes.datause.Main.main(String[])
:
i -> content -> handler -> target -> data: "this is the content of the item"
2) In the method that closes the Session:
org.springframework.orm.jpa.SharedEntityManagerCreator.DeferredQueryInvocationHandler.invoke(Object, Method, Object[])
:
retVal -> elementData[0] -> content -> handler -> target -> data: "this is the content of the item"
So much for lazily loaded data... what am I doing so terribly wrong?
I seem to have got the specification of LobString to be lazily loaded too. But if that's the case, and I do have the content in memory... why can't I access it?
Edit3:
It looks like the behaviour in Edit2 (to have the lazily loaded data in memory) only happens while debugging if I stay long enough in debugging mode.....
So basically, if I run in debug and just hit "play" "play" "play" all the way through my breakpoints, I get the LazyInitializationException... but if I stay long enough in debug mode, no LazyInitializationException, I get the data and print to standard out.
Any ideas?
Edit4:
Thanks to SO question I am now able to eagerly fetch all of LobString using:
EntityManager em = Item.entityManager();
List<Item> items = em.createQuery("SELECT o FROM Item o JOIN FETCH o.content", Item.class).getResultList();
Output:
2014-07-06 21:44:56,862 [main] INFO org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean - Building JPA container EntityManagerFactory for persistence unit 'persistenceUnit'
in LobString default constructor
in LobString (String data) constructor
2014-07-06 21:44:57,891 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Creating new transaction with name [com.memdrill.data.entity.Item.persist]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
2014-07-06 21:44:57,944 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Opened new EntityManager [org.hibernate.ejb.EntityManagerImpl@779d9c0d] for JPA transaction
2014-07-06 21:44:57,948 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Not exposing JPA transaction [org.hibernate.ejb.EntityManagerImpl@779d9c0d] as JDBC transaction because JpaDialect [org.springframework.orm.jpa.DefaultJpaDialect@d7e60a1] does not support JDBC Connection retrieval
2014-07-06 21:44:57,999 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Initiating transaction commit
2014-07-06 21:44:58,000 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Committing JPA transaction on EntityManager [org.hibernate.ejb.EntityManagerImpl@779d9c0d]
2014-07-06 21:44:58,002 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [org.hibernate.ejb.EntityManagerImpl@779d9c0d] after transaction
2014-07-06 21:44:58,002 [main] DEBUG org.springframework.orm.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager
persist - OK - in Main of memdrill-data not data-use
2014-07-06 21:44:58,008 [main] DEBUG org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler - Creating new EntityManager for shared EntityManager invocation
in LobString default constructor
2014-07-06 21:44:58,133 [main] DEBUG org.springframework.orm.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager
i.getId() = 1
i.getItemId() = abc123
i.getContent() = com.memdrill.data.util.LobString@6fe30af
i.getContent().getId() = 1
i.getContent().getData() = this is the content of the item
2014-07-06 21:44:58,134 [main] INFO org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean - Closing JPA EntityManagerFactory for persistence unit 'persistenceUnit'
This is almost what I want. I just want the ids of the LobString. The idea is that this is going to be a REST call and I just want to return the Item "basic" data and ids of the LobString which can be used for separate requests.
Any idea what kind of JPQL query would achieve this? (any comments in general to help with understanding what's going on under the hood would be appreciated) - thanks