使用 PostgreSQL 的 Hibernate import.sql 重复主键

Posted

技术标签:

【中文标题】使用 PostgreSQL 的 Hibernate import.sql 重复主键【英文标题】:Hibernate import.sql with PostgreSQL repeats primary keys 【发布时间】:2016-04-05 06:33:04 【问题描述】:

我将 Hibernate 与 PostgreSQL 一起使用。我有一个 import.sql 文件,Hibernate 在创建/验证架构后执行该文件。相应地导入了内容,但是当我尝试通过我的应用程序保留新条目时,Hibernate 尝试使用从 1 开始的自动生成的主键,我的导入文件已经使用了这些主键。有没有办法解决这个问题?

我有一个所有其他实体都继承自的单一实体类,它使用GenerationType.AUTOGenerationType.IDENTITY。他们两个都没有工作:

@MappedSuperclass
public class Entidade implements Serializable 
    private static final long serialVersionUID = -991512134416936719L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    public Entidade() 
    

    public Entidade(int id) 
        this.id = id;
    

    public int getId() 
        return id;
    

    public void setId(int id) 
        this.id = id;
    


这是我的 persistence.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
    xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
    <persistence-unit name="...">
        <jta-data-source>...</jta-data-source>
        <class>...</class>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect" />
            <property name="hibernate.hbm2ddl.auto" value="create-drop" />
            <property name="hibernate.default_schema" value="jcat" />
            <property name="hibernate.hbm2ddl.import_files_sql_extractor"
                value="org.hibernate.tool.hbm2ddl.MultipleLinesSqlCommandExtractor" />
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.hbm2ddl.import_files" value="import.sql" />
            <property name="hibernate.c3p0.min_size" value="5" />
            <property name="hibernate.c3p0.max_size" value="20" />
            <property name="hibernate.c3p0.max_statements" value="50" />
            <property name="hibernate.c3p0.timeout" value="3000" />
            <property name="hibernate.c3p0.idle_test_period" value="1800" />
        </properties>
    </persistence-unit>
</persistence>

【问题讨论】:

为什么不查看为 INSERT 调用的 SQL? 您需要更改 SQL 脚本,以便在脚本内调整序列。但是如果没有看到那个脚本,就不可能提供更多细节(基本上你不应该在那里使用硬编码的 ID,而是从序列中获取 ID) 如果我有需要填写的外键怎么办?使用硬编码的 ID,我可以确切地知道要放置哪些 ID,但是通过使用序列,我无法确定序列正在使用哪些值,因此我可以将它们放入外键中。 你不需要。只需使用例如引用生成的 ID lastval() 或使用数据修改 CTE。向我们展示您的 SQL 脚本。 *** 上有很多例子。 JPA“IDENTITY”的定义是在数据存储区中设置的生成值。这适用于 RDBMS 中的 AUTO_INCREMENT/SERIAL 类型功能。由于您使用了 INT,因此您颠覆了 JPA 机制。又名用户错误 【参考方案1】:

很可能,Hibernate 使用称为hibernate_sequence 的全局数据库范围序列。您也可以从其他数据库导入它。您可以在您的脚本中添加一个 insert 语句以获取 hibernate_sequence 中的大 id 值。

请在 SQL 日志中查看 Hibernate 是否使用 hibernate_sequence

更新 @DouglasDeRizzoMeneghetti 的一些补充说明

我按照你说的做了,效果很好。 FK 变成了整数,可以为空,现在我在数据库中只有一个序列,而不是每个 ID 一个序列。我发现即使是连接表也创建了自己的序列,因此通过使用单个序列,一切都变得更干净、更有条理。

【讨论】:

如果您使用 GenerationType.SEQUENCE,Hibernate 会创建自己的序列,但这些值会在所有表中使用,这是我想避免的行为。 @DouglasDeRizzoMeneghetti 不知道你这样做的原因。多年来,我们的团队一直在将此类序列用于大型系统(使用 Oracle DB 和 PostgreSQL)。但如你所愿:)【参考方案2】:

事实证明 PostgreSQL 的列有一个“串行”数据类型,它基本上是一个与序列相关联的整数数据类型。该序列由 DBMS 自动创建和处理。为了强制 Hibernate 创建这样一种列类型,可以使用 @Column(columnDefinition = "serial") 注释,如下所示:

@Id
@Column(columnDefinition = "serial")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;

请注意,虽然使用序列生成 ID,但我没有使用 strategy = GenerationType.SEQUENCE,因为这会告诉 Hibernate 创建自己的 hibernate_sequence 并在所有表中使用它,这是我试图避免的行为.生成序列的名称遵循模式$tablename_$column_id_name_seq,如“client_id_seq”。然后我可以在我的导入脚本中省略 ID 的硬编码插入,所以:

insert into schema.table (id, name) values (1, 'John');

成为:

insert into schema.table (name) values ('John');

而且,当在我的 web 应用程序中持久化实体时,Hibernate 会自动为每个表使用正确的顺序,因此我不会再遇到任何关于重复值的问题。

作为另一个贡献,如果您有一个@MappedSuperclass 在其 ID 列中使用上述注释,则所有子类都会自动创建自己的序列,因此不必为每个实体都这样做。我看到的唯一缺点是,虽然我有一个在 MariaDB 但不能在 PostgreSQL 下工作的注释,但现在我有一个在 MariaDB 上不起作用的注释,因为我不记得它具有@Column(columnDefinition = "serial") 的串行数据类型军队。对数据库的抽象和分离来说就这么多了。

编辑:这种策略引发了另一个问题。我的 PK 是串行类型的事实使 Hibernate 将其相应的 FK 创建为串行。由于在 PostgreSQL 中序列类型不能为空,我的实体之间不能有可选的关系。在插入我的测试数据后,我最终使用了@v.ladynev 提出的解决方案,在我的实体 ID 中使用了GenerationType.SEQUENCE,并为hibernate_sequence 设置了一个较高的起始值

这样,FK 变成了整数,可以为空,现在我在数据库中只有一个序列,而不是每个 ID 一个序列。我发现即使是连接表也创建了自己的序列,因此通过使用单个序列,一切都变得更清晰、更有条理。谢谢你。

【讨论】:

如何处理外键? 在导入脚本中,我可以只使用之前使用的序号。但是,PostgreSQL 将 FK 列创建为 serial 列并创建其对应的序列,但 serial 列还允许您在数据插入期间选择一个值,在这种情况下它不使用序列值。到目前为止,我还没有发现任何问题。 @v.ladynev 现在我想我知道你在说什么了。由于我的外键是串行类型的,它们不能为空,所以我的实体之间不能有可选的关系。现在这是一个我需要帮助的问题。 那么为什么不回到顺序方法呢? @v.ladynev 你的意思是使用hibernate_sequence?那么我的列可以是普通的旧整数类型吗?【参考方案3】:

你在 postgres 中的 id 列的数据类型应该是串行的

比使用这个注解

@Id @GeneratedValue(strategy=GenerationType.IDENTITY)

【讨论】:

目前我的 PK 是 int 类型。如果是这种情况,那么如何通过注释我的类来设置序列列类型? 你需要在PostgreSQL数据库中改变你的列数据类型,你不能通过注解类来改变列的类型。 @Id @Column(columnDefinition = "serial") @GeneratedValue(strategy = GenerationType.IDENTITY) 可以解决问题。

以上是关于使用 PostgreSQL 的 Hibernate import.sql 重复主键的主要内容,如果未能解决你的问题,请参考以下文章

使用 PostgreSQL 的 Hibernate import.sql 重复主键

使用 Hibernate 从 PostgreSQL 中的 JSONArray 获取对象的问题

不能用Hibernate调用PostgreSQL的11个存储过程

Hibernate使用PostgreSQL序列不影响序列表

是否可以将 Hibernate 与 PostgreSql 的 JSONB 数据类型一起使用?

PostGreSQL 结合 Hibernate 在项目中的使用小结