12

我在使用 JPA 和 Hibernate 的 PostgreSQL 中使用 CITEXT 数据类型时遇到了困难。CITEXT 应该提供不区分大小写的文本数据类型,但是当与 JPA/Hibernate 一起使用时,它不会以不区分大小写的方式运行。有没有其他人遇到过这个问题或知道解决方法?我已经看到一些关于 JDBC 问题的提及(但非常非常少),但这至少可以追溯到一年前并且不是很清楚。

我在 postgres 9.1 中有一个“昵称”列定义为 citext。我刚刚做了一个测试,看看它是否可以使用命名查询找到一行:

create table test(
    nickname citext
)

@NamedQuery(name = "Person.findByNickname", 
            query = "SELECT p 
                     FROM Person p 
                     WHERE p.nickname = :nickname")

在数据库中插入昵称:

insert into test values('testNick')

然后运行这段代码:

String nickname = "testNick";

Query q = em.createNamedQuery("Person.findByNickname");
q.setParameter("nickname", nickname);
if (q.getResultList().isEmpty()) {
    return (false);
}
return (true);

这将返回“true”(即数据库中已经有一个“testNick”)。

如果我做这个任务

String nickname = "testnick"; //(lower case 'N') 

并再次运行它返回'false'。

由于该列是 CITEXT,它应该再次返回“true”。即不区分大小写的文本。

使用 JPA 和休眠。有人有什么想法吗?

同时,我已将该列改回 varchar 并为小写创建了一个功能索引。我现在必须创建一个本机查询来使用数据库函数进行搜索。想知道是否有一种方法我可以不必这样做来维护数据库抽象。

问候。

4

2 回答 2

15

citext提供在数据库中使用的不区分大小写的运算符,以及其他 citext 值

发生了什么

text猜测一下,您的 JPA 实现在创建参数化语句时明确指定参数的类型。citext没有定义citext = text运算符,因此 PostgreSQL 将citextto转换为text并使用区分大小写的text = text运算符。实际上,比较citexttext区分大小写的。

这就是我认为正在发生的事情。给定虚拟数据:

regress=# CREATE EXTENSION citext;
regress=# CREATE TABLE citest ( x citext );
regress=# INSERT INTO citest(x) VALUES ('FRED'), ('FrEd');
regress=# SELECT * FROM citest;
  x   
------
 FRED
 FrEd
(2 rows)

... citext 与未知字符串文字的比较将被解释为citext=citext不区分大小写:

regress=# SELECT * FROM citest WHERE x = 'FRED';
  x   
------
 FRED
 FrEd
(2 rows)

...但是citext与显式text类型的文字之间的比较会将citext参数转换为textusingcitext的隐式转换为文本,然后进行text=text 区分大小写的比较:

regress=# SELECT * FROM citest WHERE x = 'FRED'::text;
  x   
------
 FRED
(1 row)

或者更确切地说,Hibernate 所做的将更接近于:

regress=# PREPARE blah(text) AS SELECT * FROM citest WHERE x = $1;
PREPARE
regress=# EXECUTE blah('FRED');
  x   
------
 FRED
(1 row)

其中类型text在绑定参数时指定,因为 Hibernate“知道”字符串是text.

换句话说,您需要让 Hibernate 通过 PgJDBC 显式指定citext数据类型作为查询的参数类型,结果如下:

regress=# PREPARE blah(citext) AS SELECT * FROM citest WHERE x = $1;
PREPARE
regress=# EXECUTE blah('FRED');
  x   
------
 FRED
 FrEd
(2 rows)

注意citext准备好的语句的显式类型参数。那将是……有趣的……做,特别是因为 PgJDBC 对类型一无所知citext。您必须为使用 PgJDBC 的 Hibernate 编写自定义数据类型处理程序setObject;即便如此,您也会在 Java 和 Pg 之间遇到运算符一致性问题(见下文)。

lower()IMO 使用传统的区分大小写的类型和,ILIKE等会更好。

Hibernate 也有可能依赖于 PgJDBC 告诉它的关于列区分大小写的内容。至少在 9.2-devel PgJDBC 对类型一无所知citext,所以当被问到时它总是说“是的,那是区分大小写的”。

追踪

如果没有看到 JPA 运行的实际查询,就很难确定发生了什么。尝试设置log_statement = 'all'postgresql.conf. 然后SIGHUPpostmaster,使用pg_ctl reload,或者重启 Pg 以使更改生效。

重新运行测试并检查日志。测试您看到的查询psql以观察结果。如果您不确定发生了什么,请向他们更新您的问题。如果您更新还包括您的 Hibernate 版本和您的 PgJDBC 版本。

Hibernate 也有可能依赖于 PgJDBC 告诉它的关于列区分大小写的内容。至少在 9.2-devel PgJDBC 对类型一无所知citext,所以当被问到时它总是说“是的,那是区分大小写的”。

算子一致性困难

警告citext一旦文本从数据库中出来,该类型就不会影响 Hibernate 如何处理文本。例如,它不会对String.equals方法产生任何影响。您需要告诉Hibernate您希望它将文本视为不区分大小写。否则,如果您有一个textvarchar主/外键,您可能会遇到 Hibernate 要求该键的情况"FRED",它会"FrEd"返回,并且非常困惑,因为数据库返回的键不等于 - 根据 Hibernate - 它的一个被要求。如果您在实体citext中包含 -backed 字符串equalshashCode实现,则会发生类似的怪事。

不幸的是,JPA 似乎没有在@Column映射中指定列是否区分大小写的注释属性。Java无论如何都没有不区分大小写的字符串数据类型的概念,因此即使 JPA 确实指定了它也不会有很多好处。

只要您不使用citextfor 键或在and中包含citext值,您可能会避免过度混淆 Hibernate 。equalshashCode

于 2012-08-20T02:21:02.523 回答
12

我是为了未来的读者而回答的。问题是 JDBC 自动将 String 参数转换为 varchar,从而强制比较区分大小写。可以通过将 JDBC 连接参数“ stringtype ”设置为“ unspecified ”来更改此行为。

如果您使用 JPA,请将以下内容放入数据源配置中:

<datasource jndi-name="java:jboss/datasources/testDS"
    pool-name="test" enabled="true"
    use-java-context="true" spy="true">
    <connection-url>jdbc:postgresql://localhost:5432/postgres</connection-url>
    <driver>postgresql</driver>
    <connection-property name="stringtype">unspecified</connection-property>
    <security>
        <user-name>postgres</user-name>
        <password>******</password>
    </security>
</datasource>
于 2014-05-18T12:04:37.753 回答