2016-09-26 3 views
0

J'ai une entité JPA avec Lazy loaded collection sur elle. Je n'ai pas besoin de la collection à chaque fois.OpenSessionInView vs. Transactional? (Spring/Hibernate/JPA)

@Entity(name = "Foo") 
@Access(AccessType.FIELD) 
@Table(name = "TEST", schema = "TEST") 
public class Foo implements Serializable { 
    private static final long serialVersionUID = 1L; 

    @OneToMany(mappedBy="foo", targetEntity=Bar.class, fetch=FetchType.LAZY, cascade=CascadeType.ALL) 
    private List<Bar> bars; 
} 

@Entity(name = "Bar") 
@Access(AccessType.FIELD) 
@Table(name = "TEST", schema = "TEST") 
public class Bar implements Serializable { 
    private static final long serialVersionUID = 1L; 

    @ManyToOne(targetEntity = Foo.class) 
    @JoinColumn(name = "FOO_ID", referencedColumnName = "ID") 
    private Foo foo; 
} 

J'ai quelques méthodes sur une classe de service qui effectuent beaucoup d'interactions de base de données et à la fin sauver une entité Foo à la base de données. J'ai besoin que cela arrive pour environ 100 articles dans une collection.

@Service 
public class FooService { 

    @Autowired 
    private FooRepository fooRepository; 

    public void processAllFoos() { 
     fooRepository.findAll().forEach(foo -> { 
      processFoo(foo); 
     }); 
    } 

    private void processFoo(Foo foo) { 
     foo.getBars().forEach(bar -> { 
      // Do a lot of time consuming stuff here that involves 
      // entities of other types and modify each bar object 
     }); 
     fooRepository.save(foo); 
    } 
} 

processAllFoos est appelé à partir d'un @RESTController chaque fois qu'il reçoit une demande.

Cependant, je ne veux pas processAllFoos pour être enveloppé dans une seule transaction de base de données, car cela verrouille toute la table Foo jusqu'à ce que la logique métier soit exécutée pour tous les Foos.

Si je fais la méthode processFoo@Transactional je reçois le LazyInitializationException qui se plaint que la session Hibernate est inexistante. Pour que cela fonctionne, j'ai besoin de faire toutes les méthodes dans la pile d'appel @Transactional afin que les méthodes imbriquées puissent se joindre à la transaction de la méthode appelante. Mais cela verrouille toute la table Foo comme mentionné ci-dessus.

Ajout d'un OpenSessionInViewFilter pour le dispatcher servlet résout mon problème mais j'ai lu qu'il ya des problèmes avec la performance et l'entité détachant/refixer (ce que je fais dans d'autres parties de l'application) avec cette approche.

Y at-il un moyen de faire ce que je veux sans utiliser l'approche OpenSessionInView? Quelles sont les autres vulnérabilités que j'ajoute en utilisant cette approche?

Spring/Hibernate 4.x


Sur la base de la réponse ci-dessous, j'ai pu faire ce qui suit:

@Service 
public class FooService { 

    @Autowired 
    private FooRepository fooRepository; 

    @Autowired 
    private TransactionTemplate transactionTemplate; 

    public void processAllFoos() { 
     fooRepository.findAll().forEach(foo -> { 
      transactionTemplate.execute(new TransactionCallback<Object>() { 
       public Object doInTransaction(TransactionStatus status) { 
        try { 
         processFoo(foo); 
         status.flush(); 
        } catch(Exception e) { 
         status.setRollbackOnly(); 
        } 
        return null; 
       } 
      }); 
     }); 
    } 

    private void processBar(Foo foo) { 
     foo.getBars().foreEach(bar -> { 
      // Do a lot of time consuming stuff here that involves 
      // entities of other types and modify each bar object 
     }); 
     fooRepository.save(foo); 
    } 
} 

Répondre

2

OpenSessionInViewFilter couramment utilisé pour résoudre le problème de LazyInitialization dans la couche de vue (composants de l'interface utilisateur ou des modèles de page) , parce que View layer ne peut pas et ne doit pas gérer les transactions directement. Dans votre cas, une autre façon d'obtenir tous les objets Bar peut être appliquée.

Première Vous obtenez tous les ID d'objet Foo à la place pour obtenir tous les objets.

Deuxième Utilisez la collection Foo ids pour parcourir les objets Bar associés.

Troisième Si vous ne voulez pas une transaction BIG, vous pouvez utiliser le modèle Spring Transaction pour gérer les transactions explicitement.

Votre exemple de code peut ressembler à ceci:

@Service 
public class FooService { 

    @Autowired 
    private FooRepository fooRepository; 

    @Autowired 
    private BarRepository barRepository; 

    @Autowired 
    private TransactionTemplate transactionTemplate; 

    public void processAllFoos() { 
     final List <Long> fooIdList = transactionTemplate.execute(new TransactionCallback() { 
      public Object doInTransaction(TransactionStatus status) { 

       return fooRepository.findIdList(); 
      } 
     }); 

     transactionTemplate.execute(new TransactionCallback() { 
      public Object doInTransaction(TransactionStatus status) { 
       barRepository.findByFooIdList(fooIdList).forEach(bar - > { 
        processBar(bar); 
       }); 
       return null; 
      } 
     }); 

    } 

    private void processBar(Bar bar) { 
     // Do a lot of time consuming stuff here that involves 
     // entities of other types and modify each bar object 
     barRepository.save(bar); 
    } 
} 

exemple ci-dessous montre comment résoudre votre tâche sans certains frais généraux de performance.Mais vous devez comprendre que si Foo et Bar tables liées avec la contrainte de clé étrangère, l'enregistrement lié dans la table Foo peut être bloqué par RDBMS chaque fois que vous mettez à jour la ligne dans la table Bar.

+0

Merci @SergeyBespalov Cela a rendu l'ensemble du processus incroyablement rapide. Y a-t-il des réserves à cette approche? Choses que vous ne devriez pas faire lors de la gestion des transactions explicitement? – battle2048

+0

Je peux vous suggérer deux règles simples basées sur cet exemple: 1. N'utilisez pas la gestion des transactions déclarative si cela ne vous convient pas; 2. N'utilisez pas complètement les objets si vous n'avez besoin que d'une seule propriété; –