Scala mongodb事务:如何回滚?

时间:2019-04-29 10:21:46

标签: mongodb scala transactions

有一个Scala mongodb事务示例:

https://github.com/mongodb/mongo-scala-driver/blob/r2.4.0/driver/src/it/scala/org/mongodb/scala/DocumentationTransactionsExampleSpec.scala

但是尚不清楚在失败的情况下如何回滚事务。

这是我从官方示例中复制的代码,但进行了一些修改以使事务在第二次插入中失败(插入2个具有相同ID的文档),但是问题是第一个文档被保留了,我需要进行整个事务要回滚。

import org.mongodb.scala._
import scala.concurrent.Await
import scala.concurrent.duration.Duration

object Application extends App {

  val mongoClient: MongoClient = MongoClient("mongodb://localhost:27018")

  val database = mongoClient.getDatabase("hr")
  val employeesCollection = database.getCollection("employees")

  // Implicit functions that execute the Observable and return the results
  val waitDuration = Duration(5, "seconds")
  implicit class ObservableExecutor[T](observable: Observable[T]) {
    def execute(): Seq[T] = Await.result(observable.toFuture(), waitDuration)
  }

  implicit class SingleObservableExecutor[T](observable: SingleObservable[T]) {
    def execute(): T = Await.result(observable.toFuture(), waitDuration)
  }


  updateEmployeeInfoWithRetry(mongoClient).execute()

  Thread.sleep(3000)

  /// -------------------------


  def updateEmployeeInfo(database: MongoDatabase, observable: SingleObservable[ClientSession]): SingleObservable[ClientSession] = {
    observable.map(clientSession => {
      val eventsCollection = database.getCollection("events")

      val transactionOptions = TransactionOptions.builder().readConcern(ReadConcern.SNAPSHOT).writeConcern(WriteConcern.MAJORITY).build()
      clientSession.startTransaction(transactionOptions)

      eventsCollection.insertOne(clientSession, Document("_id" -> "123", "employee" -> 3, "status" -> Document("new" -> "Inactive", "old" -> "Active")))
        .subscribe((res: Completed) => println(res))

      // THIS SHOULD FAIL, SINCE  THERE IS ALREADY DOCUMENT WITH ID = 123, but PREVIOUS OPERATION SHOULD BE ALSO ROLLED BACK.
      // I COULD NOT FIND THE WAY HOW TO ROLLBACK WHOLE TRANSACTION IF ONE OF OPERATIONS FAILED
      eventsCollection.insertOne(clientSession, Document("_id" -> "123", "employee" -> 3, "status" -> Document("new" -> "Inactive", "old" -> "Active")))
        .subscribe((res: Completed) => println(res))
      // I'VE TRIED VARIOUS THINGS (INCLUDING CODE BELOW)
//        .subscribe(new Observer[Completed] {
//          override def onNext(result: Completed): Unit = println("onNext")
//
//          override def onError(e: Throwable): Unit = clientSession.abortTransaction()
//
//          override def onComplete(): Unit = println("complete")
//        })
      clientSession
    })
  }

  def commitAndRetry(observable: SingleObservable[Completed]): SingleObservable[Completed] = {
    observable.recoverWith({
      case e: MongoException if e.hasErrorLabel(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL) => {
        println("UnknownTransactionCommitResult, retrying commit operation ...")
        commitAndRetry(observable)
      }
      case e: Exception => {
        println(s"Exception during commit ...: $e")
        throw e
      }
    })
  }

  def runTransactionAndRetry(observable: SingleObservable[Completed]): SingleObservable[Completed] = {
    observable.recoverWith({
      case e: MongoException if e.hasErrorLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL) => {
        println("TransientTransactionError, aborting transaction and retrying ...")
        runTransactionAndRetry(observable)
      }
    })
  }

  def updateEmployeeInfoWithRetry(client: MongoClient): SingleObservable[Completed] = {

    val database = client.getDatabase("hr")
    val updateEmployeeInfoObservable: Observable[ClientSession] = updateEmployeeInfo(database, client.startSession())
    val commitTransactionObservable: SingleObservable[Completed] =
      updateEmployeeInfoObservable.flatMap(clientSession => clientSession.commitTransaction())
    val commitAndRetryObservable: SingleObservable[Completed] = commitAndRetry(commitTransactionObservable)

    runTransactionAndRetry(commitAndRetryObservable)
  }
}

如果任何操作失败,如何回滚整个事务?

1 个答案:

答案 0 :(得分:0)

来自Scala驱动程序的源代码,位于https://github.com/mongodb/mongo-scala-driver/blob/r2.6.0/driver/src/main/scala/org/mongodb/scala/ClientSessionImplicits.scala

似乎与abortTransaction()一起定义了commitTransaction()方法。

在另一个说明中,如果MongoDB 4.0中的单个副本集事务在60秒内未提交(可配置),它将自动中止。在MongoDB Multi-Document ACID Transactions博客文章中:

  

默认情况下,MongoDB将自动中止运行超过60秒的任何多文档事务。请注意,如果对服务器的写入量很少,则可以灵活地调整事务以延长执行时间。