L'attributo Spring @Transactional funziona su un metodo privato?


196

Se ho un @Transactional -annotation su un metodo privato in un bean Spring, l'annotazione ha qualche effetto?

Se l' @Transactionalannotazione è su un metodo pubblico, funziona e apre una transazione.

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

  }
}

...

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

Risposte:


163

La domanda non è privata o pubblica, la domanda è: come viene invocata e quale implementazione AOP usi!

Se si utilizza (predefinito) Spring Proxy AOP, tutte le funzionalità AOP fornite da Spring (come @Transational) verranno prese in considerazione solo se la chiamata passa attraverso il proxy. - Questo è normalmente il caso se il metodo annotato viene richiamato da un altro bean.

Ciò ha due implicazioni:

  • Poiché i metodi privati ​​non devono essere richiamati da un altro bean (l'eccezione è la riflessione), la loro @TransactionalAnnotazione non viene presa in considerazione.
  • Se il metodo è pubblico, ma viene richiamato dallo stesso bean, non verrà nemmeno preso in considerazione (questa istruzione è corretta solo se viene utilizzato (predefinito) Spring Proxy AOP).

@ Vedi riferimento di primavera: capitolo 9.6 9.6 Meccanismi di proxy

IMHO dovresti usare la modalità aspectJ, invece dei Spring Proxies, che risolverà il problema. E gli Aspetti Transazionali AspectJ sono intrecciati anche con metodi privati ​​(controllati per la primavera 3.0).


4
Entrambi i punti non sono necessariamente veri. Il primo non è corretto: i metodi privati possono essere richiamati in modo riflessivo, ma la logica di individuazione del proxy sceglie di non farlo. Il secondo punto è vero solo per i proxy JDK basati su interfaccia, ma non per i proxy basati su sottoclasse CGLIB.
Skaffman,

@skaffman: 1 - Rendo il mio statuto più preciso, 2. Ma il proxy predefinito è basato sull'interfaccia - non è vero?
Ralph,

2
Dipende se il target utilizza interfacce o meno. In caso contrario, viene utilizzato CGLIB.
Skaffman,

puoi dirmi la risonanza o qualche riferimento perché cglib non può ma aspettoj può?
Phil

1
Riferimento dal collegamento nel blocco risposte, se si desidera utilizzare Spring Proxies [ambiente predefinito], inserire annotazioni su doStuff () e chiamare doPrivateStuff () utilizzando ((Bean) AopContext.currentProxy ()). DoPrivateStuff (); Eseguirà entrambi i metodi in una stessa transazione se viene propagata la propagazione [ambiente predefinito].
Michael Ouyang,

219

La risposta alla tua domanda è no: non @Transactionalavrà alcun effetto se utilizzata per annotare metodi privati. Il generatore di proxy li ignorerà.

Questo è documentato nel Manuale di primavera capitolo 10.5.6 :

Visibilità del metodo e @Transactional

Quando si utilizzano i proxy, è necessario applicare l' @Transactionalannotazione solo ai metodi con visibilità pubblica. Se si annotano metodi protetti, privati ​​o visibili a pacchetto con l' @Transactionalannotazione, non viene generato alcun errore, ma il metodo annotato non mostra le impostazioni transazionali configurate. Prendi in considerazione l'uso di AspectJ (vedi sotto) se devi annotare metodi non pubblici.


Sei sicuro di questo? Non mi aspetto che faccia la differenza.
Willcodejavaforfood,

che ne dici se lo stile proxy è Cglib?
giglio,

32

Per impostazione predefinita, l' @Transactionalattributo funziona solo quando si chiama un metodo annotato su un riferimento ottenuto da applicationContext.

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

  }
}

Questo aprirà una transazione:

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

Questo non:

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

Riferimento primaverile: utilizzo di @Transactional

Nota: in modalità proxy (che è l'impostazione predefinita), verranno intercettate solo le chiamate di metodo "esterne" che arrivano attraverso il proxy. Ciò significa che "auto-invocazione", ovvero un metodo all'interno dell'oggetto target che chiama qualche altro metodo dell'oggetto target, non porterà a una transazione effettiva in fase di esecuzione anche se il metodo invocato è contrassegnato con @Transactional!

Prendi in considerazione l'uso della modalità AspectJ (vedi sotto) se prevedi che anche le autoinvocazioni vengano completate con le transazioni. In questo caso, non ci sarà in primo luogo un proxy; invece, la classe target verrà "tessuta" (ovvero il suo codice byte verrà modificato) al fine di trasformarsi @Transactionalin comportamento runtime su qualsiasi tipo di metodo.


Intendi bean = new Bean () ;?
Willcodejavaforfood,

No. Se creo bean con new Bean (), l'annotazione non funzionerà mai almeno senza l'utilizzo di Aspect-J.
Juha Syrjälä,

2
Grazie! Questo spiega uno strano comportamento che stavo osservando. Abbastanza intuitivo questo limite di invocazione del metodo interno ...
manuel aldana,

Ho appreso che "solo le chiamate di metodo esterne che arrivano attraverso il proxy saranno intercettate" nel modo più duro
asg

13

Sì, è possibile utilizzare @Transactional su metodi privati, ma come altri hanno già detto questo non funzionerà immediatamente. Devi usare AspectJ. Mi ci è voluto del tempo per capire come farlo funzionare. Condividerò i miei risultati.

Ho scelto di usare la tessitura a tempo di compilazione anziché la tessitura a tempo di caricamento perché penso che sia un'opzione complessivamente migliore. Inoltre, sto usando Java 8, quindi potrebbe essere necessario regolare alcuni parametri.

Innanzitutto, aggiungi la dipendenza per aspectjrt.

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

Quindi aggiungi il plug-in AspectJ per eseguire la tessitura bytecode effettiva in Maven (questo potrebbe non essere un esempio minimo).

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

Infine aggiungi questo alla tua classe di configurazione

@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)

Ora dovresti essere in grado di utilizzare @Transactional su metodi privati.

Un avvertimento a questo approccio: dovrai configurare il tuo IDE per essere a conoscenza di AspectJ altrimenti, se ad esempio esegui l'app tramite Eclipse, potrebbe non funzionare. Assicurati di testare contro una build Maven diretta come controllo di integrità.


se il metodo di proxy è cglib, non è necessario implementare un'interfaccia di cui il metodo dovrebbe essere pubblico, quindi è in grado di utilizzare @Transactional su metodi privati?
giglio,

Sì, funziona con metodi privati ​​e senza interfacce! Finché AspectJ è configurato correttamente, garantisce fondamentalmente decoratori di metodi di lavoro. E user536161 ha sottolineato nella sua risposta che funzionerà anche su auto-invocazioni. È davvero bello e solo un po 'spaventoso.
James Watkins,

12

Se è necessario racchiudere un metodo privato all'interno di una transazione e non si desidera utilizzare aspectj, è possibile utilizzare 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(){
        //...
    }

}

Buono a mostrare l' TransactionTemplateutilizzo, ma si prega di chiamare quel secondo metodo ..RequiresTransactionanziché ..InTransaction. Dai sempre un nome a come vorresti leggerlo un anno dopo. Direi anche se è necessario un secondo metodo privato: mettere il suo contenuto direttamente nell'implementazione anonima executeo se diventa disordinato potrebbe essere un'indicazione per suddividere l'implementazione in un altro servizio che è possibile annotare @Transactional.
Bloccato il

@Stuck, il secondo metodo non è effettivamente necessario, ma risponde alla domanda originale che è come applicare una transazione di primavera su un metodo privato
loonis

sì, ho già votato a favore della risposta ma volevo condividere un po 'di contesto e pensieri su come applicarla, perché penso che dal punto di vista dell'architettura questa situazione sia una potenziale indicazione per un difetto di progettazione.
Bloccato

5

Spring Docs lo spiega

In modalità proxy (che è l'impostazione predefinita), vengono intercettate solo le chiamate di metodo esterne che arrivano attraverso il proxy. Ciò significa che l'auto-invocazione, in effetti, un metodo all'interno dell'oggetto target che chiama un altro metodo dell'oggetto target, non porterà a una transazione effettiva in fase di esecuzione anche se il metodo invocato è contrassegnato con @Transactional.

Prendi in considerazione l'uso della modalità AspectJ (vedi l'attributo mode nella tabella seguente) se prevedi che anche le autoinvocazioni vengano completate con le transazioni. In questo caso, non ci sarà in primo luogo un proxy; invece, la classe target verrà tessuta (ovvero, il suo codice byte verrà modificato) al fine di trasformare @Transactional in comportamento runtime su qualsiasi tipo di metodo.

Un altro modo è l'utente BeanSelfAware


potresti aggiungere un riferimento a BeanSelfAware? Non sembra una lezione di primavera
chiede il

@asgs Supponiamo che si tratti di autoiniezione (fornire un bean con un'istanza di se stesso racchiusa in un proxy). Puoi vedere esempi in stackoverflow.com/q/3423972/355438 .
Lu55


1

Allo stesso modo in cui @loonis ha suggerito di utilizzare TransactionTemplate, è possibile utilizzare questo componente di supporto (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()
    }
}

Uso:

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

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

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

Non so se TransactionTemplateriutilizzare la transazione esistente o meno, ma questo codice sicuramente lo fa.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.