101 votes

Printemps @Transactional propagation en lecture seule

Je suis en train d'expérimenter l'utilisation du modèle de conception de commande pour permettre à ma couche web de fonctionner avec des entités Hibernate dans le contexte d'une seule transaction (évitant ainsi les exceptions de chargement paresseux). Cependant, je suis maintenant confus sur la manière dont je devrais gérer les transactions.

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

Ma couche de service expose un gestionnaire de commandes qui exécute les commandes qui lui sont passées au nom de la couche web.

@Transactional
public Command handle(Command cmd) throws CommandException

Je suppose que j'ai raison de rendre la méthode handle du gestionnaire de commandes transactionnelle. C'est là que la confusion intervient. Si l'implémentation d'une commande appelle plusieurs méthodes de la couche de service, il n'y a aucun moyen pour le gestionnaire de commandes 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 la propagation fonctionne dans cet exemple. Si je devais rendre la méthode handle() readOnly = true, que se passe-t-il si la commande appelle ensuite une méthode de la couche de service qui est annotée avec @Transactional(realOnly = false) ?

123voto

candiru Points 2108

Tout d'abord, puisque Spring ne gère pas la persistance elle-même, il ne peut pas spécifier exactement ce que readOnly devrait signifier. Cet attribut est seulement un indice pour le fournisseur, le comportement dépend, dans ce cas, de Hibernate.

Si vous spécifiez readOnly comme true, le mode flush sera défini sur FlushMode.NEVER dans la Session Hibernate actuelle, empêchant ainsi la session de valider la transaction.

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

Maintenant, voyons comment fonctionne la propagation des transactions.

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

D'accord, nous y sommes presque. Voyons maintenant ce que readOnly apporte dans ce scénario.

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

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

Dans les cas readOnly-vers-ReadOnly et lecture/écriture-vers-lecture/écriture, la transaction externe n'a pas besoin d'être suspendue (sauf si vous spécifiez une propagation différente, évidemment).

43voto

dan carter Points 492

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

Dans votre exemple, la méthode handle() de votre couche de service lance une nouvelle transaction en lecture-écriture. Si la méthode handle appelle à son tour des méthodes de service annotées en lecture seule, la lecture seule n'aura aucun effet car elles participeront à 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 au lieu de participer à la transaction de lecture-écriture existante.

Voici un exemple de fonctionnement, CircuitStateRepository est un référentiel JPA de spring-data.

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

  • Bean1 démarre une tx en lecture seulement.

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

  • 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 enregistré dans la base de données.

Modifiez maintenant l'annotation @Transactional de Bean2 pour y ajouter propagation=Propagation.REQUIRES_NEW

  • Bean1 démarre une tx en lecture seulement.

    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 une nouvelle tx en lecture-écriture

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

Et les modifications apportées par Bean2 sont maintenant enregistré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 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);
     }
}

16voto

sijk Points 51

Par défaut, la propagation des transactions est REQUISE, ce qui signifie que la même transaction se propagera d'un appelant transactionnel à un appelant transactionnel. Dans ce cas, le statut en lecture seule se propagera également. Par exemple, si une transaction en lecture seule appelle une transaction en lecture-écriture, l'ensemble de la transaction sera en lecture seule.

Pourriez-vous utiliser le modèle Open Session in View pour permettre le chargement paresseux ? De cette façon, votre méthode de gestion n'a pas besoin d'être transactionnelle du tout.

6voto

andrew chen Points 31

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

org.springframework.transaction.PlatformTransactionManager
TransactionStatus getTransaction(TransactionDefinition definition)
                         throws TransactionException
Renvoie une transaction actuellement active ou en crée une nouvelle, en fonction du comportement de propagation spécifié.
Notez que des paramètres tels que le niveau d'isolation ou le délai d'attente 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 transaction : une implémentation correcte d'un gestionnaire de transaction doit générer une exception en cas de paramètres non pris en charge.
Une exception à la règle ci-dessus est le drapeau en lecture seule, qui doit être ignoré si aucun mode en lecture seule explicite n'est pris en charge. L'indicateur en lecture seule est essentiellement juste un indice pour une éventuelle optimisation.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X