Laravel中Book与Review一对多关联数据填充失败求助
Laravel一对多关联数据填充报错:book_id不能为空
执行php artisan migrate:refresh --seed进行数据填充时失败,报错提示SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'book_id' cannot be null。预期生成100本书(34本好评书+33本中等评价书+33本差评书),每本书对应5-30条评论,但实际仅生成34本书,无任何评论数据,不过reviews表的book_id外键字段已成功创建。
Book模型
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Book extends Model { use HasFactory; public function reviews(){ // 一本书对应多条评论 return $this->hasMany(Review::class); // 尝试过以下写法,未解决问题 //return $this->hasMany(Review::class, 'book_id'); } }
Review模型
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Review extends Model { use HasFactory; public function books(){ // 一条评论属于一本书 return $this->belongsTo(Book::class); // 尝试过以下写法,未解决问题 //return $this->belongsTo(Book::class,'book_id'); } }
Book模型迁移文件
public function up() : void { Schema::create('books', function (Blueprint $table) { $table->id(); $table->string('title'); $table->string('author'); $table->timestamps(); }); }
Review模型迁移文件
public function up() : void { Schema::create('reviews', function (Blueprint $table) { $table->id(); $table->text('review'); $table->unsignedTinyInteger('rating'); $table->timestamps(); // 定义外键的原始写法 // $table->unsignedBigInteger( 'book_id' ); // $table->foreign( 'book_id' ) //->references( 'id' )->on( 'books' )->onDelete( 'cascade' ); // 简洁写法 $table->foreignId('book_id')->constrained()->cascadeOnDelete(); }); }
BookFactory文件
public function definition() : array { return [ 'title' => fake()->sentence(3), 'author' => fake()->name, 'created_at' => fake()->dateTimeBetween('-2 years'), 'updated_at' => fake()->dateTimeBetween('created_at', 'now') ]; }
ReviewFactory文件
class ReviewFactory extends Factory { /** * 定义模型的默认状态 * * @return array<string, mixed> */ public function definition() : array { return [ 'book_id' => null, 'review' => fake()->paragraph, 'rating' => fake()->numberBetween(1, 5), 'created_at' => fake()->dateTimeBetween('-2 years'), 'updated_at' => fake()->dateTimeBetween('created_at', 'now') ]; } // 创建状态方法生成多样化数据 public function good(){ return $this->state(function (array $attributes) { return [ 'rating' => fake()->numberBetween(4,5) ]; }); } public function average(){ return $this->state(function (array $attributes) { return [ 'rating' => fake()->numberBetween(2,5) ]; }); } public function bad(){ return $this->state(function (array $attributes) { return [ 'rating' => fake()->numberBetween(1,3) ]; }); } }
DatabaseSeeder.php文件
public function run() : void { Book::factory(34)->create()->each(function ($book) { $numReviews = random_int(5, 30); Review::factory() ->count($numReviews) // 生成评论数量 ->good() // 使用好评状态 ->for($book) // 关联当前书籍 ->create(); }); Book::factory(33)->create()->each(function ($book) { $numReviews = random_int(5, 30); Review::factory() ->count($numReviews) ->average() ->for($book) ->create(); }); Book::factory(33)->create()->each(function ($book) { $numReviews = random_int(5, 30); Review::factory() ->count($numReviews) ->bad() ->for($book) ->create(); }); }
执行命令时的错误信息
INFO Rolling back migrations. 2023_08_17_222856_create_reviews_table ...................................... 18ms DONE 2023_08_17_222848_create_books_table ......................................... 7ms DONE 2019_12_14_000001_create_personal_access_tokens_table ........................ 8ms DONE 2019_08_19_000000_create_failed_jobs_table ................................... 7ms DONE 2014_10_12_100000_create_password_reset_tokens_table ......................... 7ms DONE 2014_10_12_000000_create_users_table ......................................... 7ms DONE INFO Running migrations. 2014_10_12_000000_create_users_table ........................................ 33ms DONE 2014_10_12_100000_create_password_reset_tokens_table ........................ 36ms DONE 2019_08_19_000000_create_failed_jobs_table .................................. 24ms DONE 2019_12_14_000001_create_personal_access_tokens_table ....................... 34ms DONE 2023_08_17_222848_create_books_table ......................................... 9ms DONE 2023_08_17_222856_create_reviews_table ...................................... 45ms DONE INFO Seeding database. Illuminate\Database\QueryException SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'book_id' cannot be null (Connection: mysql, SQL: insert into `reviews` (`book_id`, `review`, `rating`, `created_at`, `updated_at`) values (?, Officiis consequatur temporibus maxime sequi laudantium et. Nam non voluptas est ea. Id fugit et amet deserunt ullam laborum eveniet. Harum eos ratione voluptate debitis qui sequi., 5, 2022-11-02 05:18:02, 2022-12-06 12:41:24)) at vendor\laravel\framework\src\Illuminate\Database\Connection.php:795 791▕ // If an exception occurs when attempting to run a query, we'll format the error 792▕ // message to include the bindings with SQL, which will make this exception a 793▕ // lot more helpful to the developer instead of just the database's errors. 794▕ catch (Exception $e) { ➜ 795▕ throw new QueryException( 796▕ $this->getName(), $query, $this->prepareBindings($bindings), $e 797▕ ); 798▕ } 799▕ } 1 vendor\laravel\framework\src\Illuminate\Database\Connection.php:580 PDOException::("SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'book_id' cannot be null") 2 vendor\laravel\framework\src\Illuminate\Database\Connection.php:580 PDOStatement::execute()
问题解决方法
核心原因
ReviewFactory的definition方法中硬编码了'book_id' => null,调用->for($book)时,factory的默认值会覆盖关联注入的book_id,导致插入评论时book_id为null,违反数据库非空约束。
修复步骤
- 修改ReviewFactory,移除definition里的
book_id字段定义:
public function definition() : array { return [ 'review' => fake()->paragraph, 'rating' => fake()->numberBetween(1, 5), 'created_at' => fake()->dateTimeBetween('-2 years'), 'updated_at' => fake()->dateTimeBetween('created_at', 'now') ]; }
- 优化Review模型关联命名(可选,但符合Laravel规范):将
books()改为book(),因为单条评论仅属于一本书:
public function book(){ return $this->belongsTo(Book::class); }
- 重新执行命令:
php artisan migrate:refresh --seed
修改后,->for($book)会自动将当前书籍的id赋值给评论的book_id字段,即可正常生成关联数据。
内容的提问来源于stack exchange,提问作者Amin
相关产品推荐
相关产品推荐

