Fixed bug that increment/decrement does not works as expect when used in transaction. (#3598)

This commit is contained in:
李铭昕 2021-05-18 21:00:26 +08:00 committed by GitHub
parent 6f66dbcffd
commit 6d97e438c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 117 additions and 2 deletions

View File

@ -1,5 +1,9 @@
# v2.1.18 - TBD
## Fixed
- [#3598](https://github.com/hyperf/hyperf/pull/3598) Fixed bug that `increment/decrement` does not works as expect when used in transaction for model-cache.
# v2.1.17 - 2021-05-17
## Fixed

View File

@ -78,7 +78,9 @@ trait Cacheable
{
$res = parent::increment($column, $amount, $extra);
if ($res > 0) {
if (empty($extra)) {
if ($this->getConnection()->transactionLevel() && $this instanceof CacheableInterface) {
InvalidCacheManager::instance()->push($this);
} elseif (empty($extra)) {
// Only increment a column's value.
/** @var Manager $manager */
$manager = $this->getContainer()->get(Manager::class);
@ -101,7 +103,9 @@ trait Cacheable
{
$res = parent::decrement($column, $amount, $extra);
if ($res > 0) {
if (empty($extra)) {
if ($this->getConnection()->transactionLevel() && $this instanceof CacheableInterface) {
InvalidCacheManager::instance()->push($this);
} elseif (empty($extra)) {
// Only decrement a column's value.
/** @var Manager $manager */
$manager = $this->getContainer()->get(Manager::class);

View File

@ -12,10 +12,13 @@ declare(strict_types=1);
namespace HyperfTest\ModelCache;
use Hyperf\Database\Model\Relations\Relation;
use Hyperf\DbConnection\Db;
use Hyperf\DbConnection\Listener\InitTableCollectorListener;
use Hyperf\ModelCache\EagerLoad\EagerLoader;
use Hyperf\ModelCache\InvalidCacheManager;
use Hyperf\ModelCache\Listener\EagerLoadListener;
use Hyperf\Redis\RedisProxy;
use Hyperf\Utils\Reflection\ClassInvoker;
use HyperfTest\ModelCache\Stub\BookModel;
use HyperfTest\ModelCache\Stub\ContainerStub;
use HyperfTest\ModelCache\Stub\ImageModel;
@ -381,4 +384,104 @@ class ModelCacheTest extends TestCase
BookModel::findFromCache(1);
$this->assertSame(100, $redis->ttl('{mc:default:m:book}:id:1'));
}
public function testModelSaveInTransaction()
{
$container = ContainerStub::mockContainer();
$id = 209;
UserModel::query()->firstOrCreate(['id' => $id], [
'name' => uniqid(),
'gender' => 1,
]);
$redis = $container->make(RedisProxy::class, ['pool' => 'default']);
wait(function () use ($redis, $id) {
Db::beginTransaction();
try {
$model = UserModel::findFromCache($id);
/* @var \Redis $redis */
$this->assertEquals(1, $redis->exists('{mc:default:m:user}:id:' . $id));
$model->gender = 2;
$model->save();
$this->assertEquals(1, $redis->hGet('{mc:default:m:user}:id:' . $id, 'gender'));
$invoker = new ClassInvoker(InvalidCacheManager::instance());
$this->assertSame(1, count($invoker->models));
Db::commit();
} catch (\Throwable $exception) {
Db::rollBack();
}
});
$this->assertSame(0, $redis->exists('{mc:default:m:user}:id:' . $id));
UserModel::query(true)->where('id', $id)->delete();
}
public function testModelIncrInTransaction()
{
$container = ContainerStub::mockContainer();
$id = 209;
UserModel::query()->firstOrCreate(['id' => $id], [
'name' => uniqid(),
'gender' => 1,
]);
$redis = $container->make(RedisProxy::class, ['pool' => 'default']);
wait(function () use ($redis, $id) {
Db::beginTransaction();
try {
$model = UserModel::findFromCache($id);
/* @var \Redis $redis */
$this->assertEquals(1, $redis->exists('{mc:default:m:user}:id:' . $id));
$model->increment('gender');
$this->assertEquals(1, $redis->hGet('{mc:default:m:user}:id:' . $id, 'gender'));
$invoker = new ClassInvoker(InvalidCacheManager::instance());
$this->assertSame(1, count($invoker->models));
Db::commit();
} catch (\Throwable $exception) {
Db::rollBack();
}
});
$this->assertSame(0, $redis->exists('{mc:default:m:user}:id:' . $id));
UserModel::query(true)->where('id', $id)->delete();
}
public function testModelDecrInTransaction()
{
$container = ContainerStub::mockContainer();
$id = 209;
UserModel::query()->firstOrCreate(['id' => $id], [
'name' => uniqid(),
'gender' => 1,
]);
$redis = $container->make(RedisProxy::class, ['pool' => 'default']);
wait(function () use ($redis, $id) {
Db::beginTransaction();
try {
$model = UserModel::findFromCache($id);
/* @var \Redis $redis */
$this->assertEquals(1, $redis->exists('{mc:default:m:user}:id:' . $id));
$model->decrement('gender');
$this->assertEquals(1, $redis->hGet('{mc:default:m:user}:id:' . $id, 'gender'));
$invoker = new ClassInvoker(InvalidCacheManager::instance());
$this->assertSame(1, count($invoker->models));
Db::commit();
} catch (\Throwable $exception) {
Db::rollBack();
}
});
$this->assertSame(0, $redis->exists('{mc:default:m:user}:id:' . $id));
UserModel::query(true)->where('id', $id)->delete();
}
}

View File

@ -22,6 +22,7 @@ use Hyperf\Database\Model\Events\Deleted;
use Hyperf\Database\Model\Events\Saved;
use Hyperf\DbConnection\Collector\TableCollector;
use Hyperf\DbConnection\ConnectionResolver;
use Hyperf\DbConnection\Db;
use Hyperf\DbConnection\Frequency;
use Hyperf\DbConnection\Pool\DbPool;
use Hyperf\DbConnection\Pool\PoolFactory;
@ -42,6 +43,7 @@ use Hyperf\Redis\Pool\RedisPool;
use Hyperf\Redis\RedisProxy;
use Hyperf\Utils\ApplicationContext;
use Hyperf\Utils\Packer\PhpSerializerPacker;
use Hyperf\Utils\Waiter;
use Mockery;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Log\LogLevel;
@ -168,6 +170,8 @@ class ContainerStub
$container->shouldReceive('get')->with(Manager::class)->andReturn(new Manager($container));
$container->shouldReceive('get')->with(PhpSerializerPacker::class)->andReturn(new PhpSerializerPacker());
$container->shouldReceive('get')->with(EagerLoader::class)->andReturn(new EagerLoader());
$container->shouldReceive('get')->with(Waiter::class)->andReturn(new Waiter());
$container->shouldReceive('get')->with(Db::class)->andReturn(new Db($container));
return $container;
}
}