Composer des transactions
Combinez les effets en toute sécurité.
Composer des transactions est une leçon Scala for Backend Engineering & Functional Programming gratuite sur CoddyKit. Ceci est la leçon 4 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Scala for Backend Engineering & Functional Programming, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Scala for Backend Engineering & Functional Programming comprend 4 leçons au total.
ConnectionIO est une monade
La véritable puissance de Doobie vient du fait que ConnectionIO est une monade. Vous combinez plusieurs instructions en un programme plus vaste avec flatMap ou une compréhension for.
Tout ce que vous enchaînez ainsi s'exécute sur la même connexion, au sein d'une seule transaction.
import doobie.implicits._
val program: ConnectionIO[Long] =
for {
id <- insertUser("Ada")
_ <- insertProfile(id)
} yield idUne transaction par transact
Quel que soit le nombre d'instructions que vous composez, l'ensemble de ConnectionIO devient une seule transaction lorsque vous appelez .transact.
Si une étape échoue, la stratégie du transacteur annule tout ; si toutes réussissent, elle valide l'ensemble une seule fois à la fin.
val io: IO[Long] = program.transact(xa)Écritures atomiques en plusieurs instructions
Puisque les instructions composées partagent une transaction, vous bénéficiez gratuitement de l'atomicité. Un transfert qui débite un compte et crédite un autre est entièrement effectué, ou ne l'est pas du tout.
Il n'existe aucun état partiel si la seconde mise à jour lève une exception.
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 ()Lever et gérer les erreurs
ConnectionIO possède une instance de MonadError ; vous pouvez donc utiliser raiseError pour interrompre une transaction et déclencher son annulation.
Vous pouvez également utiliser handleErrorWith pour récupérer l'erreur, mais notez que cette récupération n'annule pas à elle seule les instructions précédentes, sauf si la transaction est annulée.
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"))
}Logique conditionnelle dans une transaction
Puisqu'il s'agit simplement d'une monade, le flux de contrôle Scala ordinaire fonctionne. Vous pouvez lire une ligne, choisir une branche en fonction de sa valeur, puis écrire en conséquence, le tout de manière atomique.
Cela réunit les règles métier et la persistance dans une unité cohérente.
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 ()Points de sauvegarde pour une annulation partielle
Pour un contrôle plus précis, Doobie expose les Savepoints JDBC via l'algèbre de connexion libre FC. Vous pouvez revenir à un point de sauvegarde sans annuler toute la transaction.
Cela permet d'utiliser des stratégies d'essai et de repli au sein d'une seule transaction externe.
import doobie.free.{connection => FC}
val withSp =
for {
sp <- FC.setSavepoint
_ <- riskyWrite.handleErrorWith(_ => FC.rollback(sp))
} yield ()Combiner requêtes et écritures
Les lectures et les écritures se combinent librement dans le même programme. Vous pouvez sélectionner l'état actuel, calculer une modification en Scala, puis la conserver, le tout sur une seule connexion.
La lecture voit les modifications non validées effectuées plus tôt dans la même transaction.
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 nRéutiliser des programmes avec traverse
Puisque les programmes sont des valeurs, vous pouvez exécuter le même programme sur une liste avec traverse de cats, en les enchaînant dans une seule transaction.
Toutes les insertions sont validées ensemble, ou aucune ne l'est.
import cats.syntax.all._
def saveAll(names: List[String]): ConnectionIO[List[Long]] =
names.traverse(insertUser)Niveaux d'isolation
Vous pouvez définir le niveau d'isolation de la transaction au sein d'un programme en utilisant l'algèbre de connexion avant d'effectuer votre travail.
Les niveaux plus stricts, comme SERIALIZABLE, empêchent les anomalies, mais peuvent amener la base de données à interrompre les transactions en conflit ; vous devez alors réessayer.
import doobie.free.{connection => FC}
import java.sql.Connection
val serializable =
FC.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE) *> programRéessayer après des échecs de sérialisation
Les nouvelles tentatives appartiennent à la couche des effets, et non à la transaction. Après .transact, vous disposez d'un IO et pouvez réessayer toute la transaction en cas d'erreur de sérialisation.
Chaque nouvelle tentative est une transaction nouvelle et indépendante.
def runWithRetry(io: IO[Unit], n: Int): IO[Unit] =
io.handleErrorWith {
case _ if n > 0 => runWithRetry(io, n - 1)
case e => IO.raiseError(e)
}Garder les transactions courtes
Ne composez dans un seul ConnectionIO que le travail qui doit être atomique. N'effectuez pas d'appels externes (HTTP ou calculs CPU longs) dans une transaction : celle-ci retient une connexion et des verrous.
Lisez, calculez à l'extérieur si possible, puis écrivez dans une transaction courte.
// good: gather input first, then one short transactional write
val write: ConnectionIO[Int] =
sql"update users set name = $name where id = $id".update.runVérification rapide
Raisonnez sur les limites des transactions.
Récapitulatif
ConnectionIO est une monade ; les compréhensions for composent donc les instructions en une transaction atomique par .transact. Les erreurs annulent tout ; raiseError interrompt volontairement la transaction.
Utilisez les points de sauvegarde pour les annulations partielles, définissez les niveaux d'isolation dans le programme, réessayez les échecs de sérialisation dans la couche des effets et gardez les transactions courtes.
Questions Fréquemment Posées
La leçon « Composer des transactions » est-elle gratuite ?
Oui — le texte complet de « Composer des transactions » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Scala for Backend Engineering & Functional Programming, passe à CoddyKit PRO. Le cours Scala for Backend Engineering & Functional Programming comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Composer des transactions » ?
Combinez les effets en toute sécurité. Tu pratiques Scala for Backend Engineering & Functional Programming avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer Scala for Backend Engineering & Functional Programming ?
Aucune expérience préalable n'est requise. Scala for Backend Engineering & Functional Programming sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 4 sur 4.
Combien de temps prend la leçon « Composer des transactions » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon Scala for Backend Engineering & Functional Programming ?
Oui. Chaque leçon Scala for Backend Engineering & Functional Programming inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Se connecter avec un transacteur
- Exécuter des requêtes
- Insertions et mises à jour
- Composer des transactions