我在使用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和Hibernate。有人有什么想法吗?
与此同时,我已将列更改回varchar并为小写创建了一个功能索引。我现在必须创建一个本机查询来使用数据库函数进行搜索。想知道是否有一种方法我不能这样做来维护数据库抽象。
问候。
答案 0 :(得分:12)
citext
提供了不区分大小写的运算符,用于在数据库中使用,以及其他citext值。
猜测,您的JPA实现在创建参数化语句时明确指定参数的类型为text
。 citext
未定义citext = text
运算符,因此PostgreSQL会将citext
强制转换为text
并使用区分大小写的text = text
运算符。实际上,将citext
与text
进行比较是区分大小写的。
这就是我认为正在发生的事情。给出虚拟数据:
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
隐式转换将text
参数转换为citext
发短信,然后进行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的setObject
的Hibernate编写自定义数据类型处理程序;即使这样,Java和Pg之间也会出现运算符一致性问题(见下文)。
IMO你会更好地使用传统区分大小写的类型和lower()
,ILIKE
等。
Hibernate也可能依赖于PgJDBC对列案例敏感性的看法。至少从9.2开始,PgJDBC对citext
类型一无所知,因此当被问到时,它总是会说“是的,这是区分大小写的”。
如果没有看到JPA运行的实际查询,很难确定发生了什么。尝试在log_statement = 'all'
中设置postgresql.conf
。然后SIGHUP
邮寄主管,使用pg_ctl reload
,或重新启动Pg以使更改生效。
重新运行测试并检查日志。测试您在psql
中看到的查询以观察结果。如果您不确定发生了什么,请与他们一起更新您的问题。如果你更新还包括你的Hibernate版本和你的PgJDBC版本。
Hibernate也可能依赖于PgJDBC对列案例敏感性的看法。至少从9.2开始,PgJDBC对citext
类型一无所知,因此当被问到时,它总是会说“是的,这是区分大小写的”。
警告:citext
类型不会影响Hibernate在文本出现后如何处理数据库。例如,它对String.equals
方法没有任何影响。您需要告诉 Hibernate 您希望它将文本视为不区分大小写。否则,如果您有text
或varchar
主/外键,您可以获得Hibernate请求密钥"FRED"
的情况,它会返回"FrEd"
,并且非常困惑因为数据库返回了一个不相等的密钥 - 根据Hibernate - 它被要求的密钥。如果您在实体的citext
和equals
实施中包含hashCode
支持的字符串,则会出现类似的奇怪现象。
不幸的是,JPA似乎没有在@Column
映射中指定注释属性,以确定列是否区分大小写。无论如何,Java doesn't have the concept of a case-insensitive string data type,即使JPA指定它也不会有好处。
只要您不使用citext
密钥或在citext
和equals
中包含hashCode
值,您就可以避免混淆Hibernate。
答案 1 :(得分:6)
我为了未来的读者而回答。问题是JDBC自动将String参数强制转换为varchar,因此强制比较区分大小写。通过将JDBC连接参数“ stringtype ”设置为“未指定”可以更改此行为。
如果您使用的是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>