Help needed with Spring/Hibernate Lazy-loading
I know that this has been discussed lot of times. I just can't understand how this work or where my mistake is.
I think giving you a reduced example is the best way to show you what I'm trying to do and what assumptions I'm taking...I have a Product class with a name. The name is a String property that is lazy.
My DAO:
public abstract class HibernateProductDAO extends HibernateDaoSupport implements ProductDAO
{
public List getAll()
{
return this.getHibernateTemplate().find("from " + this.getDomainClass().getSimpleName());
}
}
My Service Interface:
public interface ProductService {
//This methods are Transactional, but same exception error is thrown if there weren't
@Transactional
public Product getProduct();
@Transactional
public String getName(Product tp);
}
My Service Implementation:
public class ProductServiceImpl implements ProductService {
private ProductDAO productDAO;
public Product getProduct() {
List ps = this.productDAO.getAll();
return (Product) ps.get(0);
}
public String getName(Product p){
return p.getName();
}
}
My Main class:
public class Main {
private ProductService productService;
public static void main(String[] args) {
Main main= new Main();
main.productService= (ProductService)(new ClassPathXmlApplicationContext("applicationContext.xml")).getBean("productProxy");
//load the product without the name
Product p = main.productService.getProduct();
//load the lazy name
System.out.println(main.productService.getName(p)); //EXCEPTION IS THROWN IN THIS LINE
}
public void setProductService(ProductService productService) {
this.productService= productService;
}
public ProductService getProductService() {
return productService;
}
My applicationContext.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:lang="http://www.springframework.org/schema/lang"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:sca="http://xmlns.oracle.com/weblogic/weblogic-sca">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName"><value>oracle.jdbc.driver.OracleDriver</value></property>
<property name="url"><value>jdbc:oracle:thin:@${hostname}:${port}:${schema}</value></property>
<property name="username"><value>${username}</value></property>
<property name="password"><value>${password}</value></property>
</bean>
<!-- Hibernate SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource"><ref local="dataSource"/></property>
<property name="configLocation"><value>hibernate.cfg.xml</value></property>
<property name="configurationClass"><value>org.hibernate.cfg.AnnotationConfiguration</value></property>
</bean>
<!-- Transaction manager for a single Hibernate SessionFactory (alternative to JTA) -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
<property name="sessionFactory"><ref bean="sessionFactory"/></property>
<property name="allowCreate" value="true"/>
</bean>
<bean id="productDAO" class="product.model.data.ProductDAO" >
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<bean id="hibernateInterceptor"
class="org.springframework.orm.hibernate3.HibernateInterceptor">
<property name="sessionFactory">
<ref bean="sessionFactory"/>
</property>
</bean>
<bean id="productService"
class="product.services.ProductServiceImpl">
<property name="productDAO">
开发者_开发问答 <ref bean="ProductDAO"/>
</property>
</bean>
<bean id="productProxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref bean="productService"/>
</property>
<property name="proxyInterfaces">
<value>product.services.ProductService</value>
</property>
<property name="interceptorNames">
<list>
<value>hibernateInterceptor</value>
</list>
</property>
</bean>
</beans>
The exception fragment:
11:59:57,775 [main] DEBUG org.springframework.orm.hibernate3.SessionFactoryUtils - Opening Hibernate Session
11:59:57,775 [main] DEBUG org.hibernate.impl.SessionImpl - opened session at timestamp: 12749723977
11:59:57,777 [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:108)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:150)
at org.hibernate.proxy.pojo.cglib.CGLIBLazyInitializer.invoke(CGLIBLazyInitializer.java:150)
Am I right if I assume that the HibernateInterceptor keeps the Hibernate Session Open among the different calls? If I'm so, Why is the session closed after loading the product object?
I read somewhere that I also can use OpenSessionInViewInterceptor, but I can't make it work. How would you add that interceptor to this little example?
Is there any code mistake or missunderstanding on how this work?
Do you know any simple example code I can download to check how this work?
Thanks in advance, Neuquino
the problem is that HibernateInterceptor closes the session after returning from ProductService getProduct()
From the API DOCs:
This interceptor binds a new Hibernate Session to the thread before a method call, closing and removing it afterwards in case of any method outcome. If there already is a pre-bound Session (e.g. from HibernateTransactionManager, or from a surrounding Hibernate-intercepted method), the interceptor simply participates in it.
and opens a new one for the follwing call to ProductService.getProductName()
. The newly created session has no knowledge of the Product Entity which you fetched from the DB in the previous session.
you have various possibilities to resolve this:
1.) add a method which eagerly loads the names, and use it in the specific contexts, this is obviuos ;)
2.) you can reattach the entity to the active session using Session.update(myProductEntity)
before calling Product.getName()
in ProductService.getProductName()
3.) you can wrap it all in a transaction which the wrapped method calls to ProductService.getProduct()
and ProductService.getProductName()
participate in. See Transaction Propagation
4.) in a web application you can use an OpenSessionInViewFilter to keep the session open as long as the view gets created in your controllers
5.) i think you can also use AOP directly. you can have an aspect keep the session open on an arbitrary Joinpoint but i have no real experience there and can not be more specific ;)
hope that helped...
精彩评论