-
Notifications
You must be signed in to change notification settings - Fork 1.5k
DOCSP-35968: Transactions docs #2847
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
ccho-mongodb
merged 15 commits into
mongodb:4.1
from
ccho-mongodb:DOCSP-35968-transactions
Apr 15, 2024
Merged
Changes from 13 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
f45455b
DOCSP-35968: Transactions docs
024cbfe
apply phpcbf formatting
ccho-mongodb d8cc216
add on this page, fix rst
7d7becd
fix test
24e4c90
fix tests
07df2ab
apply phpcbf formatting
ccho-mongodb fc6d22f
reference code blocks
ffc4e95
fix for DOCSP-38411: compatibility ref
4caeb1f
change code boundary names
6773a82
apply phpcbf formatting
ccho-mongodb 98c7afe
add emphasize lines
1792004
updates, grammar fixes
9f6dbf0
list intro update
c17db6f
PRR fixes
2adbc4b
fix elemmatch link
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Models; | ||
|
||
use MongoDB\Laravel\Eloquent\Model; | ||
|
||
class Account extends Model | ||
{ | ||
protected $connection = 'mongodb'; | ||
protected $fillable = ['number', 'balance']; | ||
} |
144 changes: 144 additions & 0 deletions
144
docs/includes/fundamentals/transactions/TransactionsTest.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Http\Controllers; | ||
|
||
use App\Models\Account; | ||
use Illuminate\Support\Facades\DB; | ||
use MongoDB\Laravel\Tests\TestCase; | ||
|
||
class TransactionsTest extends TestCase | ||
{ | ||
/** | ||
* @runInSeparateProcess | ||
* @preserveGlobalState disabled | ||
*/ | ||
public function testTransactionCallback(): void | ||
{ | ||
require_once __DIR__ . '/Account.php'; | ||
|
||
Account::truncate(); | ||
|
||
Account::insert([ | ||
[ | ||
'number' => 223344, | ||
'balance' => 5000, | ||
], | ||
[ | ||
'number' => 776655, | ||
'balance' => 100, | ||
], | ||
]); | ||
|
||
// begin transaction callback | ||
DB::transaction(function () { | ||
$transferAmount = 200; | ||
|
||
$sender = Account::where('number', 223344)->first(); | ||
$sender->balance -= $transferAmount; | ||
$sender->save(); | ||
|
||
$receiver = Account::where('number', 776655)->first(); | ||
$receiver->balance += $transferAmount; | ||
$receiver->save(); | ||
}); | ||
// end transaction callback | ||
|
||
$sender = Account::where('number', 223344)->first(); | ||
$receiver = Account::where('number', 776655)->first(); | ||
|
||
$this->assertEquals(4800, $sender->balance); | ||
$this->assertEquals(300, $receiver->balance); | ||
} | ||
|
||
public function testTransactionCommit(): void | ||
{ | ||
require_once __DIR__ . '/Account.php'; | ||
|
||
Account::truncate(); | ||
|
||
Account::insert([ | ||
[ | ||
'number' => 223344, | ||
'balance' => 5000, | ||
], | ||
[ | ||
'number' => 776655, | ||
'balance' => 100, | ||
], | ||
]); | ||
|
||
// begin commit transaction | ||
DB::beginTransaction(); | ||
$oldAccount = Account::where('number', 223344)->first(); | ||
|
||
$newAccount = Account::where('number', 776655)->first(); | ||
$newAccount->balance += $oldAccount->balance; | ||
$newAccount->save(); | ||
|
||
$oldAccount->delete(); | ||
DB::commit(); | ||
// end commit transaction | ||
|
||
$acct1 = Account::where('number', 223344)->first(); | ||
$acct2 = Account::where('number', 776655)->first(); | ||
|
||
$this->assertNull($acct1); | ||
$this->assertEquals(5100, $acct2->balance); | ||
} | ||
|
||
public function testTransactionRollback(): void | ||
{ | ||
require_once __DIR__ . '/Account.php'; | ||
|
||
Account::truncate(); | ||
Account::insert([ | ||
[ | ||
'number' => 223344, | ||
'balance' => 200, | ||
], | ||
[ | ||
'number' => 776655, | ||
'balance' => 0, | ||
], | ||
[ | ||
'number' => 990011, | ||
'balance' => 0, | ||
], | ||
]); | ||
|
||
// begin rollback transaction | ||
DB::beginTransaction(); | ||
|
||
$sender = Account::where('number', 223344)->first(); | ||
$receiverA = Account::where('number', 776655)->first(); | ||
$receiverB = Account::where('number', 990011)->first(); | ||
|
||
$amountA = 100; | ||
$amountB = 200; | ||
|
||
$sender->balance -= $amountA; | ||
$receiverA->balance += $amountA; | ||
|
||
$sender->balance -= $amountB; | ||
$receiverB->balance += $amountB; | ||
|
||
if ($sender->balance < 0) { | ||
// insufficient balance, roll back the transaction | ||
DB::rollback(); | ||
} else { | ||
DB::commit(); | ||
} | ||
|
||
// end rollback transaction | ||
|
||
$sender = Account::where('number', 223344)->first(); | ||
$receiverA = Account::where('number', 776655)->first(); | ||
$receiverB = Account::where('number', 990011)->first(); | ||
|
||
$this->assertEquals(200, $sender->balance); | ||
$this->assertEquals(0, $receiverA->balance); | ||
$this->assertEquals(0, $receiverB->balance); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,71 +9,148 @@ Transactions | |
:values: tutorial | ||
|
||
.. meta:: | ||
:keywords: php framework, odm, code example | ||
:keywords: php framework, odm, rollback, commit, callback, code example, acid, atomic, consistent, isolated, durable | ||
|
||
MongoDB transactions require the following software and topology: | ||
.. contents:: On this page | ||
:local: | ||
:backlinks: none | ||
:depth: 2 | ||
:class: singlecol | ||
|
||
Overview | ||
-------- | ||
|
||
In this guide, you can learn how to perform a **transaction** in MongoDB by | ||
using the {+odm-long+}. Transactions let you run a sequence of write operations | ||
that update the data only after the transaction is committed. | ||
|
||
If the transaction fails, the PHP library that manages MongoDB operations | ||
for {+odm-short+} ensures that MongoDB discards all the changes made within | ||
the transaction before they become visible. This property of transactions | ||
that ensures that all changes within a transaction are either applied or | ||
discarded is called **atomicity**. | ||
|
||
MongoDB performs write operations on single documents atomically. If you | ||
need atomicity in write operations on multiple documents or data consistency | ||
across multiple documents for your operations, run them in a multi-document | ||
transaction. | ||
|
||
Multi-document transactions are **ACID compliant** because MongoDB | ||
guarantees that the data in your transaction operations remains consistent, | ||
even if the driver encounters unexpected errors. | ||
|
||
Learn how to perform transactions in the following sections of this guide: | ||
|
||
- :ref:`laravel-transaction-requirements` | ||
- :ref:`laravel-transaction-callback` | ||
- :ref:`laravel-transaction-commit` | ||
- :ref:`laravel-transaction-rollback` | ||
|
||
To learn more about transactions in MongoDB, see :manual:`Transactions </core/transactions/>` | ||
in the {+server-docs-name+}. | ||
|
||
.. _laravel-transaction-requirements: | ||
|
||
Requirements and Limitations | ||
---------------------------- | ||
|
||
To perform transactions in MongoDB, you must be using the following MongoDB | ||
GromNaN marked this conversation as resolved.
Show resolved
Hide resolved
|
||
version and topology: | ||
|
||
- MongoDB version 4.0 or later | ||
- A replica set deployment or sharded cluster | ||
|
||
You can find more information :manual:`in the MongoDB docs </core/transactions/>` | ||
MongoDB server and {+odm-short+} have the following limitations: | ||
|
||
.. code-block:: php | ||
- In MongoDB versions 4.2 and earlier, write operations performed within a | ||
transaction must be on existing collections. In MongoDB versions 4.4 and | ||
later, the server automatically creates collections as necessary when | ||
you perform write operations in a transaction. To learn more about this | ||
limitation, see :manual:`Create Collections and Indexes in a Transaction </core/transactions/#create-collections-and-indexes-in-a-transaction>` | ||
in the {+server-docs-name+}. | ||
- MongoDB does not support nested transactions. If you attempt to start a | ||
transaction within another one, the extension raises a ``RuntimeException``. | ||
To learn more about this limitation, see :manual:`Transactions and Sessions </core/transactions/#transactions-and-sessions>` | ||
in the {+server-docs-name+}. | ||
- The {+odm-long+} does not support the database testing traits | ||
``Illuminate\Foundation\Testing\DatabaseTransactions`` and ``Illuminate\Foundation\Testing\RefreshDatabase``. | ||
As a workaround, you can create migrations with the ``Illuminate\Foundation\Testing\DatabaseMigrations`` | ||
trait to reset the database after each test. | ||
|
||
DB::transaction(function () { | ||
User::create(['name' => 'john', 'age' => 19, 'title' => 'admin', 'email' => '[email protected]']); | ||
DB::collection('users')->where('name', 'john')->update(['age' => 20]); | ||
DB::collection('users')->where('name', 'john')->delete(); | ||
}); | ||
.. _laravel-transaction-callback: | ||
|
||
.. code-block:: php | ||
Run a Transaction in a Callback | ||
------------------------------- | ||
|
||
This section shows how you can run a transaction as a callback. | ||
GromNaN marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
When using this method of running a transaction, all the code in the | ||
callback method runs as a single transaction. | ||
|
||
// begin a transaction | ||
DB::beginTransaction(); | ||
User::create(['name' => 'john', 'age' => 19, 'title' => 'admin', 'email' => '[email protected]']); | ||
DB::collection('users')->where('name', 'john')->update(['age' => 20]); | ||
DB::collection('users')->where('name', 'john')->delete(); | ||
In the following example, the transaction consists of write operations that | ||
transfer the funds from a bank account, represented by the ``Account`` model, | ||
to another account: | ||
|
||
// commit changes | ||
DB::commit(); | ||
.. literalinclude:: /includes/fundamentals/transactions/TransactionsTest.php | ||
:language: php | ||
:dedent: | ||
:start-after: begin transaction callback | ||
:end-before: end transaction callback | ||
|
||
To abort a transaction, call the ``rollBack`` method at any point during the transaction: | ||
You can optionally pass the maximum number of times to retry a failed transaction as the second parameter as shown in the following code example: | ||
|
||
.. code-block:: php | ||
:emphasize-lines: 4 | ||
|
||
DB::beginTransaction(); | ||
User::create(['name' => 'john', 'age' => 19, 'title' => 'admin', 'email' => '[email protected]']); | ||
DB::transaction(function() { | ||
// transaction code | ||
}, | ||
retries: 5, | ||
); | ||
|
||
// Abort the transaction, discarding any data created as part of it | ||
DB::rollBack(); | ||
.. _laravel-transaction-commit: | ||
|
||
Begin and Commit a Transaction | ||
------------------------------ | ||
|
||
.. note:: | ||
This section shows how to start and commit a transaction. | ||
|
||
Transactions in MongoDB cannot be nested. DB::beginTransaction() function | ||
will start new transactions in a new created or existing session and will | ||
raise the RuntimeException when transactions already exist. See more in | ||
MongoDB official docs :manual:`Transactions and Sessions </core/transactions/#transactions-and-sessions>`. | ||
To use this method of starting and committing a transaction, call the | ||
``DB::beginTransaction()`` to start the transaction. Then, call the | ||
GromNaN marked this conversation as resolved.
Show resolved
Hide resolved
|
||
``DB::commit()`` method to end the transaction, which applies all the updates | ||
performed within the transaction. | ||
|
||
.. code-block:: php | ||
In the following example, the balance from the first account is moved to the | ||
second account, after which the first account is deleted: | ||
|
||
DB::beginTransaction(); | ||
User::create(['name' => 'john', 'age' => 20, 'title' => 'admin']); | ||
.. literalinclude:: /includes/fundamentals/transactions/TransactionsTest.php | ||
:language: php | ||
:dedent: | ||
:emphasize-lines: 1,9 | ||
:start-after: begin commit transaction | ||
:end-before: end commit transaction | ||
|
||
// This call to start a nested transaction will raise a RuntimeException | ||
DB::beginTransaction(); | ||
DB::collection('users')->where('name', 'john')->update(['age' => 20]); | ||
DB::commit(); | ||
DB::rollBack(); | ||
.. _laravel-transaction-rollback: | ||
|
||
Database Testing | ||
---------------- | ||
Roll Back a Transaction | ||
----------------------- | ||
|
||
For testing, the traits ``Illuminate\Foundation\Testing\DatabaseTransactions`` | ||
and ``Illuminate\Foundation\Testing\RefreshDatabase`` are not yet supported. | ||
Instead, create migrations and use the ``DatabaseMigrations`` trait to reset | ||
the database after each test: | ||
This section shows how to roll back a transaction. A rollback reverts all the | ||
write operations performed within that transaction. This means that the | ||
data is reverted to its state before the transaction. | ||
|
||
.. code-block:: php | ||
To perform the roll back, call the ``DB::rollback()`` function anytime before | ||
GromNaN marked this conversation as resolved.
Show resolved
Hide resolved
|
||
the transaction is committed. | ||
|
||
In the following example, the transaction consists of write operations that | ||
transfer funds from one account, represented by the ``Account`` model, to | ||
multiple other accounts. If the sender account has insufficient funds, the | ||
transaction is rolled back, and none of the models are updated: | ||
|
||
.. literalinclude:: /includes/fundamentals/transactions/TransactionsTest.php | ||
:language: php | ||
:dedent: | ||
:emphasize-lines: 1,18,20 | ||
:start-after: begin rollback transaction | ||
:end-before: end rollback transaction | ||
|
||
use Illuminate\Foundation\Testing\DatabaseMigrations; |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.