Postgresql 手动插入的行与休眠保存与顺序保存



当我有以下情况时,我有一个带有@Id列的实体:

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq")
@SequenceGenerator(name = "seq", sequenceName = "user_accounts_id_seq")
public Long getId(){
return id;
}

当我通过执行以下命令手动将数据插入表中时:

insert into user_accounts(id, name) values(1, "John");

我没有使用序列,而是手动添加等于 1 的 id。

然后我通过以下方式在 Java 中创建用户帐户

UserAccount user = new UserAccount(null, "Paul") // where null => id

并用UserAccountService's方法保存save并得到一个错误,例如 id 密钥中有重复项。

我不确定我是否很好地理解了这些策略。我希望能够在数据库编辑器中手动添加一些值,然后在保存时在程序中添加一些值,如果存在带有 id 的值,则休眠应该采用下一个可能的值。

手工制作的ID 与数据库生成的 ID 混合在一起不是一个好主意。你应该尽可能避免它

序列的工作原理

假设这是您的表:

CREATE TABLE user_accounts
(
id SERIAL PRIMARY KEY,
name TEXT NOT NULL CHECK(trim(name) > '')
) ;
-- The previous `CREATE` has actually worked as if it were defined like:
CREATE TABLE user_accounts
(
-- Name of sequence = name of table || '_' || name of column || '_id'
id INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('user_accounts_id_seq'),
name TEXT NOT NULL CHECK(trim(name) > '')
) ;

此时,您实际上可以检查是否已创建一个序列:

SELECT *
FROM information_schema.sequences;
sequence_catalog | sequence_schema | sequence_name | data_type | numeric_precision | numeric_precision_radix | numeric_scale | start_value | minimum_value | maximum_value | 增量 | cycle_option :--------------- |:-------------------------- |:------------------- |:-------- |----------------: |----------------------: |------------: |:---------- |:------------ |:------------------ |:-------- |:----------- 邮政 |fiddle_sehahfpstptzxjchrypb |user_accounts_id_seq |比金特 |               64 |                      2 |            0 |1 |1 |9223372036854775807 |1 |NO

此时,您可以在表中插入一行,而无需指定列的值idid列将采用其默认值,该值将被视为名为user_accounts_id_seq的序列的nextval

INSERT INTO user_accounts
(name)
VALUES
('First inserted user account') ;

在单个用户设置中(没有其他人对user_accounts做任何事情(,我们将得到id = 1

SELECT * FROM user_accounts;
ID | 名称                       -: |:--------------------------  1 |第一个插入的用户帐户

您现在可以检查他的序列当前值,它是1: 选择库尔瓦尔('user_accounts_id_seq'( ;

|
库尔瓦尔 | |------: | |      1 |

我们现在可以做奇怪的事情了。首先,我们插入一行,其中包含 id 的显式NULL值。它在SQL中不起作用(我不知道Hibernate是否会自己操纵这个INSERT删除NULL并将其转换为DEFAULT(:

INSERT INTO user_accounts
(id, name)
VALUES
(NULL, 'It won''t work');
错误:列"id"中的空值违反了非空约束 详细信息:失败的行包含(空,它不起作用(。

序列电流值保持不变

SELECT currval('user_accounts_id_seq') ;
|
库尔瓦尔 | |------: | |      1 |

此时,我们可以插入一行id = 2.它将起作用,因为表中没有任何带有该id的行:

INSERT INTO user_accounts
(id, name)
VALUES
(2, 'Inserted a 2 id, it will work, but will produce problems');
受影响的 1 行

但是,顺序不会更改,这将导致以后的问题:

SELECT currval('user_accounts_id_seq') ;
|
库尔瓦尔 | |------: | |      1 |

如果我们现在尝试插入序列号,我们将不走运,序列将给出 2 的nextval(currval+ 1(。由于id = 2已经在桌面上,它将产生一个PK violation

INSERT INTO user_accounts
(name)
VALUES
('This won''t work either, we broke the sequence');
错误:重复的键值违反了唯一约束"user_accounts_pkey" 详细信息:密钥 (id(=(2( 已存在。

您可以在以下位置查看所有设置和实验:dbfiddle 这里


解决方法:

如果您确实需要同时使用自动生成id手动生成的ids,最安全的方法是确保它们位于不重叠的范围内。例如,保留id1 ...10000 用于手动输入,并以10001开始您的序列以进行自动输入。

CREATE TABLE user_accounts
(
id SERIAL PRIMARY KEY,
name TEXT NOT NULL CHECK(trim(name) > '')
) ;
ALTER SEQUENCE user_accounts_id_seq RESTART WITH 10001 ;

我建议不要尝试让数据库(通过触发器或但是(使用下一个可用的id因为它是一个PK violation。需要具有隔离级别SERIALIZABLE才能正常工作,否则在并发方案中仍很有可能出现问题。

在这里的dbfiddle上检查它

如果id类型是串行的,并且user_accounts_id_seq是自动生成的序列,则对于手动插入,请使用:

insert into user_accounts(name) values("John");

将从user_accounts_id_seq序列中获取 ID。
因此,您和休眠将使用相同的user_accounts_id_seq序列,以保证 id 的唯一值。

最新更新