• 事务工作单元
    • 保存一个实体
    • 保存多个实体
    • 新增实体
    • 更新实体
    • 删除实体
    • 刷新实体
    • 手工启动事务 beginTransaction
    • 执行失败事务回滚 rollBack
    • 事务包裹在闭包中 transaction
    • 事务包裹在闭包中失败回滚 transaction
    • 设置根实体 setRootEntity
    • 更改数据库连接 setConnect
    • 无实体执行 flush 什么都不做
    • 实体实体支持缓存
    • 重新保存已删除的实体实体
    • 注册更新的实体不能重新被创建
    • 注册删除的实体不能重新被创建
    • 注册替换的实体不能重新被创建
    • 不能多次创建同一个实体

    事务工作单元

    用事务工作单元更好地处理数据库相关工作。

    引入相关类

    • use Leevel\Database\Ddd\Entity;
    • use Leevel\Database\Ddd\IUnitOfWork;
    • use Leevel\Database\Ddd\UnitOfWork;
    • use Tests\Database\DatabaseTestCase as TestCase;
    • use Tests\Database\Ddd\Entity\CompositeId;
    • use Tests\Database\Ddd\Entity\Guestbook;
    • use Tests\Database\Ddd\Entity\GuestbookRepository;
    • use Tests\Database\Ddd\Entity\Relation\Post;
    • use Throwable;

      保存一个实体

    1. public function testBaseUse()
    2. {
    3. $work = UnitOfWork::make();
    4. $this->assertInstanceof(UnitOfWork::class, $work);
    5. $this->assertInstanceof(IUnitOfWork::class, $work);
    6. $post = new Post([
    7. 'title' => 'hello world',
    8. 'user_id' => 1,
    9. 'summary' => 'post summary',
    10. ]);
    11. $this->assertNull($post->id);
    12. $work->persist($post);
    13. $work->flush();
    14. $this->assertSame('1', $post->id);
    15. $this->assertSame('1', $post['id']);
    16. $this->assertSame('1', $post->getId());
    17. $this->assertSame(1, $post->userId);
    18. $this->assertSame('post summary', $post->summary);
    19. }

    ::: tip
    通过 persist 方法保存一个实体,并通过 flush 将实体持久化到数据库。
    :::

    保存多个实体

    1. public function testPersist()
    2. {
    3. $work = UnitOfWork::make();
    4. $this->assertInstanceof(UnitOfWork::class, $work);
    5. $this->assertInstanceof(IUnitOfWork::class, $work);
    6. $post = new Post([
    7. 'title' => 'hello world',
    8. 'user_id' => 1,
    9. 'summary' => 'post summary',
    10. ]);
    11. $this->assertNull($post->id);
    12. $post2 = new Post([
    13. 'title' => 'hello world',
    14. 'user_id' => 2,
    15. 'summary' => 'foo bar',
    16. ]);
    17. $this->assertNull($post2->id);
    18. $work->persist($post);
    19. $work->persist($post2);
    20. $work->flush();
    21. $this->assertSame('1', $post->id);
    22. $this->assertSame('1', $post['id']);
    23. $this->assertSame('1', $post->getId());
    24. $this->assertSame(1, $post->userId);
    25. $this->assertSame('post summary', $post->summary);
    26. $this->assertSame('2', $post2->id);
    27. $this->assertSame('2', $post2['id']);
    28. $this->assertSame('2', $post2->getId());
    29. $this->assertSame(2, $post2->userId);
    30. $this->assertSame('foo bar', $post2->summary);
    31. }

    ::: tip
    底层会开启一个事务,只有全部保存成功才会真正持久化到数据库。
    :::

    新增实体

    1. public function testCreate()
    2. {
    3. $work = UnitOfWork::make();
    4. $this->assertInstanceof(UnitOfWork::class, $work);
    5. $this->assertInstanceof(IUnitOfWork::class, $work);
    6. $post = new Post([
    7. 'title' => 'hello world',
    8. 'user_id' => 1,
    9. 'summary' => 'post summary',
    10. ]);
    11. $post2 = new Post([
    12. 'title' => 'hello world',
    13. 'user_id' => 2,
    14. 'summary' => 'foo bar',
    15. ]);
    16. $this->assertNull($post->id);
    17. $this->assertNull($post2->id);
    18. $this->assertFalse($work->created($post));
    19. $this->assertFalse($work->created($post2));
    20. $this->assertFalse($work->registered($post));
    21. $this->assertFalse($work->registered($post2));
    22. $work->create($post);
    23. $work->create($post2);
    24. $this->assertTrue($work->created($post));
    25. $this->assertTrue($work->created($post2));
    26. $this->assertTrue($work->registered($post));
    27. $this->assertTrue($work->registered($post2));
    28. $work->flush();
    29. $this->assertFalse($work->created($post));
    30. $this->assertFalse($work->created($post2));
    31. $this->assertFalse($work->registered($post));
    32. $this->assertFalse($work->registered($post2));
    33. $this->assertSame('1', $post->id);
    34. $this->assertSame('1', $post['id']);
    35. $this->assertSame('1', $post->getId());
    36. $this->assertSame(1, $post->userId);
    37. $this->assertSame('post summary', $post->summary);
    38. $this->assertSame('2', $post2->id);
    39. $this->assertSame('2', $post2['id']);
    40. $this->assertSame('2', $post2->getId());
    41. $this->assertSame(2, $post2->userId);
    42. $this->assertSame('foo bar', $post2->summary);
    43. }

    ::: tip
    底层执行的是 insert 语句,只有全部保存成功才会真正持久化到数据库。
    :::

    更新实体

    1. public function testUpdate()
    2. {
    3. $work = UnitOfWork::make();
    4. $this->assertInstanceof(UnitOfWork::class, $work);
    5. $this->assertInstanceof(IUnitOfWork::class, $work);
    6. $connect = $this->createDatabaseConnect();
    7. $this->assertSame('1', $connect->
    8. table('post')->
    9. insert([
    10. 'title' => 'hello world',
    11. 'user_id' => 1,
    12. 'summary' => 'post summary',
    13. ]));
    14. $this->assertSame('2', $connect->
    15. table('post')->
    16. insert([
    17. 'title' => 'hello world',
    18. 'user_id' => 2,
    19. 'summary' => 'foo bar',
    20. ]));
    21. $post = Post::find(1);
    22. $post2 = Post::find(2);
    23. $this->assertInstanceof(Entity::class, $post);
    24. $this->assertInstanceof(Entity::class, $post2);
    25. $this->assertInstanceof(Post::class, $post);
    26. $this->assertInstanceof(Post::class, $post2);
    27. $this->assertSame('1', $post->id);
    28. $this->assertSame('1', $post['id']);
    29. $this->assertSame('1', $post->getId());
    30. $this->assertSame('1', $post->userId);
    31. $this->assertSame('post summary', $post->summary);
    32. $this->assertSame('hello world', $post->title);
    33. $this->assertSame('2', $post2->id);
    34. $this->assertSame('2', $post2['id']);
    35. $this->assertSame('2', $post2->getId());
    36. $this->assertSame('2', $post2->userId);
    37. $this->assertSame('foo bar', $post2->summary);
    38. $this->assertSame('hello world', $post2->title);
    39. $this->assertFalse($work->updated($post));
    40. $this->assertFalse($work->updated($post2));
    41. $this->assertFalse($work->registered($post));
    42. $this->assertFalse($work->registered($post2));
    43. $post->title = 'new post title';
    44. $post->summary = 'new post summary';
    45. $post2->title = 'new post2 title';
    46. $post2->summary = 'new post2 summary';
    47. $work->update($post);
    48. $work->update($post2);
    49. $this->assertTrue($work->updated($post));
    50. $this->assertTrue($work->updated($post2));
    51. $this->assertTrue($work->registered($post));
    52. $this->assertTrue($work->registered($post2));
    53. $work->flush();
    54. $this->assertFalse($work->updated($post));
    55. $this->assertFalse($work->updated($post2));
    56. $this->assertFalse($work->registered($post));
    57. $this->assertFalse($work->registered($post2));
    58. $this->assertSame('1', $post->id);
    59. $this->assertSame('1', $post['id']);
    60. $this->assertSame('1', $post->getId());
    61. $this->assertSame('1', $post->userId);
    62. $this->assertSame('new post title', $post->title);
    63. $this->assertSame('new post summary', $post->summary);
    64. $this->assertSame('2', $post2->id);
    65. $this->assertSame('2', $post2['id']);
    66. $this->assertSame('2', $post2->getId());
    67. $this->assertSame('2', $post2->userId);
    68. $this->assertSame('new post2 title', $post2->title);
    69. $this->assertSame('new post2 summary', $post2->summary);
    70. }

    ::: tip
    底层执行的是 update 语句,只有全部保存成功才会真正持久化到数据库。
    :::

    删除实体

    1. public function testDelete()
    2. {
    3. $work = UnitOfWork::make();
    4. $this->assertInstanceof(UnitOfWork::class, $work);
    5. $this->assertInstanceof(IUnitOfWork::class, $work);
    6. $connect = $this->createDatabaseConnect();
    7. $this->assertSame('1', $connect->
    8. table('post')->
    9. insert([
    10. 'title' => 'hello world',
    11. 'user_id' => 1,
    12. 'summary' => 'post summary',
    13. ]));
    14. $this->assertSame('2', $connect->
    15. table('post')->
    16. insert([
    17. 'title' => 'hello world',
    18. 'user_id' => 2,
    19. 'summary' => 'foo bar',
    20. ]));
    21. $post = Post::find(1);
    22. $post2 = Post::find(2);
    23. $this->assertInstanceof(Entity::class, $post);
    24. $this->assertInstanceof(Entity::class, $post2);
    25. $this->assertInstanceof(Post::class, $post);
    26. $this->assertInstanceof(Post::class, $post2);
    27. $this->assertSame('1', $post->id);
    28. $this->assertSame('1', $post['id']);
    29. $this->assertSame('1', $post->getId());
    30. $this->assertSame('1', $post->userId);
    31. $this->assertSame('post summary', $post->summary);
    32. $this->assertSame('hello world', $post->title);
    33. $this->assertSame('2', $post2->id);
    34. $this->assertSame('2', $post2['id']);
    35. $this->assertSame('2', $post2->getId());
    36. $this->assertSame('2', $post2->userId);
    37. $this->assertSame('foo bar', $post2->summary);
    38. $this->assertSame('hello world', $post2->title);
    39. $this->assertFalse($work->deleted($post));
    40. $this->assertFalse($work->deleted($post2));
    41. $this->assertFalse($work->registered($post));
    42. $this->assertFalse($work->registered($post2));
    43. $work->delete($post);
    44. $work->delete($post2);
    45. $this->assertTrue($work->deleted($post));
    46. $this->assertTrue($work->deleted($post2));
    47. $this->assertTrue($work->registered($post));
    48. $this->assertTrue($work->registered($post2));
    49. $work->flush();
    50. $this->assertFalse($work->deleted($post));
    51. $this->assertFalse($work->deleted($post2));
    52. $this->assertFalse($work->registered($post));
    53. $this->assertFalse($work->registered($post2));
    54. $postAfter = Post::find(1);
    55. $post2After = Post::find(2);
    56. $this->assertNull($postAfter->id);
    57. $this->assertNull($postAfter['id']);
    58. $this->assertNull($postAfter->getId());
    59. $this->assertNull($postAfter->userId);
    60. $this->assertNull($postAfter->title);
    61. $this->assertNull($postAfter->summary);
    62. $this->assertNull($post2After->id);
    63. $this->assertNull($post2After['id']);
    64. $this->assertNull($post2After->getId());
    65. $this->assertNull($post2After->userId);
    66. $this->assertNull($post2After->title);
    67. $this->assertNull($post2After->summary);
    68. }

    ::: tip
    底层执行的是 delete 语句,只有全部保存成功才会真正持久化到数据库。
    :::

    刷新实体

    1. public function testRefresh()
    2. {
    3. $work = UnitOfWork::make();
    4. $this->assertInstanceof(UnitOfWork::class, $work);
    5. $this->assertInstanceof(IUnitOfWork::class, $work);
    6. $connect = $this->createDatabaseConnect();
    7. $this->assertSame('1', $connect->
    8. table('post')->
    9. insert([
    10. 'title' => 'hello world',
    11. 'user_id' => 1,
    12. 'summary' => 'post summary',
    13. ]));
    14. $post = new Post([
    15. 'id' => 1,
    16. 'title' => 'old',
    17. 'summary' => 'old',
    18. ], true);
    19. $this->assertSame(1, $post->getId());
    20. $this->assertSame('old', $post->getSummary());
    21. $this->assertSame('old', $post->getTitle());
    22. $work->persist($post);
    23. $work->refresh($post);
    24. $this->assertSame('1', $post->getId());
    25. $this->assertSame('post summary', $post->getSummary());
    26. $this->assertSame('hello world', $post->getTitle());
    27. $work->flush();
    28. $post = Post::find(1);
    29. $this->assertInstanceof(Entity::class, $post);
    30. $this->assertInstanceof(Post::class, $post);
    31. $this->assertSame('1', $post->id);
    32. $this->assertSame('1', $post['id']);
    33. $this->assertSame('1', $post->getId());
    34. $this->assertSame('1', $post->userId);
    35. $this->assertSame('post summary', $post->summary);
    36. $this->assertSame('hello world', $post->title);
    37. }

    ::: tip
    底层执行的是 select 语句,这个操作会读取数据库最新信息并刷新实体的属性。
    :::

    手工启动事务 beginTransaction

    1. public function testBeginTransaction()
    2. {
    3. $work = UnitOfWork::make();
    4. $this->assertInstanceof(UnitOfWork::class, $work);
    5. $this->assertInstanceof(IUnitOfWork::class, $work);
    6. $connect = $this->createDatabaseConnect();
    7. $this->assertSame('1', $connect->
    8. table('post')->
    9. insert([
    10. 'title' => 'hello world',
    11. 'user_id' => 1,
    12. 'summary' => 'post summary',
    13. ]));
    14. $work->beginTransaction();
    15. $post = Post::find(1);
    16. $work->update($post);
    17. try {
    18. $post->title = 'new title';
    19. $work->flush();
    20. $work->commit();
    21. } catch (Throwable $e) {
    22. $work->close();
    23. $work->rollBack();
    24. }
    25. $this->assertSame('1', $post->getId());
    26. $this->assertSame('new title', $post->getTitle());
    27. }

    ::: tip
    通常来说事务工作单元会自动帮你处理事务,可以通过手工 beginTransaction,成功 commit 或者失败 rollBack,系统提供了 API 让你也手工开启事务处理。
    :::

    执行失败事务回滚 rollBack

    1. public function testFlushButRollBack()
    2. {
    3. $this->expectException(\Leevel\Database\DuplicateKeyException::class);
    4. $this->expectExceptionMessage(
    5. 'SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry \'1\' for key \'PRIMARY\''
    6. );
    7. $work = UnitOfWork::make();
    8. $post = new Post([
    9. 'id' => 1,
    10. 'title' => 'old',
    11. 'summary' => 'old',
    12. ]);
    13. $post2 = new Post([
    14. 'id' => 1,
    15. 'title' => 'old',
    16. 'summary' => 'old',
    17. ]);
    18. $work->create($post);
    19. $work->create($post2);
    20. $work->flush();
    21. }

    ::: tip
    底层会自动运行一个事务,如果执行失败自动回滚,不会更新数据库。
    :::

    事务包裹在闭包中 transaction

    1. public function testTransaction()
    2. {
    3. $work = UnitOfWork::make();
    4. $this->assertInstanceof(UnitOfWork::class, $work);
    5. $this->assertInstanceof(IUnitOfWork::class, $work);
    6. $connect = $this->createDatabaseConnect();
    7. $this->assertSame('1', $connect->
    8. table('post')->
    9. insert([
    10. 'title' => 'hello world',
    11. 'user_id' => 1,
    12. 'summary' => 'post summary',
    13. ]));
    14. $work->transaction(function ($w) {
    15. $post = Post::find(1);
    16. $w->update($post);
    17. $post->title = 'new title';
    18. });
    19. $newPost = Post::find(1);
    20. $this->assertSame('1', $newPost->getId());
    21. $this->assertSame('new title', $newPost->getTitle());
    22. }

    ::: tip
    可以将事务包裹在一个闭包中,如果执行失败自动回滚,不会更新数据库。
    :::

    事务包裹在闭包中失败回滚 transaction

    1. public function testTransactionAndRollBack()
    2. {
    3. $this->expectException(\Leevel\Database\DuplicateKeyException::class);
    4. $this->expectExceptionMessage(
    5. 'SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry \'1\' for key \'PRIMARY\''
    6. );
    7. $work = UnitOfWork::make();
    8. $this->assertInstanceof(UnitOfWork::class, $work);
    9. $this->assertInstanceof(IUnitOfWork::class, $work);
    10. $connect = $this->createDatabaseConnect();
    11. $work->transaction(function ($w) {
    12. $post = new Post([
    13. 'id' => 1,
    14. 'title' => 'old',
    15. 'summary' => 'old',
    16. ]);
    17. $post2 = new Post([
    18. 'id' => 1,
    19. 'title' => 'old',
    20. 'summary' => 'old',
    21. ]);
    22. $w->create($post);
    23. $w->create($post2);
    24. });
    25. $this->assertSame(0, $connect->table('post')->findCount());
    26. }

    ::: tip
    可以将事务包裹在一个闭包中,执行失败自动回滚测试,不会更新数据库。
    :::

    设置根实体 setRootEntity

    1. public function testSetRootEntity()
    2. {
    3. $work = UnitOfWork::make();
    4. $this->assertInstanceof(UnitOfWork::class, $work);
    5. $this->assertInstanceof(IUnitOfWork::class, $work);
    6. $connect = $this->createDatabaseConnect();
    7. $this->assertSame('1', $connect->
    8. table('post')->
    9. insert([
    10. 'title' => 'hello world',
    11. 'user_id' => 1,
    12. 'summary' => 'post summary',
    13. ]));
    14. $post = Post::find(1);
    15. $work->setRootEntity($post);
    16. $work->update($post);
    17. $post->title = 'new title';
    18. $work->flush();
    19. $this->assertSame('1', $post->getId());
    20. $this->assertSame('new title', $post->getTitle());
    21. $newPost = Post::find(1);
    22. $this->assertSame('1', $newPost->getId());
    23. $this->assertSame('new title', $newPost->getTitle());
    24. }

    ::: tip
    系统默认读取基础的数据库配置来处理数据相关信息,设置跟实体可以更改事务处理的数据库连接。
    :::

    更改数据库连接 setConnect

    1. public function testSetConnectNotFoundWillUseDefault()
    2. {
    3. $work = UnitOfWork::make();
    4. $this->assertInstanceof(UnitOfWork::class, $work);
    5. $this->assertInstanceof(IUnitOfWork::class, $work);
    6. $connect = $this->createDatabaseConnect();
    7. $this->assertSame('1', $connect->
    8. table('post')->
    9. insert([
    10. 'title' => 'hello world',
    11. 'user_id' => 1,
    12. 'summary' => 'post summary',
    13. ]));
    14. $post = Post::find(1);
    15. $work->setConnect('hello');
    16. $work->update($post);
    17. $post->title = 'new title';
    18. $work->flush();
    19. $this->assertSame('1', $post->getId());
    20. $this->assertSame('new title', $post->getTitle());
    21. $newPost = Post::find(1);
    22. $this->assertSame('1', $newPost->getId());
    23. $this->assertSame('new title', $newPost->getTitle());
    24. }

    ::: tip
    如果没有存在的连接,则会使用默认的连接。
    :::

    无实体执行 flush 什么都不做

    1. public function testFlushButNotFoundAny()
    2. {
    3. $work = UnitOfWork::make(new Post());
    4. $this->assertNull($work->flush());
    5. }

    ::: tip
    实际上什么也不会发生。
    :::

    实体实体支持缓存

    1. public function testPersistStageManagedEntityDoNothing()
    2. {
    3. $work = UnitOfWork::make();
    4. $connect = $this->createDatabaseConnect();
    5. $post = new Post([
    6. 'id' => 1,
    7. 'title' => 'old',
    8. 'summary' => 'old',
    9. ]);
    10. $work->persist($post, 'create');
    11. $work->persist($post, 'create');
    12. $work->flush();
    13. $this->assertSame(1, $connect->table('post')->findCount());
    14. }

    ::: tip
    保存两个一样的实体,第二个实体并不会被添加。
    :::

    重新保存已删除的实体实体

    1. public function testPersistStageRemovedEntity()
    2. {
    3. $work = UnitOfWork::make();
    4. $connect = $this->createDatabaseConnect();
    5. $this->assertSame('1', $connect->
    6. table('post')->
    7. insert([
    8. 'title' => 'hello world',
    9. 'user_id' => 1,
    10. 'summary' => 'post summary',
    11. ]));
    12. $post = Post::find(1);
    13. $this->assertSame('1', $post->getId());
    14. $this->assertSame('hello world', $post->getTitle());
    15. $this->assertSame('post summary', $post->getSummary());
    16. $work->delete($post);
    17. $work->persist($post);
    18. $work->flush();
    19. $this->assertSame(1, $connect->table('post')->findCount());
    20. }

    ::: tip
    这样被删除的实体并不会被删除。
    :::

    注册更新的实体不能重新被创建

    1. public function testCreateButAlreadyInUpdates()
    2. {
    3. $this->expectException(\InvalidArgumentException::class);
    4. $this->expectExceptionMessage(
    5. 'Updated entity `Tests\\Database\\Ddd\\Entity\\Relation\\Post` cannot be added for create.'
    6. );
    7. $work = UnitOfWork::make();
    8. $post = new Post(['id' => 5, 'title' => 'foo']);
    9. $work->update($post);
    10. $work->create($post);
    11. }

    注册删除的实体不能重新被创建

    1. public function testCreateButAlreadyInDeletes()
    2. {
    3. $this->expectException(\InvalidArgumentException::class);
    4. $this->expectExceptionMessage(
    5. 'Deleted entity `Tests\\Database\\Ddd\\Entity\\Relation\\Post` cannot be added for create.'
    6. );
    7. $work = UnitOfWork::make();
    8. $post = new Post(['id' => 5]);
    9. $work->delete($post);
    10. $work->create($post);
    11. }

    注册替换的实体不能重新被创建

    1. public function testCreateButAlreadyInReplaces()
    2. {
    3. $this->expectException(\InvalidArgumentException::class);
    4. $this->expectExceptionMessage(
    5. 'Replaced entity `Tests\\Database\\Ddd\\Entity\\Relation\\Post` cannot be added for create.'
    6. );
    7. $work = UnitOfWork::make();
    8. $post = new Post(['id' => 5]);
    9. $work->replace($post);
    10. $work->create($post);
    11. }

    不能多次创建同一个实体

    1. public function testCreateManyTimes()
    2. {
    3. $this->expectException(\InvalidArgumentException::class);
    4. $this->expectExceptionMessage(
    5. 'Entity `Tests\\Database\\Ddd\\Entity\\Relation\\Post` cannot be added for twice.'
    6. );
    7. $work = UnitOfWork::make();
    8. $connect = $this->createDatabaseConnect();
    9. $post = new Post(['title' => 'foo']);
    10. $work->create($post);
    11. $work->create($post);
    12. }