43 Transaktionen in Spring Boot REST Services

Transaktionen sind ein wesentlicher Bestandteil jeder Anwendung, die mit einer Datenbank arbeitet, insbesondere in RESTful Webservices. Eine Transaktion stellt sicher, dass alle Operationen innerhalb einer Datenbanktransaktion entweder vollständig abgeschlossen oder vollständig zurückgesetzt werden, falls ein Fehler auftritt. In Spring Boot können Transaktionen automatisch mit der Annotation @Transactional verwaltet werden.

In diesem Kapitel erläutern wir die Funktionsweise von Transaktionen in Spring Boot, die Verwendung der @Transactional-Annotation und wie man mit gängigen Problemen wie Rollback und Isolation Levels umgeht.

43.1 Grundlagen der Transaktionen

Eine Transaktion ist eine Sequenz von Operationen, die als eine Einheit von Arbeit ausgeführt werden. Eine Transaktion hat die folgenden grundlegenden Eigenschaften, die oft als ACID-Prinzipien bezeichnet werden:

In Spring Boot werden Transaktionen durch das Spring Framework verwaltet, das sich um die Integration mit der darunter liegenden Datenbank kümmert.

43.2 Verwendung von @Transactional

In Spring Boot ist die häufigste Möglichkeit, Transaktionen zu verwalten, die Verwendung der Annotation @Transactional. Diese Annotation kann auf Methoden oder Klassen angewendet werden und sorgt dafür, dass alle Datenbankoperationen innerhalb einer Transaktion ausgeführt werden.

43.2.1 Beispiel: @Transactional auf einer Service-Methode

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class ProductService {

    private final ProductRepository productRepository;

    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    @Transactional
    public Product createProduct(Product product) {
        // Speichert das Produkt in der Datenbank
        return productRepository.save(product);
    }
}

In diesem Beispiel sorgt die Annotation @Transactional dafür, dass die Methode createProduct() innerhalb einer Transaktion ausgeführt wird. Sollte während der Ausführung ein Fehler auftreten, wird die Transaktion zurückgerollt und die Änderungen an der Datenbank werden verworfen.

43.3 Rollbacks bei Fehlern

Spring Boot führt automatisch ein Rollback durch, wenn eine unchecked Exception (z.B. RuntimeException) innerhalb einer @Transactional-Methode auftritt. Das bedeutet, dass alle Änderungen, die in der Transaktion gemacht wurden, rückgängig gemacht werden, wenn ein Fehler auftritt.

43.3.1 Beispiel: Rollback bei einer RuntimeException

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class ProductService {

    private final ProductRepository productRepository;

    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    @Transactional
    public Product createProduct(Product product) {
        productRepository.save(product);

        if (product.getPrice() < 0) {
            throw new IllegalArgumentException("Der Preis darf nicht negativ sein.");
        }

        return product;
    }
}

In diesem Beispiel wird das Produkt nur dann gespeichert, wenn der Preis positiv ist. Wenn der Preis negativ ist, wird eine IllegalArgumentException geworfen und die Transaktion wird zurückgerollt.

43.3.2 Rollback bei Checked Exceptions

Standardmäßig führt Spring Boot kein Rollback durch, wenn eine checked Exception (z.B. IOException) geworfen wird. Sie können jedoch explizit festlegen, dass auch bei checked Exceptions ein Rollback durchgeführt wird, indem Sie den Parameter rollbackFor in der @Transactional-Annotation verwenden.

43.3.2.1 Beispiel: Rollback für checked Exceptions

import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;

@Service
public class ProductService {

    @Transactional(rollbackFor = IOException.class)
    public Product createProduct(Product product) throws IOException {
        productRepository.save(product);

        if (product.getPrice() < 0) {
            throw new IOException("Ungültiger Preis");
        }

        return product;
    }
}

In diesem Beispiel wird ein Rollback auch dann durchgeführt, wenn eine IOException auftritt.

43.4 Transaktions-Propagation

Spring Boot bietet verschiedene Propagationsstufen für Transaktionen. Diese steuern, wie Transaktionen zwischen Methoden weitergereicht werden, insbesondere wenn eine Methode mit einer Transaktion eine andere Methode aufruft, die ebenfalls in einer Transaktion läuft.

43.4.1 Wichtige Propagation-Typen:

43.4.2 Beispiel: Verwendung von Propagation.REQUIRES_NEW

import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void processOrder(Order order) {
        // Neue Transaktion wird gestartet
        orderRepository.save(order);
    }
}

In diesem Beispiel wird die Methode processOrder() immer in einer neuen Transaktion ausgeführt, unabhängig davon, ob eine bestehende Transaktion vorhanden ist.

43.5 Isolation Levels

Das Isolation Level einer Transaktion bestimmt, wie isoliert eine Transaktion von anderen parallelen Transaktionen ist. Spring Boot unterstützt die Standard-Isolationsebenen von SQL, die festlegen, wie Daten während paralleler Transaktionen konsistent bleiben.

43.5.1 Wichtige Isolation Levels:

43.5.2 Beispiel: Verwenden von Isolation Levels

import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class ProductService {

    @Transactional(isolation = Isolation.SERIALIZABLE)
    public void updateProductStock(Long productId, int quantity) {
        // Stock aktualisieren mit hoher Isolation
        Product product = productRepository.findById(productId).orElseThrow();
        product.setStock(product.getStock() - quantity);
        productRepository.save(product);
    }
}

In diesem Beispiel verwendet die Methode updateProductStock() das SERIALIZABLE-Isolation-Level, um sicherzustellen, dass die Transaktion vollständig isoliert von anderen Transaktionen ist.

43.6 Transaktionale Methoden auf Klassenebene

Sie können @Transactional nicht nur auf Methodenebene, sondern auch auf Klassenebene verwenden. In diesem Fall gilt die Transaktion für alle öffentlichen Methoden der Klasse.

43.6.1 Beispiel: @Transactional auf Klassenebene

import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class ProductService {

    public Product createProduct(Product product) {
        return productRepository.save(product);
    }

    public void deleteProduct(Long productId) {
        productRepository.deleteById(productId);
    }
}

Alle öffentlichen Methoden in der ProductService-Klasse laufen in einer Transaktion.

43.7 tl;dr

Transaktionen sind ein grundlegendes Element jeder datenbankgestützten Anwendung, und Spring Boot macht es einfach, Transaktionen durch die Verwendung der Annotation @Transactional zu verwalten. Ob es darum geht, einfache CRUD-Operationen sicherzustellen, Rollbacks bei Fehlern durchzuführen oder mit fortgeschrittenen Themen wie Propagation und Isolation Levels umzugehen, Spring Boot bietet eine robuste Unterstützung für die Verwaltung von Transaktionen in RESTful Webservices.