Laravel: 7;PHP: 7.4/8.0
在我的项目中,我有两个相关的模型:User和TimeAccount。两者都使用UUID作为主键,密钥名仍然是id。
迁移:
class CreateUsersTable extends Migration
{
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->uuid('id')->primary();
// snip...
});
}
// snip...
}class CreateTimeAccountsTable extends Migration
{
public function up()
{
Schema::create('time_accounts', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->foreignUuid('user_id')->nullable()->constrained();
// snip...
});
}
// snip...
}模型:
class User
{
use IsIdentifiedByUuid;
protected $keyType = 'string';
public function timeAccount()
{
return $this->hasOne(TimeAccount::class);
}
// snip...
}class TimeAccount
{
use IsIdentifiedByUuid;
protected $keyType = 'string';
public function user()
{
return $this->belongsTo(User::class);
}
// snip...
}IsIdentifiedByUuid性状:
trait IsIdentifiedByUuid
{
protected static function bootIsIdentifiedByUuid()
{
static::creating(fn ($model) => static::isIdentifiedByUuidCreatingHandler($model));
static::saving(fn ($model) => static::isIdentifiedByUuidSavingHandler($model));
}
public function initializeIsIdentifiedByUuid()
{
$this->keyType = 'string';
}
protected function getUuidColumn(): string
{
return property_exists($this, 'uuid_column') ? $this->uuid_column : 'id';
}
protected static function getNewUuid(): UuidInterface
{
$columnName = app(static::class)->getUuidColumn();
$tableName = app(static::class)->getTable();
$columnDescriptor = "$tableName.$columnName";
$uuid = "";
$query = \DB::table($tableName)
->select($columnDescriptor)
->where($columnDescriptor, $uuid);
$attempts = 0;
do {
if ($attempts >= \App\Constants\Uuid::MAX_ATTEMPTS) {
throw new UuidMaxGeneratorAttemptsExceededException();
}
$uuid = Str::uuid();
$attempts++;
} while ($query->setBindings([ $uuid->toString() ])->count() > 0);
return $uuid;
}
/**
* Handles the creation of a model.
* - Generates new UUID if the UUID column is empty and auto-increment is enabled
*
* @param Model $model
* @throws \App\Exceptions\UuidMaxGeneratorAttemptsExceededException
*/
protected static function isIdentifiedByUuidCreatingHandler(Model $model)
{
$columnName = $model->getUuidColumn();
if ($model->getIncrementing() && !$model->{$columnName}) {
$uuid = static::getNewUuid();
\Log::debug(
"IsIdentifiedByUuid [CREATING]:" .
" Generating new UUID for `" . get_class($model) . ": $uuid`"
);
$model->{$columnName} = $uuid->toString();
} else {
\Log::debug(
"IsIdentifiedByUuid [CREATING]:" .
" Using existing UUID for `" . get_class($model) . ": $model->{$columnName}`"
);
}
}
/**
* Handles the saving of a Model.
* - Prevents changes to the UUID column
* - Rolls back changed value to old value
*
* @param Model $model
*/
protected static function isIdentifiedByUuidSavingHandler(Model $model)
{
$columnName = $model->getUuidColumn();
$originalUuid = $model->getOriginal($columnName);
if (!is_null($originalUuid) &&
$originalUuid !== $model->{$columnName}
) {
\Log::debug(
"IsIdentifiedByUuid [SAVING]:" .
" Prevented change of UUID for `" . get_class($model) . ":$originalUuid`"
);
$model->{$columnName} = $originalUuid;
}
}
}现在的问题:
在创建的钩子上的用户观察者中,我正在这样做:
$userTimeAccount = $user->timeAccount()->create([
// snip...
]);到目前为止,在切换到UUID而不是整数键之前,一切都很好!据我所知,在用户模型被保存之前(显然是在auto被触发之前)创建相关模型是雄辩/Laravel明确允许的(尽管我找不到文档的部分)。
切换到UUID之后,它不再工作了,我得到了一个SQL错误:
SQLSTATE23000:完整性约束违反: 1452不能添加或更新子行:外键约束失败(
laravel_torus.time_accounts,约束time_accounts_user_id_foreign外键(user_id)引用users(id)) (SQL: insert insert intotime_accounts(time_account_type_id,balance,borrow,user_id,id,updated_at,created_at)值(3,0,0,0,0,a 995807b-b7e1-4af3-96d-7c48f97f943d,2021-07-09 :54:25):2021-07-09 12:54:25)
SQL美化:
insert into `time_accounts`
(`time_account_type_id`, `balance`, `borrow`, `user_id`, `id`, `updated_at`, `created_at`)
values
(3, 0, 0, 0, 'bad86e70-7496-4b42-b2a1-5cc3c5a4d06c', '2021-07-09 11:19:17', '2021-07-09 11:19:17');不知何故,Laravel试图为外键插入0(整数),这显然是行不通的。我甚至尝试手动将keyType设置为'string',但没有效果。
我哪里错了?
编辑:,似乎存在种族条件。观察者的创建钩子将在User模型的创建钩子之前触发。这将导致缺少ID --在使用整数和自动增量时,ID是存在的,就像它应该的那样。
发布于 2021-07-16 07:46:31
TL;DR
I无法用MySQL解决这个问题,于是切换到Postgres。
不要在MYSQL中使用 UUID ,除非您确信: 1.您需要使用,2.性能影响可以忽略不计!
我实在无法解释这个错误是如何发生的。该ID已经存在(与调试器一起查看模型的$attribnutes数组),在调用$user->id时,Laravel仍将返回整数$user->id。
在研究这个问题时,我注意到Laravel正在为UUID创建字符串列,而不是二进制(wich正是我所期望的)。由于与整数/二进制比较相比,字符串比较非常昂贵,所以我决定放弃MySQL。UUID在这个项目中是必需的,将来转向另一个数据库管理系统,而不是MySQL,已经是意料之中的了。
如果定义了UUID列,uuid将使用Postgres支持特殊的列类型。这不仅可以将ID作为二进制进行存储和比较,还可以检查格式和唯一值,这意味着您不能意外地输入无效的UUID,比如在MySQL常规sting列中,除了not null和unique之外,似乎根本没有检查。
https://stackoverflow.com/questions/68317720
复制相似问题