Fehlerszenario der Spring-Transaktion (Anmerkung @Transactional).

1. Proxy wird nicht wirksam

Die Annotationsanalyse in Spring basiert auf Proxys. Wenn die Zielmethode nicht von Spring proxiert werden kann, kann sie nicht von Spring verwaltet werden.

Es gibt zwei Möglichkeiten für Spring, Proxys zu generieren:
Schnittstellenbasierte dynamische JDK-Proxys, die erfordern, dass die Ziel-Proxy-Klasse eine Schnittstelle implementiert, um
CGLIB-Proxys basierend auf der Implementierung von Zielklassen-Unterklassen zu implementieren

1.1 Kommentieren Sie die Anmerkung zur Schnittstellenmethode

@Transactional unterstützt Anmerkungen zu Methoden und Klassen. Wenn die Proxy-Methode der entsprechenden Schnittstellenimplementierungsklasse nach der Markierung auf der Schnittstelle CGLIB ist, wird der Proxy der Zielklasse durch Generieren einer Unterklasse generiert und kann nicht in @Transactional aufgelöst werden, sodass die Transaktion wird ungültig.
Wir machen immer noch relativ wenige Fehler dieser Art. Grundsätzlich werden wir Anmerkungen zu den Methoden der Implementierungsklasse der Schnittstelle markieren, was von den Beamten nicht empfohlen wird.

1.2 Klassen oder Methoden, die durch Schlüsselwörter final und static modifiziert wurden

CGLIB generiert eine Proxy-Klasse durch Generieren einer Unterklasse der Zielklasse.Nachdem sie durch final und static modifiziert wurde, kann die Methode nicht inihrer Proxy-Klasse umgeschrieben werden, um Transaktionsfunktionen hinzuzufügen.

1.3 Interne Aufrufe von Klassenmethoden

Wenn es innerhalb der Methode aufgerufen wird, wird die Proxy-Logik nicht durchlaufen, also wird es nicht aufgerufen.

Beispiel

@Service
public class UserService {
    
    

    @Autowired
    private UserMapper userMapper;

  
    public void add(UserModel userModel) {
    
    
        userMapper.insertUser(userModel);
        updateStatus(userModel);
    }

    @Transactional
    public void updateStatus(UserModel userModel) {
    
    
        doSameThing();
    }
}

Lösung: Fügen Sie eine neue Dienstmethode hinzu

@Servcie
public class ServiceA {
    
    
   @Autowired
   prvate ServiceB serviceB;

   public void save(User user) {
    
    
         queryData1();
         queryData2();
         serviceB.doSave(user);
   }
 }

 @Servcie
 public class ServiceB {
    
    

    @Transactional(rollbackFor=Exception.class)
    public void doSave(User user) {
    
    
       addData1();
       updateData2();
    }

 }

1.4 (die Klasse selbst) wird von spring nicht verwaltet

In unserem üblichen Entwicklungsprozess gibt es ein Detail, das leicht übersehen wird. Das heißt, die Voraussetzung für die Verwendung von Spring-Transaktionen ist: Das Objekt muss von Spring verwaltet werden, und eine Bean-Instanz muss erstellt werden.
Normalerweise können wir die Funktionen der Bean-Instanziierung und Abhängigkeitsinjektion durch @Controller, @Service, @Component, @Repository und andere Annotationen automatisch realisieren.
Wie unten gezeigt, wurde eine Service-Klasse entwickelt, aber ich habe vergessen, die Annotation @Service hinzuzufügen, wie zum Beispiel:

//@Service
public class UserService {
    
    

    @Transactional
    public void add(UserModel userModel) {
    
    
         saveData(userModel);
         updateData(userModel);
    }    
}

Aus dem obigen Beispiel können wir sehen, dass die UserService-Klasse nicht mit @Service annotiert ist, dann wird die Klasse nicht an das Spring-Management übergeben, sodass ihre Add-Methode keine Transaktionen generiert.

2. Funktionen, die vom Framework oder der zugrunde liegenden Schicht nicht unterstützt werden

2.1 Die Methode der nicht öffentlichen Änderung

Wenn die benutzerdefinierte Transaktionsmethode (d. h. die Zielmethode) andere Zugriffsrechte als öffentlich, aber privat, standardmäßig oder geschützt hat, stellt Spring keine Transaktionsfunktionen bereit.

Wie wir alle wissen, gibt es vier Haupttypen von Zugriffsberechtigungen für Java: privat, standardmäßig, geschützt und öffentlich, und ihre Berechtigungen nehmen von links nach rechts zu.

In der computeTransactionAttribute-Methode der AbstractFallbackTransactionAttributeSource-Klasse gibt es eine Beurteilung: Wenn die Zielmethode nicht öffentlich ist, gibt TransactionAttribute null zurück, das heißt, Transaktionen werden nicht unterstützt.

 // Don't allow no-public methods as required.可以看到, 这里不支持public类型的方法
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
    
    
   return null;
}

2.2 Multi-Thread-Aufruf

Beispiel 1

Der Haupt-Thread A ruft Thread B auf, um die Daten mit der ID 1 zu speichern, und dann wartet der Haupt-Thread A darauf, dass Thread B die Ausführung beendet, und fragt dann die Daten mit der ID 1 durch Thread A ab.
Zu diesem Zeitpunkt werden Sie feststellen, dass die Daten mit der ID 1 nicht im Hauptthread A abgefragt werden können. Da sich diese beiden Threads in verschiedenen Spring-Transaktionen befinden, führen sie im Wesentlichen dazu, dass sie in verschiedenen Transaktionen in Mysql vorhanden sind.
MySQL verwendet MVCC, um sicherzustellen, dass der Thread beim Lesen des Snapshots nur Daten liest, die kleiner sind als die aktuelle Transaktionsnummer. Offensichtlich ist die Transaktionsnummer von Thread B größer als die von Thread A, sodass die Daten nicht abgefragt werden können.

Beispiel 2

@Slf4j
@Service
public class UserService {
    
    

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleService roleService;

    @Transactional
    public void add(UserModel userModel) throws Exception {
    
    
        userMapper.insertUser(userModel);
        new Thread(() -> {
    
    
            roleService.doOtherThing();
        }).start();
    }
}

@Service
public class RoleService {
    
    

    @Transactional
    public void doOtherThing() {
    
    
        System.out.println("保存role表数据");
    }
}

Aus dem obigen Beispiel können wir sehen, dass in der Transaktionsmethode add die Transaktionsmethode doOtherThing aufgerufen wird, aber die Transaktionsmethode doOtherThing in einem anderen Thread aufgerufen wird.

Dies führt dazu, dass sich die beiden Methoden nicht im selben Thread befinden und die erhaltenen Datenbankverbindungen unterschiedlich sind, also zwei unterschiedliche Transaktionen. Wenn Sie eine Ausnahme in der doOtherThing-Methode auslösen möchten, ist es für die add-Methode unmöglich, einen Rollback durchzuführen.

Wenn Sie den Quellcode von Spring-Transaktionen gesehen haben, wissen Sie vielleicht, dass Spring-Transaktionen über Datenbankverbindungen implementiert werden. Eine Zuordnung wird im aktuellen Thread gespeichert, der Schlüssel ist die Datenquelle und der Wert ist die Datenbankverbindung.

Dieselbe Transaktion, über die wir sprechen, bezieht sich tatsächlich auf dieselbe Datenbankverbindung.Nur dieselbe Datenbankverbindung kann gleichzeitig festgeschrieben und zurückgesetzt werden. Wenn es sich um verschiedene Threads handelt, muss die erhaltene Datenbankverbindung unterschiedlich sein, es handelt sich also um eine andere Transaktion.

2.3 Die Datenbank selbst unterstützt keine Transaktionen

Beispielsweise unterstützt die Myisam-Speicher-Engine von Mysql keine Transaktionen, nur die InnoDB-Speicher-Engine unterstützt dies.

3. Fehlerszenario 3: Falsche Verwendung von @Transactional

Ausbreitungseigenschaften

Wenn wir die Annotation @Transactional verwenden, können wir den Weitergabeparameter angeben.
Die Funktion dieses Parameters besteht darin, die Ausbreitungsmerkmale der Transaktion anzugeben.Spring unterstützt derzeit 7 Ausbreitungsmerkmale:

ERFORDERLICH Wenn es im aktuellen Kontext eine Transaktion gibt, treten Sie der Transaktion bei, wenn keine Transaktion vorhanden ist, erstellen Sie eine Transaktion, was der Standardwert des Weitergabeattributs ist.
UNTERSTÜTZT Wenn im aktuellen Kontext eine Transaktion vorhanden ist, wird die Transaktion beim Beitritt zur Transaktion unterstützt.Wenn keine Transaktion vorhanden ist, wird sie auf nicht transaktionale Weise ausgeführt.
MANDATORY, wenn im aktuellen Kontext eine Transaktion vorhanden ist, andernfalls eine Ausnahme auslösen.
REQUIRES_NEW erstellt jedes Mal eine neue Transaktion und setzt gleichzeitig die Transaktion im Kontext aus.Nachdem die Ausführung der aktuellen neuen Transaktion abgeschlossen ist, wird die Kontexttransaktion fortgesetzt und erneut ausgeführt.
NOT_SUPPORTED Wenn es im aktuellen Kontext eine Transaktion gibt, wird die aktuelle Transaktion angehalten, und dann wird die neue Methode in einer Umgebung ohne Transaktion ausgeführt.
Löst NEVER eine Ausnahme aus, wenn im aktuellen Kontext eine Transaktion vorhanden ist, andernfalls wird Code in einer Umgebung ohne Transaktionen ausgeführt.
NESTED Wenn im aktuellen Kontext eine Transaktion vorhanden ist, wird die verschachtelte Transaktion ausgeführt, und wenn keine Transaktion vorhanden ist, wird eine neue Transaktion erstellt.

Die oben genannten Weitergabemechanismen, die keine Transaktionen unterstützen, sind: PROPAGATION_SUPPORTS, PROPAGATION_NOT_SUPPORTED, PROPAGATION_NEVER.
Diese drei Ausbreitungsmerkmale erstellen neue Transaktionen: REQUIRED, REQUIRES_NEW, NESTED.

3.1 Die Ausbreitungseigenschaften sind falsch eingestellt

Wenn wir die Ausbreitungseigenschaften falsch einstellen, wenn wir die Ausbreitungsparameter manuell einstellen, zum Beispiel:

@Service
public class UserService {
    
    

    @Transactional(propagation = Propagation.NEVER)
    public void add(UserModel userModel) {
    
    
        saveData(userModel);
        updateData(userModel);
    }
}

Wir können sehen, dass die Transaktionsweitergabefunktion der add-Methode als Propagation.NEVER definiert ist. Diese Art der Weitergabefunktion unterstützt keine Transaktionen, und es wird eine Ausnahme ausgelöst, wenn eine Transaktion vorhanden ist.

3.2 Das rollbackFor-Attribut ist falsch gesetzt

Bildbeschreibung hier einfügen

Standardmäßig setzen Transaktionen nur Laufzeitausnahmen und Fehler zurück, nicht geprüfte Ausnahmen (z. B. IOException).
Wenn daher eine IO-Ausnahme in der Methode ausgelöst wird, wird die Transaktion standardmäßig nicht zurückgesetzt.
Wir können alle Ausnahmen erfassen, indem wir @Transactional(rollbackFor = Exception.class) angeben.

3.3 Die Ausnahme wird intern abgefangen

@Slf4j
@Service
public class UserService {
    
    
    
    @Transactional
    public void add(UserModel userModel) {
    
    
        try {
    
    
            saveData(userModel);
            updateData(userModel);
        } catch (Exception e) {
    
    
            log.error(e.getMessage(), e);
        }
    }
}

In diesem Fall wird die Spring-Transaktion natürlich nicht zurückgesetzt, da der Entwickler die Ausnahme selbst abgefangen und nicht manuell geworfen hat, also die Ausnahme geschluckt hat.
Wenn Sie möchten, dass die Spring-Transaktion normal zurückgesetzt wird, müssen Sie eine Ausnahme auslösen, die sie verarbeiten kann. Wenn keine Ausnahme ausgelöst wird, betrachtet spring das Programm als normal.

3.4 Verschachtelte Transaktionen

UserService
Bildbeschreibung hier einfügen
UserService1
Bildbeschreibung hier einfügen
oben ist, dass ich UserService und UserService1 gleichzeitig zurücksetzen möchte. Es wird jedoch auch Szenarien geben, in denen Sie nur den Datenbankvorgang zurücksetzen möchten, der einen Fehler in UserService1 gemeldet hat, ohne die Datenspeicherung in der Hauptlogik UserService zu beeinträchtigen.

Es gibt zwei Möglichkeiten, die obige Logik zu implementieren:

1. Die gesamte Methode direkt in UserService1 wird mit try/catch umschlossen

2. Verwenden Sie den Propagation.REQUIRES_NEW-Propagierungsmechanismus in UserService1

Bildbeschreibung hier einfügen

Schließen Sie 12 @Transactional-Fehlerszenarien in einem Atemzug ab
12 Szenarien des Ausfalls einer Frühlingstransaktion (Anmerkung @Transactional).

おすすめ

転載: blog.csdn.net/yzx3105/article/details/130052944