Проблема с типом данных Postgresql CITEXT, JPA, гибернация

Мне трудно использовать тип данных CITEXT в PostgreSQL с использованием JPA и Hibernate. CITEXT должен обеспечивать нечувствительный к регистру текстовый тип данных, но при использовании с JPA / Hibernate он не ведет себя нечувствительным к регистру способом. У кого-нибудь еще была эта проблема или есть способ ее обойти? Я видел некоторые упоминания (но очень, очень мало) о проблеме с JDBC, но это было как минимум год назад и было не очень ясно.

У меня есть столбец «ник», определенный как citext в postgres 9.1. Я просто проверил, сможет ли он найти строку, используя именованный запрос как таковой:

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);

Это возвращает «истину» (т.е. в базе данных уже есть «testNick»).

Если я сделаю это задание

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

и запустите его снова, он вернет false.

Поскольку столбец - CITEXT, он должен снова вернуть "true". т.е. текст без учета регистра.

Использование JPA и Hibernate. У кого-нибудь есть мысли?

Тем временем я снова изменил столбец на varchar и создал функциональный индекс для нижнего регистра. И теперь мне нужно создать собственный запрос для поиска с использованием функций базы данных. Хотел бы узнать, есть ли способ, которым я не могу этого сделать, чтобы поддерживать абстракцию базы данных.

С Уважением.


person Bill Rosmus    schedule 20.08.2012    source источник


Ответы (2)


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. Вам нужно будет написать обработчик пользовательского типа данных для Hibernate, который использует setObject PgJDBC; даже тогда у вас будут проблемы с согласованностью операторов между Java и Pg (см. ниже).

ИМО, вам будет намного лучше использовать традиционные типы с учетом регистра и 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 не поддерживает в любом случае имеют концепцию строкового типа данных без учета регистра, поэтому он не принесет много пользы, даже если JPA определит его.

Вероятно, вы избежите слишком большой путаницы с Hibernate, если не будете использовать citext для ключей и не включать citext значения в equals и hashCode.

person Craig Ringer    schedule 20.08.2012
comment
Хорошая идея. Похоже, что прямо сейчас это будет PITA для устранения неполадок, поэтому я просто воспользуюсь упомянутой мною работой. То есть используйте (ниже) в собственном запросе и используйте функциональный индекс. Я думаю, что в любом случае CITEXT делает это под капотом. Текст и CITEXT по расширению в любом случае - это просто неограниченные символы varchars в Postgres, поэтому он делает его PITA, но это то, что есть. Когда у меня будет время, я проведу повторный тест, следуя вашим идеям, и смотрю журналы (я только что закомментировал проблемный код, чтобы я мог это сделать). - person Bill Rosmus; 20.08.2012
comment
@BillR Ответ обновлен с более подробным объяснением и подробностями. Надеюсь, поможет. - person Craig Ringer; 20.08.2012

Отвечаю ради будущих читателей. Проблема в том, что 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>
person Nikša Baldun    schedule 18.05.2014
comment
Пользователи Spring Boot могут использовать следующее свойство: spring.datasource.tomcat.connection-properties=stringtype=unspecified Спасибо, что поделились этим! - person Andrew; 29.03.2018
comment
В Spring Boot 2 используйте spring.datasource.hikari.data-source-properties.stringtype=unspecified, если вы полагаетесь на источник данных по умолчанию, который теперь является пулом соединений Hikari. - person mistahenry; 04.12.2019