Postgresql CITEXT数据类型问题JPA Hibernate

Bil*_*mus 8 postgresql hibernate jpa postgresql-9.1

我在使用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")
Run Code Online (Sandbox Code Playgroud)

在数据库中插入昵称:

insert into test values('testNick')
Run Code Online (Sandbox Code Playgroud)

然后运行以下代码:

String nickname = "testNick";

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

这返回'true'(即数据库中已存在'testNick').

如果我做这个任务

String nickname = "testnick"; //(lower case 'N') 
Run Code Online (Sandbox Code Playgroud)

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

由于列是CITEXT,它应该再次返回'true'.不区分大小写的文本.

使用JPA和Hibernate.有人有什么想法?

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

问候.

Cra*_*ger 12

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

发生了什么

猜测,您的JPA实现是text在创建参数化语句时显式指定参数的类型.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)
Run Code Online (Sandbox Code Playgroud)

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

regress=# SELECT * FROM citest WHERE x = 'FRED';
  x   
------
 FRED
 FrEd
(2 rows)
Run Code Online (Sandbox Code Playgroud)

...但是citext明确text键入的文字之间的比较会将citext参数转换为text使用citext隐式强制转换为文本,然后进行text=text 区分大小写的比较:

regress=# SELECT * FROM citest WHERE x = 'FRED'::text;
  x   
------
 FRED
(1 row)
Run Code Online (Sandbox Code Playgroud)

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

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

其中类型被指定为text绑定参数时,因为Hibernate"知道"字符串是什么text.

换句话说,您需要通过PgJDBC将Hibernate明确指定citext为查询的参数类型数据类型,从而产生如下内容:

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

请注意预citext准备语句的显式类型参数.这将是......有趣......要做,尤其是因为PgJDBC对这种citext类型一无所知.您必须为使用PgJDBC的Hibernate编写自定义数据类型处理程序setObject; 即使这样,Java和Pg之间也会出现运算符一致性问题(见下文).

IMO你会关闭使用传统的区分大小写类型好多了,lower(),ILIKE,等.

Hibernate也可能依赖于PgJDBC对列案例敏感性的看法.至少从9.2开始,PgJDBC对这种citext类型一无所知,因此当被问到时,它总是会说"是的,这是区分大小写的".

追踪

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

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

Hibernate也可能依赖于PgJDBC对列案例敏感性的看法.至少从9.2开始,PgJDBC对这种citext类型一无所知,因此当被问到时,它总是会说"是的,这是区分大小写的".

操作员一致性困难

警告:该citext类型不会影响Hibernate在文本从数据库中传出后的工作方式.例如,它对该String.equals方法没有任何影响.您需要告诉Hibernate您希望它将文本视为不区分大小写.否则,如果你有一个textvarchar主/外键,你可以得到Hibernate要求密钥的情况"FRED",它会"FrEd"回来,并且很混乱因为DB返回了一个不相等的密钥 - 根据Hibernate - 给它一个被要求.如果在实体中包含citext-backed字符串equalshashCode实现,则会出现类似的奇怪现象.

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

只要您不使用citext密钥或citextequals和中包含值,您可能会避免混淆Hibernate hashCode.


Nik*_*dun 8

我为了未来的读者而回答.问题是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>
Run Code Online (Sandbox Code Playgroud)