Question Spring, @Transactional et Hibernate Lazy Loading


J'utilise le printemps + hibernation. Tous mes HibernateDAO utilisent directement sessionFactory.

J'ai couche d'application -> couche de service -> couche DAO et toutes les collections est chargé paresseux.

Donc, le problème est que dans la couche d'application (qui contient l'interface graphique / swing), je charge une entité en utilisant une méthode de couche de service (qui contient une annotation @Transactional) et je veux utiliser une propriété paresseuse de cet objet est déjà fermé

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

MODIFIER

J'essaie d'utiliser un MethodInterceptor, mon idée est d'écrire un AroundAdvice pour toutes mes entités et d'utiliser des annotations, 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 cela peut fonctionner? Le problème est, comment enregistrer cet intercepteur pour toutes mes entités sans le faire dans un fichier xml? Il y a un moyen de le faire avec une annotation?


11
2017-11-17 17:55


origine


Réponses:


Hibernate a récemment introduit des profils d'extraction qui (en plus de l'optimisation des performances) sont idéaux pour résoudre de tels problèmes. Il vous permet (lors 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 d'extraction à l'aide d'un intercepteur):

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

Il existe de nombreuses façons de configurer les intercepteurs au printemps (selon les préférences), mais la méthode la plus simple 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-lui un setter pour le profil d'extraction que vous voulez et définissez pour la fabrique de sessions 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 manières et vous avez probablement déjà une configuration AOP à laquelle vous pouvez l'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 vous suggère d’essayer d’abord l’ancienne méthode ProxyFactory (http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop-api). .html # aop-api-proxying-intf) car il est plus facile de comprendre son fonctionnement. Voici quelques exemples de XML pour vous aider à démarrer:

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

3
2017-11-17 18:18



  1. Créer une méthode dans la couche de service qui renvoie l'objet chargé par la suite pour cette entité
  2. Changer pour aller chercher impatient :)
  3. Si possible, étendez votre transaction dans la couche application

(juste en attendant que quelqu'un sache de quoi ils parlent)


1
2017-11-17 18:04



Vous devez malheureusement revoir votre gestion de session. Il s’agit d’un problème majeur lorsqu’il s’agit d’Hibernate et de 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 récupère votre objet Hibernate et la gère 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 la couche application, mais vous devez toujours pouvoir terminer la session correctement. Vous devez donc connaître les besoins en cycle de vie de vos données.

Ce problème est la plainte la plus courante concernant l'utilisation de Spring et Hibernate de cette manière; En fait, la seule manière de le gérer est de bien comprendre ce que sont les cycles de vie de vos données.


1
2017-11-17 18:12