Transaction marquée comme annulation uniquement: comment trouver la cause

93

Je rencontre des problèmes pour valider une transaction dans ma méthode @Transactional:

methodA() {
    methodB()
}

@Transactional
methodB() {
    ...
    em.persist();
    ...
    em.flush();
    log("OK");
}

Quand j'appelle methodB () depuis methodA (), la méthode passe avec succès et je peux voir "OK" dans mes journaux. Mais alors je reçois

Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:521)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:393)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:622)
    at methodA()...
  1. Le contexte de methodB est complètement absent de l'exception - ce qui est bien je suppose?
  2. Quelque chose dans la méthodeB () a marqué la transaction comme une annulation uniquement? Comment puis-je le découvrir? Existe-t-il, par exemple, un moyen de vérifier quelque chose comme getCurrentTransaction().isRollbackOnly()?- comme ça, je pourrais parcourir la méthode et trouver la cause.
Vojtěch
la source
en relation: stackoverflow.com/q/25322658/697313
Yaroslav Stavnichiy
Il est intéressant de noter que si votre table de base de données n'existe pas, cette erreur sera parfois également affichée.
Ng Sek Long

Réponses:

101

Lorsque vous marquez votre méthode comme @Transactional, l'occurrence d'une exception à l'intérieur de votre méthode marquera le TX environnant comme un retour arrière uniquement (même si vous les attrapez). Vous pouvez utiliser d'autres attributs d' @Transactionalannotation pour l'empêcher de revenir en arrière comme:

@Transactional(rollbackFor=MyException.class, noRollbackFor=MyException2.class)
Ean V
la source
6
Eh bien, j'ai essayé d'utiliser noRollbackFor=Exception.class, mais cela semble n'avoir aucun effet - est-ce que cela fonctionne pour les exceptions héritées?
Vojtěch
6
Oui. En regardant votre propre réponse, c'est vrai (vous ne l'aviez pas fournie methodCdans votre premier message). Les deux methodBet methodCutilisent le même TX et l' @Transactionalannotation la plus spécifique est toujours utilisée, donc lorsque methodCl'exception est lancée, le TX environnant sera marqué comme rollback uniquement. Vous pouvez également utiliser différents marqueurs de propagation pour éviter cela.
Ean V
@Ean toute exception dans votre méthode marquera le TX environnant comme roll-back uniquement. Cela s'applique-t-il également aux transactions en lecture seule?
Marko Vranjkovic
1
@lolotron @Ean Je peux confirmer que cela s'appliquera effectivement à une transaction en lecture seule. Ma méthode lançait une EmptyResultDataAccessExceptionexception sur une transaction en lecture seule et j'ai eu la même erreur. Changer mon annotation pour @Transactional(readOnly = true, noRollbackFor = EmptyResultDataAccessException.class)résoudre le problème.
cbmeeks
5
Cette réponse est fausse. Spring ne connaît que les exceptions qui passent par le @Transactionalwrapper proxy, c'est-à-dire non interceptées . Voir l'autre réponse de Vojtěch pour l'histoire complète. Il peut y avoir des @Transactionalméthodes imbriquées qui peuvent marquer uniquement l'annulation de votre transaction.
Yaroslav Stavnichiy
68

J'ai enfin compris le problème:

methodA() {
    methodB()
}

@Transactional(noRollbackFor = Exception.class)
methodB() {
    ...
    try {
        methodC()
    } catch (...) {...}
    log("OK");
}

@Transactional
methodC() {
    throw new ...();
}

Ce qui se passe, c'est que même si le methodBa la bonne annotation, le methodCne le fait pas. Lorsque l'exception est levée, la seconde @Transactionalmarque la première transaction comme Rollback uniquement de toute façon.

Vojtěch
la source
5
Le statut de la transaction est stocké dans une variable locale de thread. Lorsque le ressort intercepte methodC et définit l'indicateur comme rollback, votre transaction est déjà marquée pour roll back. Toute suppression supplémentaire de l'exception n'aidera pas car lorsque la validation finale se produira, vous obtiendrez l'erreur
vit le
@ Vojtěch De toute façon hypothétiquement si methodC a propagation=requires_newalors methodB ne reviendra pas?
deFreitas
4
methodCdoit être dans un bean / service Spring différent ou accessible via le proxy Spring. Sinon, Spring n'aura aucune possibilité de connaître votre exception. Seule une exception qui passe par l' @Transactionalannotation peut marquer la transaction comme annulation uniquement.
Yaroslav Stavnichiy
43

Pour récupérer rapidement l'exception à l'origine sans avoir besoin de recoder ou de reconstruire , définissez un point d'arrêt sur

org.hibernate.ejb.TransactionImpl.setRollbackOnly() // Hibernate < 4.3, or
org.hibernate.jpa.internal.TransactionImpl() // as of Hibernate 4.3

et montez dans la pile, généralement vers un intercepteur. Là, vous pouvez lire l'exception à l'origine de certains blocs catch.

FelixJongleur42
la source
6
Dans Hibernate 4.3.11, c'estorg.hibernate.jpa.internal.TransactionImpl
Wim Deblauwe
Très bien mon ami!
Rafael Andrade le
Merci! Dans les versions plus récentes d'Hibernate (5.4.17), la classe est org.hibernate.engine.transaction.internal.TransactionImplet la méthode est setRollbackOnly.
Peter Catalin
11

J'ai eu du mal avec cette exception lors de l'exécution de mon application.

Enfin, le problème était sur la requête SQL . je veux dire que la requête est fausse.

veuillez vérifier votre requête. C'est ma suggestion

Kumaresan Perumal
la source
1
Pour clarifier: si vous 1. avez une erreur dans votre syntaxe sql 2. êtes configuré pour revenir en arrière sur l'exception 3. avez des transactions readOnly, vous obtiendrez cette erreur car la syntaxe sql provoque une exception qui déclenche une restauration qui échoue car vous êtes dans " mode lecture seule ".
Dave
7

Recherchez les exceptions levées et interceptées dans les ...sections de votre code. Les exceptions d'application d'exécution et de restauration provoquent une restauration lorsqu'elles sont rejetées d'une méthode métier, même si elles sont interceptées à un autre endroit.

Vous pouvez utiliser le contexte pour savoir si la transaction est marquée pour une annulation.

@Resource
private SessionContext context;

context.getRollbackOnly();
Mareen
la source
1
Il me semble que j'ai trouvé la cause, mais je ne comprends pas pourquoi cela se produit. Une méthode interne lève une exception, que j'attrape, enregistre et ignore. Mais la transaction est de toute façon marquée comme Rollback seulement. Comment puis-je l'empêcher? Je ne veux pas que les transactions soient influencées par des exceptions que je saisis correctement.
Vojtěch
Une SessionContextclasse standard est-elle au printemps? Il me semble que c'est plutôt EJB3 et il n'est pas contenu dans mon Spring Application.
Vojtěch
3
Mon mauvais j'ai raté le fait qu'il s'agit du printemps. Quoi qu'il en soit, il devrait y avoir quelque chose comme TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()disponible.
Mareen
2

J'ai trouvé une bonne explication avec des solutions: https://vcfvct.wordpress.com/2016/12/15/spring-nested-transactional-rollback-only/

1) supprimez @Transacional de la méthode imbriquée si elle ne nécessite pas vraiment de contrôle de transaction. Donc, même s'il a une exception, cela ne fait que bouillonner et n'affecte pas les choses transactionnelles.

OU:

2) si la méthode imbriquée nécessite un contrôle de transaction, définissez-la comme REQUIRE_NEW pour la politique de propagation de cette façon, même si elle lance une exception et est marquée uniquement comme une annulation, l'appelant ne sera pas affecté.

aquajach
la source
1

désactiver le gestionnaire de transaction dans votre Bean.xml

<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager"/>
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

commentez ces lignes, et vous verrez l'exception provoquant l'annulation;)

rémy
la source
0

appliquer le code ci-dessous dans productRepository

@Query("update Product set prodName=:name where prodId=:id ") @Transactional @Modifying int updateMyData(@Param("name")String name, @Param("id") Integer id);

tandis que dans le test junit, appliquer ci-dessous le code

@Test
public void updateData()
{
  int i=productRepository.updateMyData("Iphone",102);

  System.out.println("successfully updated ... ");
  assertTrue(i!=0);

}

cela fonctionne bien pour mon code

Asif Raza
la source