0Pricing
Scala for Backend Engineering & Functional Programming · 강의

트랜잭션 조합하기

효과를 안전하게 결합해 보세요.

트랜잭션 조합하기은(는) CoddyKit의 무료 Scala for Backend Engineering & Functional Programming 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Scala for Backend Engineering & Functional Programming 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Scala for Backend Engineering & Functional Programming 강의에는 총 4개의 강의가 포함되어 있습니다.

ConnectionIO는 모나드입니다

Doobie의 진정한 강점은 ConnectionIO가 모나드라는 점입니다. flatMap이나 for 내포를 사용해 여러 문을 하나의 더 큰 프로그램으로 결합할 수 있습니다.

이 방식으로 순서대로 실행하는 모든 작업은 하나의 트랜잭션 안에서 같은 연결을 사용합니다.

import doobie.implicits._

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

transact당 하나의 트랜잭션

몇 개의 문을 조합했는지와 관계없이 .transact를 호출하면 전체 ConnectionIO가 하나의 트랜잭션이 됩니다.

어느 단계에서든 실패하면 Transactor의 전략이 모든 작업을 롤백하고, 모두 성공하면 마지막에 한 번 커밋합니다.

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

여러 문을 사용하는 원자적 쓰기

조합된 문이 하나의 트랜잭션을 공유하므로 별도의 작업 없이 원자성을 얻을 수 있습니다. 한 계정에서 debit하고 다른 계정에 credit하는 transfer는 전부 실행되거나 전혀 실행되지 않습니다.

두 번째 업데이트에서 오류가 발생해도 부분적으로만 반영된 상태는 남지 않습니다.

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

오류 발생 및 처리

ConnectionIO에는 MonadError 인스턴스가 있으므로 raiseError로 트랜잭션을 중단하고 롤백을 발생시킬 수 있습니다.

또한 handleErrorWith로 복구할 수 있지만, 트랜잭션이 롤백되지 않는 한 복구하는 것만으로 이전 문이 되돌려지지는 않는다는 점에 유의하세요.

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"))
  }

트랜잭션에서 조건부 로직 사용하기

그저 모나드이므로 일반적인 Scala 제어 흐름이 작동합니다. 행을 읽고 값에 따라 분기한 다음 그에 맞게 쓰는 작업을 모두 원자적으로 수행할 수 있습니다.

이를 통해 비즈니스 규칙과 영속성을 하나의 일관된 단위로 유지할 수 있습니다.

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

부분 롤백을 위한 세이브포인트

더 세밀하게 제어하려면 Doobie가 FC(free connection) 대수를 통해 JDBC Savepoint를 제공합니다. 전체 트랜잭션을 중단하지 않고 세이브포인트까지 롤백할 수 있습니다.

이를 통해 하나의 바깥쪽 트랜잭션 안에서 시도 및 대체 패턴을 사용할 수 있습니다.

import doobie.free.{connection => FC}

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

쿼리와 쓰기 결합하기

같은 프로그램에서 읽기와 쓰기를 자유롭게 섞을 수 있습니다. 현재 상태를 조회하고 Scala에서 변경 사항을 계산한 뒤 하나의 연결에서 모두 저장할 수 있습니다.

읽기 작업에서는 같은 트랜잭션에서 앞서 수행한 커밋되지 않은 변경 사항도 볼 수 있습니다.

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

traverse로 프로그램 재사용하기

프로그램은 값이므로 cats의 traverse를 사용해 목록 전체에 동일한 프로그램을 실행하고, 이를 하나의 트랜잭션에서 순서대로 처리할 수 있습니다.

모든 삽입이 함께 커밋되거나, 하나도 커밋되지 않습니다.

import cats.syntax.all._

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

격리 수준

작업을 수행하기 전에 연결 대수를 사용해 프로그램 내부에서 트랜잭션 격리 수준을 설정할 수 있습니다.

SERIALIZABLE 같은 더 엄격한 수준은 이상 현상을 방지하지만 데이터베이스가 충돌하는 트랜잭션을 중단하게 만들 수 있으므로 다시 시도해야 합니다.

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

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

직렬화 실패 시 다시 시도하기

재시도는 트랜잭션이 아니라 효과 계층에 두어야 합니다. .transact 뒤에는 IO가 있으므로 직렬화 오류가 발생하면 전체 트랜잭션을 다시 시도할 수 있습니다.

각 재시도는 새롭고 독립적인 트랜잭션입니다.

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

트랜잭션을 짧게 유지하기

원자적으로 처리해야 하는 작업만 하나의 ConnectionIO에 조합하세요. 트랜잭션 안에서 외부 호출(HTTP, 느린 CPU 작업)을 수행하지 마세요. 연결과 잠금을 계속 점유하기 때문입니다.

가능하다면 먼저 읽고 트랜잭션 밖에서 계산한 다음, 짧은 트랜잭션에서 쓰기 작업을 수행하세요.

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

빠른 확인

트랜잭션 경계를 파악해 보세요.

복습

ConnectionIO는 모나드이므로 for 내포가 문들을 하나의 원자적 트랜잭션으로 조합하며, .transact마다 하나의 트랜잭션이 만들어집니다. 오류가 발생하면 모든 작업이 롤백되고, raiseError는 의도적으로 중단합니다.

부분 롤백에는 세이브포인트를 사용하고, 프로그램 안에서 격리 수준을 설정하며, 효과 계층에서 직렬화 실패를 다시 시도하고, 트랜잭션은 짧게 유지하세요.

자주 묻는 질문

“트랜잭션 조합하기” 강의는 무료인가요?

네 — “트랜잭션 조합하기” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Scala for Backend Engineering & Functional Programming 강의 전체를 잠금 해제할 수 있습니다. Scala for Backend Engineering & Functional Programming 강의에는 총 4개의 강의가 포함되어 있습니다.

“트랜잭션 조합하기”에서 뭘 배우나요?

효과를 안전하게 결합해 보세요. 브라우저에서 직접 실행하는 실습 코드로 Scala for Backend Engineering & Functional Programming을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Scala for Backend Engineering & Functional Programming을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Scala for Backend Engineering & Functional Programming은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.

“트랜잭션 조합하기” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Scala for Backend Engineering & Functional Programming 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Scala for Backend Engineering & Functional Programming 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. Transactor로 연결하기
  2. 쿼리 실행하기
  3. 삽입과 갱신
  4. 트랜잭션 조합하기
← Scala for Backend Engineering & Functional Programming(으)로 돌아가기