为没有 Hibernate Annotations 的 PostgreSQL 文本类型更正 JPA Annotation

Posted

技术标签:

【中文标题】为没有 Hibernate Annotations 的 PostgreSQL 文本类型更正 JPA Annotation【英文标题】:Correct JPA Annotation for PostgreSQL's text type without Hibernate Annotations 【发布时间】:2015-04-19 17:33:14 【问题描述】:

我正在使用以下方式开发应用程序:

Java 1.7 JPA(包含在 javaee-api 7.0 中) 休眠 4.3.8.Final PostgreSQL-JDBC 9.4-1200-jdbc41 PostgreSQL 9.3.6

我想对一些字符串属性使用 PostgreSQL 文本数据类型。据我所知,在 JPA 中这应该是正确的注释,在 PostgreSQL 中使用文本:

@Entity
public class Product
    ...
    @Lob
    private String description;
    ....

当我这样注释我的实体时,我遇到了如下所示的错误: http://www.shredzone.de/cilla/page/299/string-lobs-on-postgresql-with-hibernate-36.html

简而言之:对于 clob/text-types,hibernate 和 jdbc 似乎并不一致。

所描述的解决方案正在运行:

@Entity
public class Product
    ...
    @Lob
    @Type(type = "org.hibernate.type.TextType")
    private String description;
    ...

但这有一个明显的缺点:源代码在编译时需要休眠,这应该是不必要的(这是首先使用 JPA 的原因之一)。

另一种方法是像这样使用列注释:

@Entity
public class Product
    ...
    @Column(columnDefinition = "text")
    private String description;
    ...

效果很好,但是: 现在我坚持使用具有文本类型的数据库(也称为文本;)),如果将来使用另一个数据库,注释很容易被忽略。因此,可能的错误很难找到,因为数据类型是在 String 中定义的,因此在运行之前无法找到。

有没有这么简单的解决方案,我就是没看到?我很确定我不是唯一一个将 JPA 与 Hibernate 和 PostgreSQL 结合使用的人。所以我有点困惑,我找不到更多这样的问题。

为了完成这个问题,persistence.xml 看起来像这样:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0"
  xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
                        http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="entityManager">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <class>com.app.model.Product</class>
    <properties>
      <property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver" />
      <property name="javax.persistence.jdbc.url"
        value="jdbc:postgresql://localhost:5432/awesomedb" />
      <property name="javax.persistence.jdbc.user" value="usr" />
      <property name="javax.persistence.jdbc.password" value="pwd" />
      <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect" />
      <property name="hibernate.jdbc.use_streams_for_binary" value="false" />
      <property name="hibernate.hbm2ddl.auto" value="create-drop" />
      <property name="show_sql" value="true" />
    </properties>
  </persistence-unit>
</persistence>

更新:

这个问题或多或少等同于这个问题,选择的答案是这个问题中描述的第二种方法,由于休眠运行时依赖性,我不喜欢这种方法: store strings of arbitrary length in Postgresql

这似乎与:https://hibernate.atlassian.net/browse/JPA-48

【问题讨论】:

既然你在谈论什么是真正的部署问题(自动生成的 DDL),难道你不能在 Postgres 中手动将表创建为 TEXT 而不在类中指定任何内容吗?跨度> 是的,我希望能够从休眠中生成数据库。 @chrylis 当您这样说时,听起来是错误的 :D 我更喜欢的方式是我首先使用 JPA 方式中的注释(简单地 @Lob)。并让 hibernate/jdbc 在后台发挥作用。我知道,@Lob String 应该在 DB2、Oracle 中产生 clob,在 H2/HSQLDB 中产生 longvarchar,在 mysql 中产生 longtexttext,在 PostgreSQL 中产生 text。我的问题是,hibernate 和 postgresql jdbc 会产生错误,应该没有问题。 但是 text 和 lob 在语义上不是相同的东西。 我正在寻找您想要的相反内容,您的问题就是我的答案。好奇的。我刚刚添加了columnDefinition = "text"。谢谢。 【参考方案1】:

我只需要添加这个注释:

@Column(columnDefinition="TEXT")

它不能单独工作。我不得不在数据库中重新创建表。

DROP TABLE yourtable 或使用 ALTER TABLE 语句将列类型更改为 text

【讨论】:

对我来说,大写 TEXT 不适用于 postgres,它必须全部小写,否则我会收到错误消息 ERROR: type "TEXT" does not exist at character。这有效:@Column(columnDefinition="text") 这对我有用(大写和小写),我不必重新创建表。 (postgres 13)【参考方案2】:

如果你想使用普通的 JPA,你可以像这样在方言上重新映射使用的 CLOB 类型:

public class PGSQLMapDialect extends PostgreSQL9Dialect 


  @Override
  public SqlTypeDescriptor remapSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor) 
    if (Types.CLOB == sqlTypeDescriptor.getSqlType()) 
      return LongVarcharTypeDescriptor.INSTANCE;
    
    return super.remapSqlTypeDescriptor(sqlTypeDescriptor);
  



因此它不会使用来自 JDBC 驱动程序的 CLOB 映射,该驱动程序使用 OID 作为列并通过大对象处理存储/加载文本。 这只会导致通过 VarcharTypeDescriptor 类对 Postgres JDBC 驱动程序上的 createt 文本列进行 setString 和 getString 调用。

【讨论】:

看起来很有趣,我试试看。但我仍然不明白为什么需要这样做。 您不需要它 - 它已经使用 JDBC 驱动程序中的 CLOB 和 OID 来处理字符串。如果您想更改它,最好在 JDBC 驱动程序中解决 - 恕我直言,Hibernate 在这里很好。 像这样使用“已经工作”的 CLOB 的缺点是性能极差 - 恕我直言,这不好 这可能不太好,但它不承担修复该问题的责任 - 这就是为什么我确实写了你应该向 jdbc 驱动程序开发人员解决这个问题。 请解释如何连接它 - 我认为这将帮助我使用 Derby 运行 JUnit 测试,并部署到 MariaDB,并在代码中使用 MariadDB 列类型定义注释。【参考方案3】:

由于text 类型不是 SQL 标准的一部分,我猜没有官方的 JPA 方式。

但是,text 类型与varchar 非常相似,但没有长度限制。您可以使用@Columnlength 属性来提示JPA 实现:

@Column(length=10485760)
private String description;

更新: 10 MiB 似乎是 postgresql 中 varchar 的最大长度。根据documentation,text几乎是无限的:

在任何情况下,可以存储的可能最长的字符串 大约 1 GB。

【讨论】:

是的,这在大多数情况下都有效,当应用程序不应该生成表时。它产生一个 PostgreSQL 列类型 varchar(1000000000)。有限制,文本类型被定义为存储文本,没有任何限制。 据我所知,Hibernate 和 PostgreSQL 的 jdbc 驱动,在这一点上并不真正兼容,hibernate 尝试获取一个 clob,因为 lob 注解和 jdbc 驱动提供的内容吊球不是地址。我不知道哪个是正确的方法,但绝对应该安排一个解决方案,以便最终用户可以使用这些东西。因为碰到这个问题实在是太难看了。 @knoe:您也可以在 Postgres 中无限制地使用 varchar。在 Postgres 中,textvarchar 之间确实没有区别,所以只要使用最适合混淆层的任何东西 @holmis83 顺便说一句:我刚刚试过这个,我得到了这个:PSQL ERROR: length for type varchar cannot exceed 10485760@a_horse_with_no_name 是的,这与使用@Column(columnDefinition="text") 完全一样,当我理解你的时候。这与原始问题中的解决方案二相同。 @knoe 更新了我的答案。我不建议为生产系统自动生成 DDL。【参考方案4】:

我会选择简单的private String description;。仅当您从代码生成数据库时,列类型才有问题,因为它将生成为varchar 而不是text

以与数据库无关的方式编写代码非常棒,并且没有任何 JPA 供应商特定的东西,但在某些情况下这是不可能的。如果现实是您必须支持多种数据库类型及其所有细节,那么您必须在某处考虑这些细节。一种选择是使用columnDefinition 来定义列类型。另一种是保持代码不变,只需更改数据库中的列类型。我更喜欢第二个。

【讨论】:

是的,我想从代码生成数据库。如果我不需要它。我想通了,@Column(columnDefinition = "longvarchar") private String description;可以正常工作,因为 hibernate/jdbc 驱动程序组合需要文本而不是 clob 地址。这是链接中的长/文本错误的问题。但是表创建会失败,因为 postgresql 不知道 longvarchar 类型。如果我可以自己在 PostgreSQL 中定义类似 longvarchar=text 的别名,这可能会很有趣... 这或多或少与您的解决方案相同,因为 columnDefiniton 仅用于表定义。 @knoe:varchartext 在 Postgres 中绝对没有区别。 @a_horse_with_no_name 没错,但是没有注解的 String 生成的列是 varchar(255) 但我不希望它被限制,这首先​​是问题。

以上是关于为没有 Hibernate Annotations 的 PostgreSQL 文本类型更正 JPA Annotation的主要内容,如果未能解决你的问题,请参考以下文章

hibernate中的annotations是啥意思

Hibernate Annotations 注解

spring 4 mvc + Hibernate + Annotations 配置堆内存问题

hibernate.cfg.xml 中如何设置 hbm.xml 和 Annotations 的 mapping

Hibernate Annotations/JPA 映射集合

使用 Hibernate Annotations 映射枚举类型