【Laravel】複合キーを使用している場合に、Eloquentが2回DELETEを発行する問題と対策
はじめに
複合主キーを設定しているモデルに対して、where
で条件を指定しdelete()
で削除処理を行った際に、意図しない DELETE クエリが追加で発行されてしまう現象に遭遇しました。
原因の特定と解決に時間を要したため、備忘録として記録に残します。
現象
複合主キーを持つテーブルに対して、order_no
とitem_no
をwhere
条件で指定し、delete()
を実行しました。
$model = MstDevice::where('order_no', $order_no)
->where('item_no', $item_no)
->first();
if ($model) {
$model->delete();
}
発生した現象
-
1つ目の DELETE クエリ(意図した削除)
WHERE order_no = ? AND item_no = ?
-
2つ目の DELETE クエリ(意図しない削除)
WHERE order_no = ?
order_no
のみを条件にした DELETE クエリが追加で発行され、意図しない範囲のレコードが削除されてしまいました。
Eloquentが意図しないDELETEを発行する理由
1. Eloquentは複合主キーをサポートしていない
Laravel の Eloquent は単一の主キーを前提に設計されており、$primaryKey
に複数のカラムを指定できません。
このため、Eloquent のdelete()
はモデルの主キーのみを条件に削除を行います。
protected $primaryKey = ['order_no', 'item_no']; // Laravel標準では無効
この設定は Laravel では無効なため、Eloquent はorder_no
だけを主キーとして認識し、item_no
を無視する可能性があります。
delete()
の内部動作
2. Eloquent のdelete()
は、以下の手順で削除処理を行います。
-
where
で条件を指定し、first()
でモデルを取得する。 - 取得したモデルの
primaryKey
を元に DELETE クエリを発行する。 -
$primaryKey
がorder_no
のみと認識されるため、WHERE order_no = ?
だけで削除される。
この仕様により、意図しないレコード削除が発生します。
対策
where
条件を指定して削除する
1. 明示的にdelete()
をモデルのインスタンスで実行せず、クエリビルダで直接削除を行います。
MstDevice::where('order_no', $order_no)
->where('item_no', $item_no)
->delete();
この方法であれば、意図しない DELETE クエリが発行されることはありません。
2. getKeyName() の設定を適切に行う(非推奨)
一応、以下のようにgetKeyName()
をオーバーライドすれば、複合主キーのように扱うことも可能です。
public function getKeyName()
{
return ['order_no', 'item_no'];
}
※ただし、Eloquent は複合キーを標準サポートしていないため、別の予期しない挙動を引き起こす可能性があります。where
を明示的に指定する方法の方が安全です。
まとめ
方法 | 説明 | 推奨度 |
---|---|---|
$model->delete() |
primaryKey が単一キーと誤認される |
× |
Model::destroy() |
primaryKey が単一キーと誤認される |
× |
Model::find() |
primaryKey が単一キーと誤認される |
× |
Model::where()->delete() |
安全に削除可能 | ○ |
getKeyName() を複合キーにする |
Laravel標準では非推奨 | △ |
推奨される削除方法
MstDevice::where('order_no', $order_no)
->where('item_no', $item_no)
->delete();
Laravel では複合主キーをサポートしていないため、明示的にwhere
条件を指定して削除するのが安全です。
Eloquent の仕様を理解し、意図しないレコード削除を防ぎましょう!
Discussion