You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel中如何优化入库时更新库存表数据的实现方式?

问题背景

现有两张数据表Incomings和Stocks,表结构如下:

Incomings
-----
id (Primary Key)
trx_number
trx_date
product_id (FK)
qty

Stocks
------
id (Primary Key)
product_id (FK)
begin_stock
in
out
end_stock

需求是:每当新增一条入库(Incomings)记录时,同步更新Stocks表的in和end_stock字段。

当前采用Laravel的事件(Event)+监听器(Listener)实现,代码如下:

控制器代码:

\Illuminate\Support\Facades\DB::transaction(function () use ($request) {
    Incoming::create([
        'trx_number' => 'TRX-001',
        'trx_date' => '2023-07-31',
        'product_id' => '1001',
        'qty' => 10
    ]);

    \App\Events\IncomingOccured::dispatch($attr);
});

事件与监听器绑定:

\App\Events\IncomingOccured::class => [
    \App\Listeners\UpdateProductStock::class,
],

监听器逻辑:

$stock = Stock::where('product_id', $item['product_id'])->first();

$stock = Stock::updateOrCreate(['product_id' => $item['product_id']]);

$stock->save();

$stock->increment('in', $requestedQty);
$stock->increment('end_stock', $requestedQty);

当前已实现需求,但认为该方案过于复杂,无需使用事件机制,请问有更优的实现方式吗?


更优实现方案

方案一:直接在事务内处理逻辑

既然入库操作必须同步更新库存,完全可以把库存更新逻辑直接放到控制器的事务中,省去事件监听器的中间环节,代码更直观:

\Illuminate\Support\Facades\DB::transaction(function () use ($request) {
    $incoming = Incoming::create([
        'trx_number' => 'TRX-001',
        'trx_date' => '2023-07-31',
        'product_id' => '1001',
        'qty' => 10
    ]);

    // 直接更新库存,updateOrCreate自动处理查询或新建逻辑
    $stock = Stock::updateOrCreate(
        ['product_id' => $incoming->product_id],
        // 新建库存时初始化基础字段
        ['begin_stock' => 0, 'in' => 0, 'out' => 0, 'end_stock' => 0]
    );

    $stock->increment('in', $incoming->qty);
    $stock->increment('end_stock', $incoming->qty);
});

事务能保证入库和库存更新要么都成功要么都回滚,彻底避免数据不一致问题。

方案二:使用模型观察者(Model Observer)

如果不想把业务逻辑写在控制器里,可通过Laravel的模型观察者监听Incoming模型的created事件,自动触发库存更新:

  1. 生成观察者文件:
php artisan make:observer IncomingObserver --model=Incoming
  1. 在观察者的created方法中编写库存更新逻辑:
namespace App\Observers;

use App\Models\Incoming;
use App\Models\Stock;

class IncomingObserver
{
    public function created(Incoming $incoming)
    {
        $stock = Stock::updateOrCreate(
            ['product_id' => $incoming->product_id],
            ['begin_stock' => 0, 'in' => 0, 'out' => 0, 'end_stock' => 0]
        );

        $stock->increment('in', $incoming->qty);
        $stock->increment('end_stock', $incoming->qty);
    }
}
  1. 在AppServiceProvider的boot方法中注册观察者:
use App\Models\Incoming;
use App\Observers\IncomingObserver;

public function boot()
{
    Incoming::observe(IncomingObserver::class);
}

之后控制器只需处理入库记录创建即可:

\Illuminate\Support\Facades\DB::transaction(function () use ($request) {
    Incoming::create([
        'trx_number' => 'TRX-001',
        'trx_date' => '2023-07-31',
        'product_id' => '1001',
        'qty' => 10
    ]);
});

观察者会自动在入库记录创建成功后触发库存更新,且模型事件会在事务内执行,若事务回滚,观察者逻辑也不会生效。

方案三:模型内部绑定事件(轻量化实现)

如果不想单独创建观察者类,也可以直接在Incoming模型的boot方法中绑定created事件:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use App\Models\Stock;

class Incoming extends Model
{
    protected static function boot()
    {
        parent::boot();

        static::created(function ($incoming) {
            $stock = Stock::updateOrCreate(
                ['product_id' => $incoming->product_id],
                ['begin_stock' => 0, 'in' => 0, 'out' => 0, 'end_stock' => 0]
            );

            $stock->increment('in', $incoming->qty);
            $stock->increment('end_stock', $incoming->qty);
        });
    }
}

这种方式更轻量化,适合简单的关联逻辑,控制器代码和方案二完全一致。


注意事项

  • 无论采用哪种方案,必须保证入库和库存更新在同一个事务内,这是避免数据不一致的核心前提。
  • 原监听器代码存在冗余:updateOrCreate方法本身会自动执行保存操作,无需额外调用save();先执行first()再调用updateOrCreate也是多余的,updateOrCreate已经内置了查询或创建的逻辑。

内容的提问来源于stack exchange,提问作者owf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 20:12:42