Postgresql CITEXT数据类型问题JPA休眠

46qrfjad  于 2022-11-23  发布在  PostgreSQL
关注(0)|答案(3)|浏览(254)

我在使用JPA和Hibernate的PostgreSQL中使用CITEXT数据类型时遇到了困难。CITEXT应该提供一个不区分大小写的文本数据类型,但是当与JPA/Hibernate一起使用时,它的行为并不区分大小写。其他人有没有遇到过这个问题或者知道解决它的方法?我看到过一些关于JDBC问题的报道(但是非常非常少),但是这至少要追溯到一年前,而且不是很清楚。
我在postgres 9.1中将“nickname”列定义为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和Hibernate。有人有什么想法吗?
同时,我已经将列改回varchar,并为小写创建了一个函数索引。我现在必须创建一个本地查询,以使用数据库函数进行搜索。我想知道是否有一种方法可以不必这样做来维护数据库抽象。
祝你好运

mlnl4t2r

mlnl4t2r1#

citext提供了不区分大小写的运算符,以便在数据库中使用 * 和其他citext值 *。
发生了什么
据猜测,您的JPA实现在创建参数化语句时将参数的类型显式指定为textcitext没有定义citext = text运算符,因此PostgreSQL将citext转换为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的隐式转换将citext参数转换为text,然后执行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类型一无所知。您必须为Hibernate编写一个自定义数据类型处理程序,该处理程序使用PgJDBC的setObject;即使这样,Java和Pg之间也会存在操作符一致性问题(参见下文)。
我想你最好使用传统的区分大小写的类型和lower()ILIKE等。
Hibernate也有可能依赖于PgJDBC告诉它的列大小写敏感性。至少在9.2版本中--devel PgJDBC不知道任何关于citext类型的信息,所以当被询问时,它总是说"是的,这是大小写敏感的"。

追踪

如果没有看到JPA运行的实际查询,很难确定这是怎么回事。尝试在postgresql.conf中设置log_statement = 'all'。然后SIGHUP是邮局主管,使用pg_ctl reload,或者重新启动Pg以使更改生效。
重新运行您的测试并检查日志。测试您在psql中看到的查询以观察结果。如果您不确定发生了什么,请使用它们更新您的问题。如果您更新,还应包括Hibernate版本和PgJDBC版本。
Hibernate也有可能依赖于PgJDBC告诉它的列大小写敏感性。至少在9.2版本中--devel PgJDBC不知道任何关于citext类型的信息,所以当被询问时,它总是说"是的,这是大小写敏感的"。

操作员一致性困难

    • 警告**:citext类型不会影响Hibernate处理数据库中的文本的方式,例如,您需要告诉 * Hibernate * 您希望它将文本视为不区分大小写。否则,如果您有textvarchar主/外键,您可以得到Hibernate请求关键字"FRED",它返回"FrEd",并且非常困惑,因为DB返回的键与请求的键不相等-根据Hibernate的说法。如果在实体的equalshashCode实现中包含以citext为后盾的字符串,也会出现类似的奇怪情况。

不幸的是,JPA似乎没有在@ColumnMap中指定注解属性,以确定列是否区分大小写。Java doesn't have the concept of a case-insensitive string data type无论如何都是如此,所以即使JPA指定了它,也不会有太大的用处。
只要不使用citext作为键,或者在equalshashCode中包含citext值,就可以避免混淆Hibernate。

kxkpmulp

kxkpmulp2#

我是为了将来的读者而回答的。问题是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>
hfyxw5xn

hfyxw5xn3#

根据克雷格Ringer的回答,我明白我们应该比较一下CITEXT和CITEXT。将输入转换为CITEXT对我来说很有效(我在Spring Data JPA中进行了测试,其中查询在@Query(value = 'YOUR QUERY HERE',nativeQuery = true)下使用)
所以,下面的代码应该可以按照我的测试运行。如果可以的话,它甚至在nickname列上使用索引(当然,是否使用索引取决于查询优化器)

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

相关问题