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();
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:
@Transactional
L'annotation n'est pas prise en compte.@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).
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.
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.
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.
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(){
//...
}
}
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
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
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.