0Pricing
Scala for Backend Engineering & Functional Programming · Lektion

Transaktionen kombinieren

Kombinieren Sie Effekte sicher.

Transaktionen kombinieren ist eine kostenlose Scala for Backend Engineering & Functional Programming-Lektion auf CoddyKit. Dies ist Lektion 4 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des Scala for Backend Engineering & Functional Programming-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Scala for Backend Engineering & Functional Programming-Kurs umfasst insgesamt 4 Lektionen.

ConnectionIO ist eine Monade

Die eigentliche Stärke von Doobie besteht darin, dass ConnectionIO eine Monade ist. Sie kombinieren mehrere Anweisungen mit flatMap oder einer For-Comprehension zu einem größeren Programm.

Alles, was Sie auf diese Weise sequenzieren, läuft innerhalb einer Transaktion auf derselben Verbindung.

import doobie.implicits._

val program: ConnectionIO[Long] =
  for {
    id <- insertUser("Ada")
    _  <- insertProfile(id)
  } yield id

Eine Transaktion pro transact

Unabhängig davon, wie viele Anweisungen Sie kombinieren: Das gesamte ConnectionIO wird zu einer einzigen Transaktion, wenn Sie .transact aufrufen.

Wenn ein Schritt fehlschlägt, macht die Strategie des Transactors alles rückgängig. Wenn alle Schritte erfolgreich sind, wird am Ende einmalig ein Commit ausgeführt.

val io: IO[Long] = program.transact(xa)

Atomare Schreibvorgänge mit mehreren Anweisungen

Da kombinierte Anweisungen dieselbe Transaktion verwenden, erhalten Sie Atomarität automatisch. Eine Überweisung, die ein Konto belastet und ein anderes Konto gutschreibt, wird entweder vollständig ausgeführt oder gar nicht.

Wenn das zweite Update eine Ausnahme auslöst, bleibt kein teilweise aktualisierter Zustand zurück.

def transfer(from: Long, to: Long, cents: Int) =
  for {
    _ <- sql"update acct set bal = bal - $cents where id = $from".update.run
    _ <- sql"update acct set bal = bal + $cents where id = $to".update.run
  } yield ()

Fehler auslösen und behandeln

ConnectionIO verfügt über eine MonadError-Instanz. Daher können Sie mit raiseError eine Transaktion abbrechen und ein Rollback auslösen.

Mit handleErrorWith können Sie sich auch von einem Fehler erholen. Beachten Sie jedoch, dass die Wiederherstellung allein vorherige Anweisungen nicht rückgängig macht, solange kein Rollback der Transaktion erfolgt.

import cats.syntax.all._
import doobie.free.connection.{raiseError, pure}

def debit(id: Long, c: Int): ConnectionIO[Unit] =
  sql"update acct set bal = bal - $c where id = $id".update.run.flatMap {
    case 1 => pure(())
    case _ => raiseError(new RuntimeException("no such account"))
  }

Bedingte Logik in einer Transaktion

Da es sich lediglich um eine Monade handelt, funktioniert gewöhnlicher Scala-Kontrollfluss. Sie können eine Zeile lesen, abhängig von ihrem Wert verzweigen und entsprechend schreiben – alles atomar.

So bleiben Geschäftsregeln und Persistenz in einer konsistenten Einheit verbunden.

for {
  bal <- sql"select bal from acct where id = $id".query[Int].unique
  _   <- if (bal >= amt)
           sql"update acct set bal = bal - $amt where id = $id".update.run
         else raiseError(new RuntimeException("insufficient funds"))
} yield ()

Savepoints für teilweises Rollback

Für eine feinere Kontrolle stellt Doobie JDBC-Savepoints über die FC-Algebra (freie Verbindungsalgebra) bereit. Sie können zu einem Savepoint zurückrollen, ohne die gesamte Transaktion abzubrechen.

Dadurch werden Try-/Fallback-Muster innerhalb einer äußeren Transaktion ermöglicht.

import doobie.free.{connection => FC}

val withSp =
  for {
    sp <- FC.setSavepoint
    _  <- riskyWrite.handleErrorWith(_ => FC.rollback(sp))
  } yield ()

Abfragen und Schreibvorgänge kombinieren

Lesen und Schreiben lassen sich frei im selben Programm kombinieren. Sie können den aktuellen Zustand abfragen, eine Änderung in Scala berechnen und sie anschließend speichern – alles auf einer Verbindung.

Die Abfrage sieht nicht bestätigte Änderungen, die zuvor in derselben Transaktion vorgenommen wurden.

for {
  v <- sql"select stock from items where id = $id".query[Int].unique
  _ <- sql"update items set stock = ${v - 1} where id = $id".update.run
  n <- sql"select stock from items where id = $id".query[Int].unique
} yield n

Programme mit traverse wiederverwenden

Da Programme Werte sind, können Sie dasselbe Programm mit dem traverse von cats über eine Liste ausführen und die Ausführungen in einer einzigen Transaktion sequenzieren.

Alle INSERTs werden gemeinsam bestätigt oder keines davon.

import cats.syntax.all._

def saveAll(names: List[String]): ConnectionIO[List[Long]] =
  names.traverse(insertUser)

Isolationsstufen

Sie können die Transaktionsisolationsstufe innerhalb eines Programms mithilfe der Verbindungsalgebra festlegen, bevor Sie Ihre Arbeit ausführen.

Strengere Stufen wie SERIALIZABLE verhindern Anomalien, können die Datenbank jedoch dazu bringen, konkurrierende Transaktionen abzubrechen. In diesem Fall sollten Sie die Transaktion wiederholen.

import doobie.free.{connection => FC}
import java.sql.Connection

val serializable =
  FC.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE) *> program

Bei Serialisierungsfehlern wiederholen

Wiederholungen gehören in die Effekt-Schicht, nicht in die Transaktion. Nach .transact verfügen Sie über ein IO und können bei einem Serialisierungsfehler die gesamte Transaktion wiederholen.

Jeder Wiederholungsversuch ist eine neue, unabhängige Transaktion.

def runWithRetry(io: IO[Unit], n: Int): IO[Unit] =
  io.handleErrorWith {
    case _ if n > 0 => runWithRetry(io, n - 1)
    case e          => IO.raiseError(e)
  }

Transaktionen kurz halten

Kombinieren Sie nur die Arbeit in einem ConnectionIO, die atomar sein muss. Führen Sie innerhalb einer Transaktion keine externen Aufrufe wie HTTP-Anfragen oder langsame CPU-Berechnungen aus; dadurch werden eine Verbindung und Sperren gehalten.

Lesen Sie, berechnen Sie nach Möglichkeit außerhalb der Transaktion und schreiben Sie anschließend in einer kurzen Transaktion.

// good: gather input first, then one short transactional write
val write: ConnectionIO[Int] =
  sql"update users set name = $name where id = $id".update.run

Schnelltest

Beurteilen Sie die Grenzen von Transaktionen.

Zusammenfassung

ConnectionIO ist eine Monade. Daher kombinieren For-Comprehensions Anweisungen pro .transact zu einer atomaren Transaktion. Fehler machen alles rückgängig; raiseError bricht eine Transaktion gezielt ab.

Verwenden Sie Savepoints für teilweise Rollbacks, legen Sie Isolationsstufen im Programm fest, wiederholen Sie Serialisierungsfehler in der Effekt-Schicht und halten Sie Transaktionen kurz.

Häufig gestellte Fragen

Ist die Lektion „Transaktionen kombinieren“ kostenlos?

Ja — der vollständige Text von „Transaktionen kombinieren“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des Scala for Backend Engineering & Functional Programming-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der Scala for Backend Engineering & Functional Programming-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „Transaktionen kombinieren“?

Kombinieren Sie Effekte sicher. Du übst Scala for Backend Engineering & Functional Programming mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um Scala for Backend Engineering & Functional Programming zu starten?

Keine Vorkenntnisse erforderlich. Scala for Backend Engineering & Functional Programming auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 4 von 4.

Wie lange dauert die Lektion „Transaktionen kombinieren“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser Scala for Backend Engineering & Functional Programming-Lektion Code schreiben und ausführen?

Ja. Jede Scala for Backend Engineering & Functional Programming-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. Mit einem Transactor verbinden
  2. Abfragen ausführen
  3. Einfügen und aktualisieren
  4. Transaktionen kombinieren
← Zurück zu Scala for Backend Engineering & Functional Programming