web-dev-qa-db-fra.com

Transactions imbriquées au printemps

Dans mon projet Spring Boot, j'ai implémenté la méthode de service suivante:

@Transactional
public boolean validateBoard(Board board) {
    boolean result = false;
    if (inProgress(board)) {
        if (!canPlayWithCurrentBoard(board)) {
            update(board, new Date(), Board.AFK);
            throw new InvalidStateException(ErrorMessage.BOARD_TIMEOUT_REACHED);
        }
        if (!canSelectCards(board)) {
            update(board, new Date(), Board.COMPLETED);
            throw new InvalidStateException(ErrorMessage.ALL_BOARD_CARDS_ALREADY_SELECTED);
        }
        result = true;
    }
    return result;
}

dans cette méthode, j'utilise une autre méthode de service appelée update:

@Transactional(propagation = Propagation.REQUIRES_NEW)
public Board update(Board board, Date finishedDate, Integer status) {
    board.setStatus(status);
    board.setFinishedDate(finishedDate);

    return boardRepository.save(board);
}

Je dois valider les modifications de la base de données dans la méthode update indépendamment de la transaction de propriétaire lancée dans la méthode validateBoard À l'heure actuelle, toute modification est annulée en cas d'exception.

Même avec @Transactional(propagation = Propagation.REQUIRES_NEW) cela ne fonctionne pas.

Comment faire cela correctement avec Spring et autoriser les transactions imbriquées?

18
alexanoid

Cette documentation couvre votre problème - https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/data-access.html#transaction-declarative-annotations

En mode proxy (par défaut), seuls les appels de méthode externes entrés via le proxy sont interceptés. Cela signifie qu'une auto-invocation, en fait, une méthode de l'objet cible appelant une autre méthode de l'objet cible ne mènera pas à une transaction réelle au moment de l'exécution, même si la méthode invoquée est marquée avec @Transactional. De plus, le proxy doit être entièrement initialisé pour fournir le comportement attendu. Vous ne devez donc pas vous fier à cette fonctionnalité dans votre code d'initialisation, c'est-à-dire @PostConstruct.

Cependant, il existe une option pour passer en mode AspectJ

18
Jakub Bibro

L'utilisation du partenaire "auto" peut résoudre ce problème.

exemple de code comme ci-dessous:

@Service @Transactional
public class YourService {
   ... your member

   @Autowired
   private YourService self;   //inject proxy as instance member variable ;

   @Transactional(propagation= Propagation.REQUIRES_NEW)
   public void methodFoo() {
      //...
   }

   public void methodBar() {
      //call self.methodFoo() rather than this.methodFoo()
      self.methodFoo();
   }
}

Le point utilise "soi" plutôt que "ceci".

3
shenyu1997

La règle de base des transactions imbriquées est qu’elles dépendent entièrement de la base de données sous-jacente, c’est-à-dire que la prise en charge des transactions imbriquées et leur traitement dépendent de la base de données et varient avec elle. pas vu par la transaction 'Host' jusqu'à ce que la transaction imbriquée soit validée. Ceci peut être réalisé en utilisant l’isolation de transaction dans @Transactional (isolation = "")

Vous devez identifier l’endroit dans votre code à partir duquel une exception est levée, c’est-à-dire de la méthode parent: "validateBoard" ou de la méthode enfant: "update".

Votre extrait de code montre que vous lancez explicitement les exceptions.

TU DOIS SAVOIR::

Dans sa configuration par défaut, la transaction Spring Framework Le code d'infrastructure ne marque qu'une transaction à annuler dans le cas d'exécution, des exceptions non contrôlées; c'est à ce moment que l'exception levée est une instance ou une sous-classe de RuntimeException.

Mais @Transactional n'annule jamais une transaction pour une exception vérifiée.

Ainsi, Spring vous permet de définir

  • Exception pour laquelle la transaction devrait être annulée
  • Exception pour laquelle la transaction ne devrait pas être annulée

Essayez d'annoter votre méthode enfant: mettez à jour avec @Transactional (no-rollback-for = "NomException") ou votre méthode parent.

1
Philip Dilip

Votre annotation de transaction dans la méthode update ne sera pas considérée par l'infrastructure de transaction Spring si elle est appelée à partir d'une méthode de la même classe. Pour plus d'informations sur le fonctionnement de l'infrastructure de transaction Spring, veuillez vous reporter à this .

1
Shailendra

Votre problème est l'appel d'une méthode à partir d'une autre méthode à l'intérieur du même proxy. C'est une invocation automatique. Dans votre cas, vous pouvez facilement résoudre le problème sans déplacer une méthode dans un autre service (pourquoi devez-vous créer un autre service simplement pour déplacer une méthode d'un service à un autre simplement pour éviter l'auto-invocation?), Simplement pour appeler la seconde méthode ne provient pas directement de la classe actuelle, mais du conteneur printanier. Dans ce cas, vous appelez la deuxième méthode proxy avec transaction non avec auto-invocatio.

Ce principe est utile pour tout objet proxy lorsque vous avez besoin d'une invocation automatique, et pas uniquement d'un proxy transactionnel.

@Service
class SomeService ..... {
    -->> @Autorired
    -->> private ApplicationContext context;
    -->> //or with implementing ApplicationContextAware

    @Transactional(any propagation , it's not important in this case)
    public boolean methodOne(SomeObject object) {
      .......
       -->> here you get a proxy from context and call a method from this proxy
       -->>context.getBean(SomeService.class).
            methodTwo(object);
      ......
   }

    @Transactional(any propagation , it's not important in this case)public boolean 
    methodTwo(SomeObject object) {
    .......
   }
}

lorsque vous appelez context.getBean(SomeService.class).methodTwo(object);, le conteneur renvoie un objet proxy et sur ce proxy, vous pouvez appeler methodTwo(...) avec transaction.

0
xyz