diff --git a/examples/src/test/kotlin/SearchIndexesTest.kt b/examples/src/test/kotlin/SearchIndexesTest.kt index ea6d09c4..47b8b44f 100644 --- a/examples/src/test/kotlin/SearchIndexesTest.kt +++ b/examples/src/test/kotlin/SearchIndexesTest.kt @@ -54,7 +54,6 @@ class SearchIndexesTest { } @Ignore - @Test fun multipleSearchIndexTest() = runBlocking { // :snippet-start: multi-search-index-create val searchIdxMdl = SearchIndexModel( diff --git a/examples/src/test/kotlin/TransactionsTest.kt b/examples/src/test/kotlin/TransactionsTest.kt new file mode 100644 index 00000000..dfd758bd --- /dev/null +++ b/examples/src/test/kotlin/TransactionsTest.kt @@ -0,0 +1,86 @@ +import com.mongodb.kotlin.client.coroutine.MongoClient +import com.mongodb.client.model.Filters.eq +import com.mongodb.client.model.Updates.inc +import config.getConfig +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class TransactionsTest { + + // :snippet-start: data-class + data class Account( + val accountId: String, + val amount: Int + ) + // :snippet-end: + + companion object { + val config = getConfig() + val client = MongoClient.create(config.connectionUri) + val database = client.getDatabase("bank") + + @BeforeAll + @JvmStatic + fun beforeAll() { + runBlocking { + val savAcct = Account("9876", 900) + database.getCollection("savings_accounts").insertOne(savAcct) + + val chkAcct = Account("9876", 50) + database.getCollection("checking_accounts").insertOne(chkAcct) + } + } + + @AfterAll + @JvmStatic + fun afterAll() { + runBlocking { + database.drop() + client.close() + } + } + } + + @Test + fun transactionTest() = runBlocking { + // :snippet-start: transaction-function + // Set up the session + val session = client.startSession() + + try { + session.startTransaction() + + val savingsColl = database + .getCollection("savings_accounts") + val checkingColl = database + .getCollection("checking_accounts") + + savingsColl.findOneAndUpdate( + session, + eq(Account::accountId.name, "9876"), + inc(Account::amount.name, -100), + ) + + checkingColl.findOneAndUpdate( + session, + eq(Account::accountId.name, "9876"), + inc(Account::amount.name, 100) + ) + + // Commit the transaction + val result = session.commitTransaction() + println("Transaction committed.") + assertNotNull(result) // :remove: + } catch (error: Exception) { + println("An error occurred during the transaction: ${error.message}") + // Abort the transaction + session.abortTransaction() + } + // :snippet-end: + } +} diff --git a/snooty.toml b/snooty.toml index 16fdb108..85b277f7 100644 --- a/snooty.toml +++ b/snooty.toml @@ -21,6 +21,7 @@ driver-short = "Kotlin driver" driver-long = "MongoDB Kotlin Driver" version = "5.1" full-version = "{+version+}.2" +language = "Kotlin" mdb-server = "MongoDB server" kotlin-docs = "https://kotlinlang.org" diff --git a/source/examples/generated/TransactionsTest.snippet.data-class.kt b/source/examples/generated/TransactionsTest.snippet.data-class.kt new file mode 100644 index 00000000..45f6c152 --- /dev/null +++ b/source/examples/generated/TransactionsTest.snippet.data-class.kt @@ -0,0 +1,4 @@ +data class Account( + val accountId: String, + val amount: Int +) diff --git a/source/examples/generated/TransactionsTest.snippet.transaction-function.kt b/source/examples/generated/TransactionsTest.snippet.transaction-function.kt new file mode 100644 index 00000000..2ce7b0b5 --- /dev/null +++ b/source/examples/generated/TransactionsTest.snippet.transaction-function.kt @@ -0,0 +1,31 @@ +// Set up the session +val session = client.startSession() + +try { + session.startTransaction() + + val savingsColl = database + .getCollection("savings_accounts") + val checkingColl = database + .getCollection("checking_accounts") + + savingsColl.findOneAndUpdate( + session, + eq(Account::accountId.name, "9876"), + inc(Account::amount.name, -100), + ) + + checkingColl.findOneAndUpdate( + session, + eq(Account::accountId.name, "9876"), + inc(Account::amount.name, 100) + ) + + // Commit the transaction + val result = session.commitTransaction() + println("Transaction committed.") +} catch (error: Exception) { + println("An error occurred during the transaction: ${error.message}") + // Abort the transaction + session.abortTransaction() +} diff --git a/source/fundamentals.txt b/source/fundamentals.txt index 1ef41c30..a44f2708 100644 --- a/source/fundamentals.txt +++ b/source/fundamentals.txt @@ -19,6 +19,7 @@ Fundamentals /fundamentals/aggregation /fundamentals/aggregation-expression-operations /fundamentals/indexes + /fundamentals/transactions /fundamentals/collations /fundamentals/logging /fundamentals/monitoring diff --git a/source/fundamentals/transactions.txt b/source/fundamentals/transactions.txt new file mode 100644 index 00000000..0656b5cd --- /dev/null +++ b/source/fundamentals/transactions.txt @@ -0,0 +1,130 @@ +.. _kotlin-fundamentals-transactions: + +============ +Transactions +============ + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: modify, customize + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to use the {+driver-short+} to perform +**transactions**. :manual:`Transactions ` allow +you to run a series of operations that do not change any data until the +transaction is committed. If any operation in the transaction returns an +error, the driver cancels the transaction and discards all data changes +before they ever become visible. + +In MongoDB, transactions run within logical **sessions**. A +:manual:`session ` is a grouping of related +read or write operations that you intend to run sequentially. Sessions +enable :manual:`causal consistency +` for a +group of operations or allow you to execute operations in an +:website:`ACID transaction `. MongoDB +guarantees that the data involved in your transaction operations remains +consistent, even if the operations encounter unexpected errors. + +When using the {+driver-short+}, you can create a new session from a +``MongoClient`` instance as a ``ClientSession``. We recommend that you reuse +your client for multiple sessions and transactions instead of +instantiating a new client each time. + +.. warning:: + + Use a ``ClientSession`` only with the ``MongoClient`` (or associated + ``MongoDatabase`` or ``MongoCollection``) that created it. Using a + ``ClientSession`` with a different ``MongoClient`` results in operation + errors. + +Methods +------- + +Create a ``ClientSession`` by using the ``startSession()`` method on your +``Client`` instance. You can then modify the session state by using the +following methods: + +.. list-table:: + :widths: 25 75 + :header-rows: 1 + + * - Method + - Description + + * - ``startTransaction()`` + - | Starts a new transaction for this session with the + default transaction options. You cannot start a + transaction if there's already an active transaction + on the session. + | + | To set transaction options, use ``startTransaction(transactionOptions: TransactionOptions)``. + + * - ``abortTransaction()`` + - | Ends the active transaction for this session. Returns an error + if there is no active transaction for the + session or the transaction was previously ended. + + * - ``commitTransaction()`` + - | Commits the active transaction for this session. Returns an + error if there is no active transaction for the session or if the + transaction was ended. + +A ``ClientSession`` also has methods to retrieve session properties and modify +mutable session properties. View the `API documentation <{+api+}/apidocs/mongodb-driver-kotlin-coroutine/mongodb-driver-kotlin-coroutine/com.mongodb.kotlin.client.coroutine/-client-session/index.html>`__ +to learn more about these methods. + +Example +------- + +This example uses the following {+language+} data class to model its documents: + +.. literalinclude:: /examples/generated/TransactionsTest.snippet.data-class.kt + :language: kotlin + +The following example demonstrates how you can create a session, create a transaction, +and commit changes to existing documents: + +1. Create a session from the client using the ``startSession()`` method. +#. Use the ``startTransaction()`` method to start a transaction. +#. Update the specified documents, then use the ``commitTransaction()`` method if all + operations succeed, or ``abortTransaction()`` if any operations fail. + +.. literalinclude:: /examples/generated/TransactionsTest.snippet.transaction-function.kt + :language: kotlin + +Additional Information +---------------------- + +To learn more about the concepts mentioned in this guide, see the following pages in +the Server manual: + +- :manual:`Transactions ` +- :manual:`Server Sessions ` +- :manual:`Read Isolation, Consistency, and Recency ` + +To learn more about ACID compliance, see the :website:`What are ACID +Properties in Database Management Systems? ` +article on the MongoDB website. + +API Documentation +~~~~~~~~~~~~~~~~~ + +To learn more about any of the types or methods discussed in this +guide, see the following API Documentation: + +- `ClientSession <{+api+}/apidocs/mongodb-driver-kotlin-coroutine/mongodb-driver-kotlin-coroutine/com.mongodb.kotlin.client.coroutine/-client-session/index.html>`__ +- `startTransaction <{+api+}/apidocs/mongodb-driver-kotlin-coroutine/mongodb-driver-kotlin-coroutine/com.mongodb.kotlin.client.coroutine/-client-session/start-transaction.html>`__ +- `commitTransaction <{+api+}/apidocs/mongodb-driver-kotlin-coroutine/mongodb-driver-kotlin-coroutine/com.mongodb.kotlin.client.coroutine/-client-session/commit-transaction.html>`__ +- `abortTransaction <{+api+}/apidocs/mongodb-driver-kotlin-coroutine/mongodb-driver-kotlin-coroutine/com.mongodb.kotlin.client.coroutine/-client-session/abort-transaction.html>`__