mehr-it / lara-transactions
处理多个同时进行的交易,并为laravel提供通用事务接口
Requires
- php: >=7.1
- laravel/framework: ^5.8|^6.0|^7.0|^8.0
Requires (Dev)
- orchestra/testbench: ^3.8|^4.0|^5.0|^6.0
- phpunit/phpunit: ^7.4|^8.0
- yajra/laravel-oci8: ^5.8|^6.0|^7.0|^8.0
README
处理多个同时进行的交易,并为laravel提供通用事务接口。
通常,在处理数据库以确保一致性时使用事务。但此包管理任何类型的事务(不仅仅是数据库),只要实现了Transaction
接口。
安装
composer require mehr-it/lara-transactions
此包使用Laravel的包自动发现功能,因此服务提供者和别名将被自动加载。
管理事务
Transaction
外观提供了三种基本的事务方法
Transaction::begin( /* transactional entities */);
Transaction::commit();
Transaction::rollback();
这些方法的行为类似于对应的数据库操作。
您还可以使用run()
方法,该方法在一个事务中执行回调
Transaction::run( /* transactional entities */ , function() {
// put your code here
});
首先开始事务。然后执行回调,之后提交事务。如果在回调执行期间抛出异常,则回滚事务并抛出异常。
开始事务
每当您想要开始一个事务时,您首先必须列出应该为哪些实体创建事务。我们称这些为“事务实体”。在数据库事务的意义上,事务实体将是数据库连接。您可以传递连接实例或连接名称
Transaction::begin(DB::connection());
Transaction::begin('myConnection');
Transaction::begin(['myConnection', 'anotherConnection']);
Transaction::run('myConnection' , function() { /* ... */ });
如示例所示,可以将多个“事务实体”传递给单个begin()
或run()
调用。有关更多信息,请参阅以下部分 "多事务处理"。
但是,您也可以传递一个模型。模型的底层数据库连接将自动检测并为其开始事务
Transaction::begin(MyModel::class);
Transaction::begin([$user, $profile]);
Transaction::run(MyModel::class , function() { /* ... */ });
如果传递了使用同一数据库连接的多个模型,则仅开始一个事务。
事务者
有时实体不自行实现其事务。模型是一个很好的例子:它们的交易是由底层数据库连接实现的。但直接传递模型可能更方便,而不是传递使用的数据库连接。
这就是“事务者”发挥作用的地方:事务者为一个给定的实体创建必要的交易并将它们添加到管理的交易池中。对于数据库连接和模型,默认情况下可用的相应事务者是可用的。但是,您可以根据需要实现自己的。
简单实现Transactor
接口并注册您的交易者
Transaction::registerTransactor(EntityClass::class, TransactorClass::class);
多事务处理
Transaction
外观允许传递多个“事务实体”,可以同时为这些实体创建和管理多个事务。
在不同连接上的多个事务的问题在于,不能保证它们都提交或失败:它们必须逐个提交。如果一个提交成功而后续的一个失败,可能会发生不一致。您应尽可能避免多个事务。但是,现实世界的示例表明,在某些情况下这是不可避免的。考虑需要多个数据库或使用不同存储系统的应用程序。
这个问题不能解决,但我们可以尝试最小化风险。当然,每当发生不一致的提交时,我们都应报告。
我们为了最大限度地降低不一致提交的风险,会在第一次事务提交之前检查每个事务是否“活跃”。只有当没有事务看起来被破坏时,我们才会逐个提交它们。由于第一次测试的事务和最后一次提交的事务之间的时间间隔非常低,因此事务在中间被破坏的风险极低。当然,这也意味着提交操作必须快速且健壮(很少失败)。
对于数据库事务,通过执行简单的“SELECT 1”来执行“活跃”测试,以查看服务器会话是否仍然完好。
嵌套事务
当其他事务打开时,您可能开始新的事务。以下示例展示了这一点
Transaction::run([EntityA::class, EntityB::class] , function() {
// CodeBlock1
Transaction::run(EntityC::class , function() {
// CodeBlock2
});
// CodeBlock3
});
此示例按预期工作:在CodeBlock1和CodeBlock3之间建立了另一个事务。它在CodeBlock1之后开始,在达到CodeBlock3之前提交。只要所有实体都使用支持嵌套的相同底层事务实现,就没有什么需要担心的。这对于大多数数据库连接都是正确的。
但如果你使用的是EntityC的事务,它与外部实体的事务(不同的事务实现或其他数据库连接)独立,那么必须小心。外部事务的失败不会导致内部事务回滚。这是因为其底层事务并不嵌套在外部底层事务中。