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事件,自动触发库存更新:
- 生成观察者文件:
php artisan make:observer IncomingObserver --model=Incoming
- 在观察者的
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); } }
- 在
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
相关产品推荐
相关产品推荐

