Postgresql手动插入行与Hibernate保存顺序

时间:2017-08-05 14:09:24

标签: java postgresql hibernate

我有一个带有@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");

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

然后我用

创建Java用户帐户
UserAccount user = new UserAccount(null, "Paul") // where null => id

并使用UserAccountService's方法保存 save并收到错误消息,例如ID键中有重复。

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

2 个答案:

答案 0 :(得分:0)

如果id的类型是串行的,user_accounts_id_seq是自动生成的此属性序列,则手动插入使用:

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

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

答案 1 :(得分:0)

手工制作的 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       | increment | cycle_option
:--------------- | :-------------------------- | :------------------- | :-------- | ----------------: | ----------------------: | ------------: | :---------- | :------------ | :------------------ | :-------- | :-----------
postgres         | fiddle_sehahfpstptzxjchrypb | user_accounts_id_seq | bigint    |                64 |                       2 |             0 | 1           | 1             | 9223372036854775807 | 1         | NO          

此时,您可以在表格中插入一行,而不指定列id的值,id列将采用其默认值,这将是对于名为nextval的序列,我们将其视为user_accounts_id_seq

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

在单个用户设置中(没有其他人使用user_accounts执行任何操作),我们已经获得id = 1

SELECT * FROM user_accounts;
id | name                       
-: | :--------------------------
 1 | First inserted user account

您现在可以查看序列当前值,它是1:     SELECT currval(' user_accounts_id_seq');

| currval |
| ------: |
|       1 |

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

INSERT INTO user_accounts
    (id, name)
VALUES
    (NULL, 'It won''t work');
ERROR:  null value in column "id" violates not-null constraint
DETAIL:  Failing row contains (null, It won't work).

序列当前值继续相同

SELECT currval('user_accounts_id_seq') ;
| currval |
| ------: |
|       1 |

在这一点上,我们可以插入一行id = 2。它会起作用,因为表中已经没有id的任何行:

INSERT INTO user_accounts
    (id, name)
VALUES
    (2, 'Inserted a 2 id, it will work, but will produce problems');
1 rows affected

但是,序列更改,这将导致以后出现问题:

SELECT currval('user_accounts_id_seq') ;
| currval |
| ------: |
|       1 |

如果我们现在尝试使用序列号插入,我们将运气不好,序列将给出nextval 2(currval + 1)。由于id = 2已经在桌面上,它将生成PK violation

INSERT INTO user_accounts
    (name)
VALUES
    ('This won''t work either, we broke the sequence');
ERROR:  duplicate key value violates unique constraint "user_accounts_pkey"
DETAIL:  Key (id)=(2) already exists.

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

解决方法:

如果真的需要同时使用自动生成的 id以及手动生成 id s,最安全的方法是确保它们在不重叠的范围内。例如,保留id 1 ... 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 here

上查看