Funktioniert das Attribut Spring @Transactional mit einer privaten Methode?

195

Wenn ich eine @Transactional- Annotation für eine private Methode in einer Spring Bean habe, hat die Annotation irgendeine Auswirkung?

Wenn sich die @TransactionalAnnotation auf einer öffentlichen Methode befindet, funktioniert sie und öffnet eine Transaktion.

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

  }
}

...

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

Antworten:

162

Die Frage ist nicht privat oder öffentlich, die Frage lautet: Wie wird sie aufgerufen und welche AOP-Implementierung verwenden Sie?

Wenn Sie Spring Proxy AOP (Standard) verwenden, werden alle von Spring bereitgestellten AOP-Funktionen (wie @Transational) nur berücksichtigt, wenn der Anruf über den Proxy erfolgt. - Dies ist normalerweise der Fall, wenn die mit Anmerkungen versehene Methode von einer anderen Bean aufgerufen wird .

Dies hat zwei Auswirkungen:

  • Da private Methoden nicht von einer anderen Bean aufgerufen werden dürfen (Ausnahme: Reflection), wird ihre @TransactionalAnnotation nicht berücksichtigt.
  • Wenn die Methode öffentlich ist, aber von derselben Bean aufgerufen wird, wird sie auch nicht berücksichtigt (diese Anweisung ist nur korrekt, wenn (Standard) Spring Proxy AOP verwendet wird).

@ Siehe Federreferenz : Kapitel 9.6 9.6 Proxy-Mechanismen

IMHO sollten Sie anstelle der Spring Proxies den AspektJ-Modus verwenden, um das Problem zu lösen. Und die AspectJ-Transaktionsaspekte sind sogar in private Methoden eingebunden (auf Spring 3.0 geprüft).

Ralph
quelle
4
Beide Punkte sind nicht unbedingt wahr. Die erste ist falsch - private Methoden können reflektierend aufgerufen werden, aber die Proxy-Erkennungslogik verzichtet darauf. Der zweite Punkt gilt nur für Schnittstellen-basierte JDK-Proxys, nicht jedoch für CGLIB-Subklassen-basierte Proxys.
Skaffman
@skaffman: 1 - Ich mache meine Aussage präziser, 2. Aber der Standard-Proxy ist die Schnittstelle - nicht wahr?
Ralph
2
Das hängt davon ab, ob das Ziel Schnittstellen verwendet oder nicht. Ist dies nicht der Fall, wird CGLIB verwendet.
Skaffman
Kannst du mir den Resonanz oder eine Referenz sagen, warum Cglib nicht kann, aber Aspekt?
Phil
1
Referenz aus dem Link im Antwortblock: Wenn Sie Spring Proxies [Standardumgebung] verwenden möchten, fügen Sie eine Anmerkung zu doStuff () hinzu und rufen Sie doPrivateStuff () mit ((Bean) AopContext.currentProxy ()) auf. DoPrivateStuff (); Es werden beide Methoden in derselben Transaktion ausgeführt, wenn die Weitergabe erneut erforderlich ist [Standardumgebung].
Michael Ouyang
218

Die Antwort auf Ihre Frage lautet "Nein" - @Transactionalhat keine Auswirkung, wenn private Methoden mit Anmerkungen versehen werden. Der Proxy-Generator ignoriert sie.

Dies ist im Spring Manual Kapitel 10.5.6 dokumentiert :

Methodensichtbarkeit und @Transactional

Wenn Sie Proxys verwenden, sollten Sie die @TransactionalAnmerkung nur auf Methoden mit öffentlicher Sichtbarkeit anwenden . Wenn Sie geschützte, private oder für Pakete sichtbare Methoden mit der @TransactionalAnnotation kommentieren, wird kein Fehler ausgegeben, aber die mit Annotationen versehene Methode weist nicht die konfigurierten Transaktionseinstellungen auf. Ziehen Sie die Verwendung von AspectJ in Betracht (siehe unten), wenn Sie nicht öffentliche Methoden mit Anmerkungen versehen müssen.

Skaffman
quelle
Bist du dir da sicher? Ich würde nicht erwarten, dass es einen Unterschied macht.
Willcodejavaforfood
Wie wäre es, wenn der Proxy-Stil Cglib ist?
Lilie
32

Standardmäßig @Transactionalfunktioniert das Attribut nur, wenn eine mit Anmerkungen versehene Methode für eine aus applicationContext erhaltene Referenz aufgerufen wird.

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

  }
}

Dies öffnet eine Transaktion:

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

Dies wird nicht:

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

Federreferenz: Verwenden von @Transactional

Hinweis: Im Proxy-Modus (Standardeinstellung) werden nur 'externe' Methodenaufrufe abgefangen, die über den Proxy eingehen. Dies bedeutet, dass 'Selbstaufruf', dh eine Methode innerhalb des Zielobjekts, die eine andere Methode des Zielobjekts aufruft, zur Laufzeit nicht zu einer tatsächlichen Transaktion führt, selbst wenn die aufgerufene Methode mit markiert ist @Transactional!

Ziehen Sie die Verwendung des AspectJ-Modus in Betracht (siehe unten), wenn Sie erwarten, dass Selbstaufrufe auch mit Transaktionen abgeschlossen werden. In diesem Fall gibt es überhaupt keinen Proxy. Stattdessen wird die Zielklasse "gewebt" (dh ihr Bytecode wird geändert), um @Transactionalbei jeder Art von Methode ein Laufzeitverhalten zu erzielen.

Juha Syrjälä
quelle
Meinst du bean = new Bean ();?
Willcodejavaforfood
Nee. Wenn ich Beans mit new Bean () erstelle, funktioniert die Annotation zumindest ohne Verwendung von Aspect-J nie.
Juha Syrjälä
2
Vielen Dank! Dies erklärt das seltsame Verhalten, das ich beobachtet habe. Ganz kontraintuitiv diese interne Methode
Aufrufbeschränkung
Ich habe gelernt, dass die "einzigen externen Methodenaufrufe, die über den Proxy
eingehen, auf
13

Ja, es ist möglich, @Transactional für private Methoden zu verwenden, aber wie andere bereits erwähnt haben, funktioniert dies nicht sofort. Sie müssen AspectJ verwenden. Ich brauchte einige Zeit, um herauszufinden, wie ich es zum Laufen bringen konnte. Ich werde meine Ergebnisse teilen.

Ich habe mich für das Weben zur Kompilierungszeit anstelle des Webens zur Ladezeit entschieden, da ich denke, dass dies eine insgesamt bessere Option ist. Außerdem verwende ich Java 8, sodass Sie möglicherweise einige Parameter anpassen müssen.

Fügen Sie zunächst die Abhängigkeit für aspectjrt hinzu.

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

Fügen Sie dann das AspectJ-Plugin hinzu, um das eigentliche Bytecode-Weben in Maven durchzuführen (dies ist möglicherweise kein minimales Beispiel).

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

Fügen Sie dies schließlich Ihrer Konfigurationsklasse hinzu

@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)

Jetzt sollten Sie @Transactional für private Methoden verwenden können.

Eine Einschränkung bei diesem Ansatz: Sie müssen Ihre IDE so konfigurieren, dass Sie AspectJ kennen. Andernfalls funktioniert die App möglicherweise nicht, wenn Sie sie über Eclipse ausführen. Stellen Sie sicher, dass Sie einen Test gegen einen direkten Maven-Build durchführen.

James Watkins
quelle
Wenn die Proxy-Methode cglib ist, muss keine Schnittstelle implementiert werden, deren Methode öffentlich sein soll. Kann sie dann @Transactional für private Methoden verwenden?
Lilie
Ja, es funktioniert mit privaten Methoden und ohne Schnittstellen! Solange AspectJ richtig konfiguriert ist, garantiert es grundsätzlich Dekoratoren für Arbeitsmethoden. Und user536161 wies in seiner Antwort darauf hin, dass es sogar bei Selbstaufrufen funktionieren wird. Es ist wirklich cool und nur ein kleines bisschen beängstigend.
James Watkins
12

Wenn Sie eine private Methode in eine Transaktion einschließen müssen und aspectj nicht verwenden möchten, können Sie TransactionTemplate verwenden .

@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(){
        //...
    }

}
Loonis
quelle
Gut, um die TransactionTemplateVerwendung zu zeigen , aber bitte rufen Sie diese zweite Methode auf ..RequiresTransactionund nicht ..InTransaction. Nennen Sie Dinge immer so, wie Sie sie ein Jahr später lesen möchten. Ich würde auch argumentieren, wenn es wirklich eine zweite private Methode erfordert: Entweder den Inhalt direkt in die anonyme executeImplementierung einfügen oder wenn dies chaotisch wird, könnte dies ein Hinweis darauf sein, die Implementierung in einen anderen Dienst aufzuteilen, den Sie dann mit Anmerkungen versehen können @Transactional.
stuck
@Stuck, die 2. Methode ist zwar nicht notwendig, beantwortet aber die ursprüngliche Frage, wie eine Spring-Transaktion auf eine private Methode
angewendet
Ja, ich habe die Antwort bereits positiv bewertet, wollte aber einige Zusammenhänge und Gedanken darüber teilen, wie sie angewendet werden soll, da ich aus architektonischer Sicht denke, dass diese Situation ein potenzieller Hinweis auf einen Konstruktionsfehler ist.
Stuck
5

Spring Docs erklären das

Im Proxy-Modus (Standardeinstellung) werden nur externe Methodenaufrufe abgefangen, die über den Proxy eingehen. Dies bedeutet, dass der Selbstaufruf einer Methode innerhalb des Zielobjekts, die eine andere Methode des Zielobjekts aufruft, zur Laufzeit nicht zu einer tatsächlichen Transaktion führt, selbst wenn die aufgerufene Methode mit @Transactional markiert ist.

Ziehen Sie die Verwendung des AspectJ-Modus in Betracht (siehe Modusattribut in der folgenden Tabelle), wenn Sie erwarten, dass Selbstaufrufe auch mit Transaktionen abgeschlossen werden. In diesem Fall gibt es überhaupt keinen Proxy. Stattdessen wird die Zielklasse gewebt (dh ihr Bytecode wird geändert), um @Transactional bei jeder Art von Methode in Laufzeitverhalten umzuwandeln.

Ein anderer Weg ist der Benutzer BeanSelfAware

user536161
quelle
Könnten Sie einen Verweis auf hinzufügen BeanSelfAware? Es sieht nicht aus wie eine Frühlingsklasse
wie
@asgs Angenommen, es geht um Selbstinjektion (stellen Sie einer Bean eine Instanz von sich selbst zur Verfügung, die in einen Proxy eingewickelt ist). Beispiele finden Sie unter stackoverflow.com/q/3423972/355438 .
Lu55
1

Genauso wie @loonis vorgeschlagen hat , TransactionTemplate zu verwenden, kann diese Hilfskomponente (Kotlin) verwendet werden:

@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()
    }
}

Verwendung:

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

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

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

Ich weiß nicht, ob TransactionTemplatevorhandene Transaktionen wiederverwendet werden oder nicht, aber dieser Code tut es definitiv.

Lu55
quelle