web-dev-qa-db-fra.com

Spring @Transactional lecture seule

J'expérimente l'utilisation du modèle de commande pour permettre à ma couche Web de travailler avec des entités Hibernate dans le contexte d'une seule transaction (évitant ainsi les exceptions de chargement paresseux). Cependant, je suis maintenant confus quant à la façon dont je dois gérer les transactions.

Mes commandes appellent des méthodes de couche de service qui sont annotées avec des annotations @Transactional. Certaines de ces méthodes de couche de service sont en lecture seule - par exemple @Transactional(readOnly=true) - et certains sont en lecture/écriture.

Ma couche de service expose un gestionnaire de commandes qui exécute les commandes qui lui sont transmises au nom de la couche Web.

@Transactional
public Command handle( Command cmd ) throws CommandException

Je suppose que j'ai raison de rendre transactionnelle la méthode handle() du gestionnaire de commandes. C'est là qu'intervient la confusion. Si l'implémentation d'une commande fait appel à plusieurs méthodes de couche de service, le gestionnaire de commandes n'a aucun moyen de savoir si les opérations appelées dans la commande seront en lecture seule, en lecture/écriture ou une combinaison des deux.

Je ne comprends pas comment fonctionne la propagation dans cet exemple. Si je devais faire la méthode handle()readOnly=true, que se passe-t-il si la commande appelle ensuite une méthode de couche de service qui est annotée avec @Transactional(realOnly=false)?

J'apprécierais une meilleure compréhension de cela et j'accueillerais vos commentaires ...

Andrew

55
DrewEaster

Tout d'abord, puisque Spring ne fait pas de persistance lui-même, il ne peut pas spécifier ce que readOnly doit exactement signifier. Cet attribut n'est qu'un indice pour le fournisseur, le comportement dépend, dans ce cas, d'Hibernate.

Si vous spécifiez readOnly comme true, le mode de vidage sera défini comme FlushMode.NEVER dans la session Hibernate en cours empêchant la session de valider la transaction.

De plus, setReadOnly (true) sera appelé sur la connexion JDBC, qui est également un indice pour la base de données sous-jacente. Si votre base de données le prend en charge (très probablement), cela a essentiellement le même effet que FlushMode.NEVER, mais c'est plus fort car vous ne pouvez même pas vider manuellement.

Voyons maintenant comment fonctionne la propagation des transactions.

Si vous ne définissez pas explicitement readOnly sur true, vous aurez des transactions en lecture/écriture. En fonction des attributs de transaction (comme REQUIRES_NEW), votre transaction est parfois suspendue à un moment donné, une nouvelle est démarrée et finalement validée, puis la première transaction est reprise.

OK, nous y sommes presque. Voyons ce qui apporte readOnly dans ce scénario.

Si une méthode dans une transaction lecture/écriture appelle une méthode qui nécessite une transaction lecture seule, la première doit être suspendue, car sinon une vidange/validation se produirait à la fin de la deuxième méthode.

Inversement, si vous appelez une méthode à partir d'une transaction readOnly qui nécessite lecture/écriture, encore une fois, la première sera suspendue, car elle ne peut pas être vidée/validée, et la deuxième méthode en a besoin.

Dans les cas readOnly-to-readOnly, et read/write-to-read/write la transaction externe n'a pas besoin d'être suspendue (sauf indication contraire de la propagation , évidemment).

68
candiru

L'appel de readOnly = false à partir de readOnly = true ne fonctionne pas car la transaction précédente se poursuit.

Dans votre exemple, la méthode handle () sur votre couche de service démarre une nouvelle transaction en lecture-écriture. Si la méthode handle appelle à son tour des méthodes de service qui annotent en lecture seule, la lecture seule n'aura aucun effet car elles participeront à la place à la transaction de lecture-écriture existante.

S'il est essentiel que ces méthodes soient en lecture seule, vous pouvez les annoter avec Propagation.REQUIRES_NEW, et elles démarreront alors une nouvelle transaction en lecture seule plutôt que de participer à la transaction en lecture-écriture existante.

Voici un exemple concret, CircuitStateRepository est un référentiel JPA de données de printemps.

BeanS appelle un Bean1 transactionnel = en lecture seule, qui effectue une recherche et appelle un Bean2 transactionnel = lecture-écriture qui enregistre un nouvel objet.

  • Bean1 démarre un tx en lecture seule.

31 09: 39: 44.199 [pool-1-thread-1] DEBUG o.s.orm.jpa.JpaTransactionManager - Création d'une nouvelle transaction avec le nom [nz.co.vodafone.wcim.business.Bean1.startSomething]: PROPAGATION_REQUIRED, ISOLATION_DEFAULT, readOnly; ''

  • Le bean 2 y participe.

    31 09: 39: 44.230 [pool-1-thread-1] DEBUG o.s.orm.jpa.JpaTransactionManager - Participation à une transaction existante

    Rien n'est engagé dans la base de données.

Maintenant, changez Bean2 @Transactional annotation à ajouter propagation=Propagation.REQUIRES_NEW

  • Bean1 démarre un tx en lecture seule.

    31 09: 31: 36.418 [pool-1-thread-1] DEBUG o.s.orm.jpa.JpaTransactionManager - Création d'une nouvelle transaction avec le nom [nz.co.vodafone.wcim.business.Bean1.startSomething]: PROPAGATION_REQUIRED, ISOLATION_DEFAULT, readOnly; ''

  • Bean2 démarre un nouveau tx en lecture-écriture

    31 09: 31: 36.449 [pool-1-thread-1] DEBUG o.s.orm.jpa.JpaTransactionManager - Suspension de la transaction en cours, création d'une nouvelle transaction avec le nom [nz.co.vodafone.wcim.business.Bean2.createSomething]

Et les modifications apportées par Bean2 sont désormais validées dans la base de données.

Voici l'exemple, testé avec spring-data, hibernate et Oracle.

@Named
public class BeanS {    
    @Inject
    Bean1 bean1;

    @Scheduled(fixedRate = 20000)
    public void runSomething() {
        bean1.startSomething();
    }
}

@Named
@Transactional(readOnly = true)
public class Bean1 {    
    Logger log = LoggerFactory.getLogger(Bean1.class);

    @Inject
    private CircuitStateRepository csr;

    @Inject
    private Bean2 bean2;

    public void startSomething() {    
        Iterable<CircuitState> s = csr.findAll();
        CircuitState c = s.iterator().next();
        log.info("GOT CIRCUIT {}", c.getCircuitId());
        bean2.createSomething(c.getCircuitId());    
    }
}

@Named
@Transactional(readOnly = false)
public class Bean2 {    
    @Inject
    CircuitStateRepository csr;

    public void createSomething(String circuitId) {
        CircuitState c = new CircuitState(circuitId + "-New-" + new DateTime().toString("hhmmss"), new DateTime());

        csr.save(c);
     }
}
19
dan carter

Par défaut, la propagation des transactions est OBLIGATOIRE, ce qui signifie que la même transaction se propagera d'un appelant transactionnel à l'appelé transactionnel. Dans ce cas également, l'état en lecture seule se propage. Par exemple. si une transaction en lecture seule appelle une transaction en lecture-écriture, la transaction entière sera en lecture seule.

Pourriez-vous utiliser le modèle Ouvrir la session dans la vue pour autoriser le chargement paresseux? De cette façon, votre méthode de gestion n'a pas du tout besoin d'être transactionnelle.

11
sijk

Il semble ignorer les paramètres de la transaction active actuelle, il n'applique les paramètres qu'à une nouvelle transaction:

 org.springframework.transaction.PlatformTransactionManager 
 TransactionStatus getTransaction (définition TransactionDefinition) 
 lève TransactionException 
 Renvoie une transaction actuellement active ou créez-en une nouvelle, selon la spécification comportement de propagation. 
 Notez que des paramètres tels que le niveau d'isolement ou le délai d'expiration ne seront appliqués qu'aux nouvelles transactions, et seront donc ignorés lors de la participation à des transactions actives. 
 De plus, tous les paramètres de définition de transaction ne seront pas pris en charge par chaque gestionnaire de transactions: une mise en œuvre correcte du gestionnaire de transactions doit lever une exception lorsque des paramètres non pris en charge sont rencontrés. 
 Une exception à la règle ci-dessus est l'indicateur de lecture seule, qui doit être ignoré si aucun mode de lecture explicite n'est pris en charge . Essentiellement, l'indicateur de lecture seule n'est qu'un indice d'optimisation potentielle.
5
Andrew Chen