web-dev-qa-db-fra.com

L'attribut Spring @Transactional fonctionne-t-il sur une méthode privée?

Si j'ai une annotation @ Transactionnelle sur une méthode privée dans un bean Spring, l'annotation a-t-elle un effet?

Si la @Transactional annotation est sur une méthode publique, cela fonctionne et ouvre une transaction.

public class Bean {
  public void doStuff() {
     doPrivateStuff();
  }
  @Transactional
  private void doPrivateStuff() {

  }
}

...

Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();
178
Juha Syrjälä

La question n'est pas privée ou publique, la question est: comment est-elle invoquée et quelle implémentation AOP vous utilisez!

Si vous utilisez (par défaut) AOP Spring Proxy, toutes les fonctionnalités AOP fournies par Spring (comme @Transational) ne sera pris en compte que si l'appel passe par le proxy. - Ceci est normalement le cas si la méthode annotée est invoquée à partir d'un autre bean .

Cela a deux implications:

  • Comme les méthodes privées ne doivent pas être invoquées à partir d'un autre bean (l'exception est la réflexion), leur @Transactional L'annotation n'est pas prise en compte.
  • Si la méthode est publique mais qu'elle est invoquée à partir du même bean, elle ne sera pas prise en compte non plus (cette instruction n'est correcte que si (par défaut) l'AOP Spring Proxy est utilisé).

@See Référence de Spring: Chapitre 9.6 9.6 Mécanismes de remplacement

IMHO, vous devriez utiliser le mode aspectJ, au lieu des Spring Proxies, pour résoudre le problème. Et les aspects transactionnels AspectJ sont même tissés dans des méthodes privées (vérifié pour Spring 3.0).

141
Ralph

La réponse à votre question est non - @Transactional n'aura aucun effet s'il est utilisé pour annoter des méthodes privées. Le générateur de proxy les ignorera.

Ceci est documenté dans Spring Manual chapitre 10.5.6 :

Visibilité de la méthode et @Transactional

Lorsque vous utilisez des mandataires, vous devez appliquer le @Transactional annotation uniquement aux méthodes avec visibilité publique. Si vous annotez des méthodes protégées, privées ou visibles par un package avec le @Transactional annotation, aucune erreur n'est générée, mais la méthode annotée ne présente pas les paramètres transactionnels configurés. Pensez à utiliser AspectJ (voir ci-dessous) si vous devez annoter des méthodes non publiques.

210
skaffman

Par défaut, l'attribut @Transactional Ne fonctionne que lorsque vous appelez une méthode annotée sur une référence obtenue à partir de applicationContext.

public class Bean {
  public void doStuff() {
    doTransactionStuff();
  }
  @Transactional
  public void doTransactionStuff() {

  }
}

Cela ouvrira une transaction:

Bean bean = (Bean)appContext.getBean("bean");
bean.doTransactionStuff();

Cela ne va pas:

Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();

Référence Spring: Utilisation de @Transactional

Remarque: en mode proxy (par défaut), seuls les appels de méthode "externes" entrés via le proxy seront interceptés. Cela signifie que "l'auto-invocation", c'est-à-dire qu'une méthode de l'objet cible appelant une autre méthode de l'objet cible, ne conduira pas à une transaction réelle au moment de l'exécution, même si la méthode invoquée est marquée avec @Transactional!

Envisagez d'utiliser le mode AspectJ (voir ci-dessous) si vous vous attendez à ce que les invocations automatiques soient également encapsulées dans des transactions. Dans ce cas, il n'y aura pas de proxy en premier lieu; au lieu de cela, la classe cible sera "tissée" (c'est-à-dire que son code d'octet sera modifié) afin de transformer @Transactional en comportement d'exécution à tout type de méthode.

28
Juha Syrjälä

Oui, il est possible d'utiliser @Transactional sur des méthodes privées, mais comme d'autres l'ont mentionné, cela ne fonctionnera pas immédiatement. Vous devez utiliser AspectJ. Il m'a fallu un certain temps pour comprendre comment le faire fonctionner. Je vais partager mes résultats.

J'ai choisi d'utiliser le tissage au moment de la compilation au lieu du tissage au temps de chargement, car je pense que c'est une meilleure option dans l'ensemble. De plus, j'utilise Java 8 donc vous devrez peut-être ajuster certains paramètres.

Premièrement, ajoutez la dépendance pour aspectjrt.

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.8</version>
</dependency>

Ajoutez ensuite le plug-in AspectJ pour effectuer le tissage du bytecode dans Maven (il ne s'agit peut-être pas d'un exemple minimal).

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.8</version>
    <configuration>
        <complianceLevel>1.8</complianceLevel>
        <source>1.8</source>
        <target>1.8</target>
        <aspectLibraries>
            <aspectLibrary>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aspects</artifactId>
            </aspectLibrary>
        </aspectLibraries>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Enfin, ajoutez ceci à votre classe de configuration

@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)

Vous devriez maintenant pouvoir utiliser @Transactional sur des méthodes privées.

Une mise en garde à cette approche: vous devrez configurer votre IDE pour connaître AspectJ sinon, si vous exécutez l'application via Eclipse, par exemple, elle risque de ne pas fonctionner. Assurez-vous de tester contre un Maven direct construire comme un test de santé mentale.

12
James Watkins

Si vous devez insérer une méthode privée dans une transaction et ne pas utiliser aspectj, vous pouvez utiliser TransactionTemplate .

@Service
public class MyService {

    @Autowired
    private TransactionTemplate transactionTemplate;

    private void process(){
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                processInTransaction();
            }
        });

    }

    private void processInTransaction(){
        //...
    }

}
4
loonis

La réponse est non. Veuillez voir Référence Spring: Utilisation de @Transactional :

Le @Transactional l'annotation peut être placée avant une définition d'interface, une méthode sur une interface, une définition de classe ou une méthode public sur une classe

3
ivy

Spring Docs explique que

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.

Envisagez d'utiliser le mode AspectJ (voir l'attribut mode dans le tableau ci-dessous) si vous vous attendez à ce que les invocations automatiques soient également encapsulées dans des transactions. Dans ce cas, il n'y aura pas de proxy en premier lieu; au lieu de cela, la classe cible sera tissée (c'est-à-dire que son code d'octet sera modifié) afin de transformer @Transactional en comportement d'exécution à tout type de méthode.

Une autre façon est l'utilisateur BeanSelfAware

3
user536161

De la même manière que @ loonis a suggéré d'utiliser TransactionTemplate on peut utiliser ce composant d'assistance (Kotlin):

@Component
class TransactionalUtils {
    /**
     * Execute any [block] of code (even private methods)
     * as if it was effectively [Transactional]
     */
    @Transactional
    fun <R> executeAsTransactional(block: () -> R): R {
        return block()
    }
}

Usage:

@Service
class SomeService(private val transactionalUtils: TransactionalUtils) {

    fun foo() {
        transactionalUtils.executeAsTransactional { transactionalFoo() }
    }

    private fun transactionalFoo() {
        println("This method is executed within transaction")
    }
}

Je ne sais pas si TransactionTemplate réutilise la transaction existante ou non, mais ce code le fait certainement.

0
Lu55