2010-11-17 26 views
11

J'utilise spring + hibernate. Tous mes HibernateDAO utilisent directement sessionFactory.Spring, @Transactional et Hibernate Lazy Loading

J'ai une couche application -> couche de service -> DAO et toutes les collections sont chargées paresseusement. Donc, le problème est que parfois dans la couche application (qui contient l'interface graphique/swing) je charge une entité en utilisant une méthode de couche service (qui contient l'annotation @Transactional) et je veux utiliser une propriété lazly de cet objet, mais obviusly la session est déjà fermée.

Quelle est la meilleure façon de résoudre ce problème?

EDIT

J'essaie d'utiliser un MethodInterceptor, mon idée est d'écrire un AroundAdvice pour toutes mes entités et annotations utiliser, ainsi, par exemple:

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

Pensez-vous que peut-il travailler ? Le problème est, comment enregistrer cet intercepteur pour toutes mes entités sans le faire dans le fichier XML? Il existe un moyen de le faire avec des annotations?

Répondre

3

Hibernate a récemment introduit des profils d'extraction qui (en plus de l'optimisation des performances) est idéal pour résoudre des problèmes comme celui-ci. Il vous permet (au moment de l'exécution) de choisir entre différentes stratégies de chargement et d'initialisation.

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

Modifier (section ajoutée sur la façon de définir le profil à l'aide chercher un intercepteur):

Avant de commencer: Vérifiez que les profils d'extraction seront effectivement travailler pour vous. Je ne les ai pas utilisés moi-même et je me rends compte qu'ils sont actuellement limités à rejoindre des fetchs. Avant de perdre du temps à implémenter et à câbler l'intercepteur, essayez de définir le profil d'extraction manuellement et de voir qu'il résout réellement votre problème.

Il y a plusieurs façons de configurer les intercepteurs dans Spring (selon les préférences), mais la manière la plus directe serait d'implémenter un MethodInterceptor (voir http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop-api.html#aop-api-advice-around). Laissez-il un compositeur pour le fetch profil souhaité et compositeur pour l'usine de session 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); 
     } 
    } 
} 

Enfin, activez l'intercepteur dans la configuration Spring. Cela peut être fait de plusieurs façons et vous avez probablement déjà une configuration AOP que vous pouvez ajouter à. Voir http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop.html#aop-schema.

Si vous êtes nouveau sur AOP, je suggèrerais d'essayer d'abord le "vieux" ProxyFactory (http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html /aop-api.html#aop-api-proxying-intf) parce qu'il est plus facile de comprendre comment cela fonctionne. Voici quelques exemple XML pour vous lancer:

<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: ce n'est pas utile, quelle est la différence d'avoir deux méthodes dans DAO comme loadLazly et loadEager et d'utiliser un fetch-profile? Il n'y a aucune différence, je dois écrire la méthode différente si j'ai besoin de charger un certain objet qui est paresseux chargé. – blow

+1

Vous pouvez par exemple enrouler le service/dao avec un intercepteur qui définit le profil d'extraction "gui" lorsqu'il est appelé depuis l'interface graphique. Faites-moi savoir si vous avez besoin de plus de détails à ce sujet. – DaGGeRRz

+0

@DaGGeRRz: bien sûr, ce son intéressant. – blow

1
  1. Créer une méthode dans la couche de service qui retourne l'objet paresseux chargé pour cette entité
  2. changement chercher hâte :)
  3. Si possible prolonger votre transaction dans la couche d'application

(juste pendant que nous attendons quelqu'un qui sait de quoi il parle)

+0

merci, 1. est un peu ennuyeux, je dois écrire une méthode à chaque fois que je veux charger un objet paresseux, 2. est trop vaste dans perfomance. Peut-être 3. est la solution ... – blow

1

Vous avez malheureusement besoin de retravailler votre gestion de session. C'est un problème majeur lorsqu'il s'agit d'Hibernate et Spring, et c'est un problème gigantesque. Essentiellement, ce dont vous avez besoin, c'est que votre couche d'application crée une nouvelle session quand elle reçoit votre objet Hibernate, et gère cela et ferme la session correctement. Ce truc est délicat, et non trivial; l'une des meilleures façons de gérer cela est de gérer les sessions via une fabrique disponible à partir de votre couche d'application, mais vous devez toujours être en mesure de terminer la session correctement, vous devez donc être conscient des besoins du cycle de vie de vos données.

Cette substance est la plainte la plus fréquente concernant l'utilisation de Spring et Hibernate de cette manière; En fait, la seule façon de le gérer est de bien comprendre le cycle de vie de vos données.

+0

puis-je créer une session chaque fois que je frappe un objet paresseux en utilisant le printemps AOP? – blow

+0

@ blow: oui, vous pouvez, et c'est une très bonne idée; le problème consiste à savoir quand terminer la session (c'est-à-dire, lorsque la modification de votre objet est terminée). Dans certains cas, c'est facile. dans d'autres, étonnamment dur. –