12

i'm using spring + hibernate. All my HibernateDAO use directly sessionFactory.

I have application layer -> service layer -> DAO layer and all collections is lazly loaded.

So, the problem is that sometime in the application layer(that contains GUI/swing) i load an entity using a service layer method(that contains @Transactional annotation) and i want to use a lazly property of this object, but obviusly the session is already closed.

What is the best way to resolve this trouble?

EDIT

I try to use a MethodInterceptor, my idea is to write an AroundAdvice for all my Entities and use annotation, so for example:

// Custom annotation, say that session is required for this method
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SessionRequired {


// An AroundAdvice to intercept method calls
public class SessionInterceptor implements MethodInterceptor {

    public Object invoke(MethodInvocation mi) throws Throwable {
        bool sessionRequired=mi.getMethod().isAnnotationPresent(SessionRequired.class);
        // Begin and commit session only if @SessionRequired
        if(sessionRequired){
            // begin transaction here
        }
        Object ret=mi.proceed();
        if(sessionRequired){
            // commit transaction here
        }
        return ret;
    }
}

// An example of entity
@Entity
public class Customer implements Serializable {

    @Id
    Long id;

    @OneToMany
    List<Order> orders;  // this is a lazy collection

    @SessionRequired
    public List<Order> getOrders(){
        return orders;
    }
}

// And finally in application layer...
public void foo(){
    // Load customer by id, getCustomer is annotated with @Transactional
    // this is a lazy load
    Customer customer=customerService.getCustomer(1); 

    // Get orders, my interceptor open and close the session for me... i hope...
    List<Order> orders=customer.getOrders();

    // Finally use the orders
}

Do you think can this work? The problem is, how to register this interceptor for all my entities without do it in xml file? There is a way to do it with annotation?

blow
  • 12,811
  • 24
  • 75
  • 112

3 Answers3

3

Hibernate recently introduced fetch profiles which (in addition to performance tuning) is ideal for solving issues like this. It allows you to (at runtime) choose between different loading and initialization strategies.

http://docs.jboss.org/hibernate/core/3.5/reference/en/html/performance.html#performance-fetching-profiles

Edit (added section on how to set the fetch profile using an interceptor):

Before you get started: Check that fetch profiles actually will work for you. I haven't used them myself and see that they are currently limited to join fetches. Before you waste time on implementing and wiring up the interceptor, try setting the fetch profile manually and see that it actually solves your problem.

There are many ways to setup interceptors in Spring (according to preference), but the most straight-forward way would be to implement a MethodInterceptor (see http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop-api.html#aop-api-advice-around). Let it have a setter for the fetch profile you want and setter for the Hibernate Session factory:

public class FetchProfileInterceptor implements MethodInterceptor {

    private SessionFactory sessionFactory;
    private String fetchProfile;

    ... setters ...    

    public Object invoke(MethodInvocation invocation) throws Throwable {
        Session s = sessionFactory.openSession(); // The transaction interceptor has already opened the session, so this returns it.
        s.enableFetchProfile(fetchProfile);
        try {
            return invocation.proceed();
        } finally {
            s.disableFetchProfile(fetchProfile);
        }
    }
}

Lastly, enable the interceptor in the Spring config. This can be done in several ways and you probably already have a AOP setup that you can add it to. See http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop.html#aop-schema.

If you're new to AOP, I'd suggest trying the "old" ProxyFactory way first (http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop-api.html#aop-api-proxying-intf) because it's easier to understand how it works. Here's some sample XML to get you started:

<bean id="fetchProfileInterceptor" class="x.y.zFetchProfileInterceptor">
  <property name="sessionFactory" ref="sessionFactory"/>
  <property name="fetchProfile" ref="gui-profile"/>
</bean>

<bean id="businessService" class="x.y.x.BusinessServiceImpl">
  <property name="dao" .../>
  ...
</bean>

<bean id="serviceForSwinGUI" 
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces" value="x.y.z.BusinessServiceInterface/>

    <property name="target" ref="businessService"/>
    <property name="interceptorNames">
        <list>
            <value>existingTransactionInterceptorBeanName</value>
            <value>fetchProfileInterceptor</value>
        </list>
    </property>
</bean>
DaGGeRRz
  • 1,611
  • 1
  • 12
  • 13
  • @DaGGeRRz: this is not useful, what is the difference to have two methods in DAO like loadLazly and loadEager and use a fetch-profile? There is no difference, i have to write differente method if i need to load a certain object that is lazy loaded. – blow Nov 17 '10 at 18:26
  • 1
    You can for instance wrap the service / dao with an interceptor which sets fetch profile "gui" when called from the gui. Let me know if you need more details on that. – DaGGeRRz Nov 17 '10 at 18:31
  • Thank you DaGGeRRz, i have a simple question: at this moment i use auto-scan to scan my components(DAOs,srvices repository etc...), can i register an interceptor for all my entities automatically? I will edit my first post for this question. – blow Nov 17 '10 at 20:43
  • 1
    See http://stackoverflow.com/questions/2611986/intercepting-method-with-spring-aop-using-only-annotations for AOP. It does not use the MethodInterceptor, but that's not necessary if you use AspectJ and pure annotations. A comment on the interceptor you're implementing in your edit: The standard @Transactional annotation that I'm sure you're using already gives you a session and handles transactions (with features like propagation), so stick to _just_ enabling and disabling the fetch profile. – DaGGeRRz Nov 17 '10 at 21:15
  • No becouse i have no session for enabling and disabling the fetch profile, the session is already close, this is the problem! – blow Nov 17 '10 at 21:27
  • If you're using Spring's session factory beans annotate your service with @Transactional (TX) and add your own interceptor (FP), then call chain will be TX(open session, start transaction)->FP(get open session, set profile)->Business service->FP(disable profile)->TX(commit transaction, close session). Set a breakpoint in your business method to check that the transaction interceptor is there. – DaGGeRRz Nov 17 '10 at 21:45
1
  1. Create a method in the service layer that returns the lazy-loaded object for that entity
  2. Change to fetch eager :)
  3. If possible extend your transaction into the application layer

(just while we wait for someone who knows what they are talking about)

willcodejavaforfood
  • 43,223
  • 17
  • 81
  • 111
  • thank you, 1. is a bit boring, i have to write a method each time i want load a lazy object, 2. is too expansive in perfomance. Maybe 3. is the solution... – blow Nov 17 '10 at 18:20
1

You need to rework your session management, unfortunately. This is a major problem when dealing with Hibernate and Spring, and it's a gigantic hassle.

Essentially, what you need is for your application layer to create a new session when it gets your Hibernate object, and to manage that and close the session properly. This stuff is tricky, and non-trivial; one of the best ways to manage this is to mediate the sessions through a factory available from your application layer, but you still need to be able to end the session properly, so you have to be aware of the lifecycle needs of your data.

This stuff is the most common complaint about using Spring and Hibernate in this way; really, the only way to manage it is to get a good handle on exactly what your data lifecycles are.

Paul Sonier
  • 38,903
  • 3
  • 77
  • 117
  • can i create a session every time i hit a lazy object using spring AOP? – blow Nov 17 '10 at 18:21
  • @blow: yes, you can, and that's a very good idea; the problem becomes knowing when to terminate the session (i.e., when your object modification is complete). In some cases, this is easy; in others, surprisingly hard. – Paul Sonier Nov 17 '10 at 19:22