【问题标题】:Laravel inserting parent child fails inside transactionLaravel插入父子在事务中失败
【发布时间】:2020-01-24 04:21:01
【问题描述】:

我正在尝试执行 2 次插入,在 2 个表中,表受外键约束。这两个操作必须在事务中执行,以防止最终失败。 (实际上我需要在更多的表上执行更多的插入操作,所以事务很重要;但本例中的 2 个表足以重现问题)

数据库驱动是pgsql

SomeRepo.php(也尝试使用事务关闭变体)

        DB::beginTransaction();
        try {
            $parentData = [
                'name' => 'Parent name'
            ];
            $parent = new Parent($parentData);
            $parent->save();

            $childData = [
                // Tried it with and without setting "parent_id" here
                'parent_id' => $parent->id,
                'name' => 'Child name'
            ];
            $child = new Child($childData);
            $parent->children()->save($child);
            DB::commit();
        } catch (Exception $e) {
            DB::rollback();
        }

Parent.php

    protected $fillable = [
        'name'
    ];

    public function children()
    {
        return $this->hasMany(Child::class);
    }

Child.php

    protected $fillable = [
        'name', 'parent_id'
    ];

尝试插入子行时执行失败,返回父 ID。

insert or update on table "child" violates foreign key constraint "child_parent_id_foreign"

编辑 子表SQL:

DROP TABLE IF EXISTS "public"."child";
CREATE TABLE "public"."child" (
  "id" int4 NOT NULL DEFAULT nextval('child_id_seq'::regclass),
  "parent_id" int4 NOT NULL,
  "is_read" bool NOT NULL DEFAULT false,
  "created_at" timestamp(0) DEFAULT now(),
  "updated_at" timestamp(0),
  "deleted_at" timestamp(0)
)
;
ALTER TABLE "public"."child" OWNER TO "my_user";

-- ----------------------------
-- Primary Key structure for table child
-- ----------------------------
ALTER TABLE "public"."child" ADD CONSTRAINT "child_pkey" PRIMARY KEY ("id");

-- ----------------------------
-- Foreign Keys structure for table child
-- ----------------------------
ALTER TABLE "public"."child" ADD CONSTRAINT "child_parent_id_fkey" FOREIGN KEY ("parent_id") REFERENCES "public"."parent" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION DEFERRABLE INITIALLY DEFERRED;

【问题讨论】:

标签: php laravel postgresql transactions foreign-keys


【解决方案1】:

children 函数需要一个中间表,将其更改为:

 public function children()
{
    return $this->belongsToMany(Child::class);
}

不要这样做$parent->children()->save($child);

或者,如果您想这样做,请创建 child_parent 表,其中包含两个字段 child_idparent_id

【讨论】:

  • 我的场景使用一对多。如果没有用,我不想实现多对多关系。
【解决方案2】:

要在相关表中保存数据,请使用此

Parent::create(['name'=>'parent name']); //save in parent table
$lastId = Parent::query()->max('id'); //get last inserted row id

$parent = App\Parent::find($lastId);

$child = $parent->children()->create([
    'message' => 'A new comment.',
]);

你也可以使用createMany方法

$parent = App\Parent::find($lastId);

$parent->children()->createMany([
    [
        'message' => 'A new comment.',
    ],
    [
        'message' => 'Another new comment.',
    ],
]);

【讨论】:

  • 为什么要使用last id获取父模型?根据 eloquent 文档 (laravel.com/docs/5.8/eloquent) Parent::create() 返回创建的模型,因此不需要这两行额外的行(由于使用不是 100% 安全的 last id 而这很危险)。
  • 不需要查询最大id。尝试了您的代码,它给出了相同的外键错误。您是否尝试针对 PostgreSQL 数据库运行它?
【解决方案3】:

在 Parent 模型的关系上使用 createcreateMany 尝试使用此代码:

// Create the parent object.
$parent = Parent::create([
  'name' => 'Parent 1'
]);

// Insert one.
$child = $parent->children()->create([
    'name' => 'Child 1',
]);

// Insert many.
$parent->children()->createMany([
    [
        'name' => 'Child 2',
    ],
    [
        'name' => 'Child 3',
    ],
]);

【讨论】:

  • 如果没有交易,这肯定会奏效。但正如您在原始问题中看到的那样,我需要使用 PostgreSQL 数据库在事务中完成所有操作。
【解决方案4】:

你可以试试这个

try {
     DB::beginTransaction();
       $parent = Parent::create([
     'name' => 'Parent name'
     ]);
     $parent->children()->create([
       'parent_id' => $parent->id,
       'name' => 'Child name'
     ]);
     DB::commit();
 } catch (Exception $e) {
     DB::rollback();
 }

【讨论】:

  • 这段代码与原始问题中的代码有何不同?
【解决方案5】:

改变外键——在数据库上运行这个 sql

alter table child drop constraint child_parent_id;

alter table child add foreign key (parent_id) references parent(id) deferrable initially deferred;

这将允许您以任一顺序创建子级或父级,并且在提交之前不会验证约束。在这种情况下应该没有必要 - 但是,它确实取决于您的 ID 是如何生成的。

【讨论】:

  • 我已经尝试过了,但我仍然得到同样的错误:insert or update on table "child" violates foreign key constraint "child_parent_id_fkey"↵DETAIL: Key (parent_id)=(254) is not present in table "parent"我的 ids 是从序列生成的:default value: nextval('parent_id_seq'::regclass)
  • 检查子表的外键。也许它被错误地创建了。您可以粘贴 DDL(用于创建的 SQL)外键约束吗?要么是这样,要么是插入的数据不正确。意思是,子表插入没有插入您刚刚创建的父键。这实际上更有意义,因为如果您以正确的顺序创建此数据,通常您不必执行延迟约束,并且根据您的来源,您是。
  • 我用用于创建子表的 SQL 更新了问题。我还记录了创建的父级的 id,它与在返回 FK 错误的子表上执行的 INSERT 中存在的 parent_id 字段匹配。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多