2010-11-17 33 views
11

Estoy usando spring + hibernate. Todos mis HibernateDAO usan directamente sessionFactory.Spring, @Transactional e Hibernate Lazy Loading

Tengo la capa de aplicación -> capa de servicio -> la capa DAO y todas las colecciones están cargadas de forma diferida.

Entonces, el problema es que en algún momento en la capa de aplicación (que contiene GUI/swing) cargo una entidad usando un método de capa de servicio (que contiene anotación @Transactional) y quiero usar una propiedad poco común de este objeto, pero evidentemente la sesión ya está cerrada.

¿Cuál es la mejor manera de resolver este problema?

EDITAR

trato de usar un MethodInterceptor, mi idea es escribir un AroundAdvice para todos mis Entidades y el uso de anotación, así por ejemplo:

// 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 
} 

¿Cree que puede funcionar esto ? El problema es, ¿cómo registrar este interceptor para todas mis entidades sin hacerlo en el archivo xml? ¿Hay una manera de hacerlo con la anotación?

Respuesta

3

Hibernate presentó recientemente perfiles de búsqueda que (además del ajuste del rendimiento) es ideal para resolver problemas como este. Le permite (en tiempo de ejecución) elegir entre diferentes estrategias de carga e inicialización.

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

Editar (sección añadida sobre cómo configurar la zona de alcance de perfiles mediante un interceptor):

Antes de comenzar: Compruebe que recuperan perfiles realmente va a trabajar para usted. No los he usado yo mismo y veo que actualmente están limitados a unirse a fetches. Antes de perder tiempo en la implementación y el cableado del interceptor, intente configurar manualmente el perfil de búsqueda y asegúrese de que resuelva su problema.

Hay muchas maneras de configurar los interceptores en Spring (de acuerdo con las preferencias), pero la forma más directa sería implementar un MethodInterceptor (ver http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop-api.html#aop-api-advice-around). Hacer que tenga un regulador para la traes perfil que desee y setter para la fábrica de sesión de Hibernate:

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); 
     } 
    } 
} 

Por último, habilitar el interceptor en la configuración de primavera. Esto se puede hacer de varias maneras y es probable que ya tenga una configuración de AOP a la que pueda agregarla. Ver http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop.html#aop-schema.

Si eres nuevo en AOP, te sugiero probar primero la "antigua" forma de ProxyFactory (http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html /aop-api.html#aop-api-proxying-intf) porque es más fácil entender cómo funciona. Aquí hay algo de XML de ejemplo para empezar:

<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> 
+0

@DaGGeRRz: esto no es útil, ¿cuál es la diferencia de tener dos métodos en DAO como loadLazly y loadEager y usar un fetch-profile? No hay diferencia, tengo que escribir un método diferente si necesito cargar un determinado objeto que está cargado de forma diferida. – blow

+1

Puede, por ejemplo, ajustar el servicio/dao con un interceptor que establece el perfil de búsqueda "gui" cuando se llama desde la interfaz gráfica de usuario. Avíseme si necesita más detalles sobre eso. – DaGGeRRz

+0

@DaGGeRRz: seguro gracias, este sonido es interesante. – blow

1
  1. Crear un método en la capa de servicio que devuelve el objeto cargado ligeramente para esa entidad
  2. Cambio a buscar ganas :)
  3. Si es posible extender su transacción en la capa de aplicación

(justo mientras esperamos a alguien que sepa de lo que están hablando)

+0

gracias, 1. es un poco aburrido, tengo que escribir un método cada vez que quiera cargar un objeto perezoso, 2. es demasiado expansivo en perfomance de. Tal vez 3. es la solución ... – blow

1

Por desgracia, necesita volver a trabajar en la gestión de la sesión. Este es un problema importante cuando se trata de Hibernate y Spring, y es una molestia gigantesca.

Esencialmente, lo que necesita es que su capa de aplicación cree una nueva sesión cuando obtiene su objeto Hibernate, y que administre y cierre la sesión correctamente. Esto es complicado y no trivial; Una de las mejores maneras de gestionar esto es mediar en las sesiones a través de una fábrica disponible desde la capa de aplicación, pero aún así debe poder finalizar la sesión correctamente, de modo que debe tener en cuenta las necesidades de ciclo de vida de sus datos.

Este es el reclamo más común sobre el uso de Spring e Hibernate de esta manera; Realmente, la única forma de administrarlo es obtener una buena idea de cuáles son exactamente sus ciclos de vida de datos.

+0

¿puedo crear una sesión cada vez que golpeo un objeto perezoso usando la primavera AOP? – blow

+0

@blow: sí, puedes, y esa es una muy buena idea; el problema es saber cuándo finalizar la sesión (es decir, cuando se completa la modificación del objeto). En algunos casos, esto es fácil; en otros, sorprendentemente difícil. –