JPA2: нечувствителен к регистру, как и везде - PullRequest
45 голосов
/ 02 января 2011

Я использовал ограничения Hibernate в JPA 1.0 (драйвер Hibernate).Определено Restrictions.ilike("column","keyword", MatchMode.ANYWHERE), которое проверяет, совпадает ли ключевое слово со столбцом в любом месте, и нечувствительно к регистру.

Теперь я использую JPA 2.0 с EclipseLink в качестве драйвера, поэтому мне нужно использовать встроенную JPA 2.0 «Ограничения».Я нашел CriteriaBuilder и метод like, я также узнал, как сделать так, чтобы он совпадал где угодно (хотя это ужасно и вручную), но все же я не понял, как сделать это без учета регистра.

В настоящее время существует мое ужасное решение:

CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<User> query = builder.createQuery(User.class);
EntityType<User> type = em.getMetamodel().entity(User.class);
Root<User> root = query.from(User.class);

// Where   
// important passage of code for question  
query.where(builder.or(builder.like(root.get(type.getDeclaredSingularAttribute("username", String.class)), "%" + keyword + "%"),
        builder.like(root.get(type.getDeclaredSingularAttribute("firstname", String.class)), "%" + keyword + "%"),
        builder.like(root.get(type.getDeclaredSingularAttribute("lastname", String.class)), "%" + keyword + "%")
        ));

// Order By
query.orderBy(builder.asc(root.get("lastname")),
            builder.asc(root.get("firstname")));

// Execute
return em.createQuery(query).
            setMaxResults(PAGE_SIZE + 1).
            setFirstResult((page - 1) * PAGE_SIZE).
            getResultList();

Вопросы:

Есть ли какая-либо функция, как в драйвере Hibernate?

Правильно ли я использую критерии JPA 2.0?Это неудобное и неудобное решение по сравнению с Hibernate Restrictions.

Или кто-нибудь может мне помочь, как изменить мое решение, чтобы оно не учитывало регистр, пожалуйста?

Большое спасибо.

Ответы [ 6 ]

82 голосов
/ 04 января 2011

Поначалу это может показаться немного неловким, но это безопасно для типов.Построение запросов из строк не так, поэтому вы замечаете ошибки во время выполнения, а не во время компиляции.Вы можете сделать запросы более читабельными, используя отступы или выполняя каждый шаг отдельно, вместо того, чтобы писать целое предложение WHERE в одной строке.

Чтобы сделать запрос нечувствительным к регистру, преобразуйте и ключевое слово, и поле сравненияв нижний регистр:

query.where(
    builder.or(
        builder.like(
            builder.lower(
                root.get(
                    type.getDeclaredSingularAttribute("username", String.class)
                )
            ), "%" + keyword.toLowerCase() + "%"
        ), 
        builder.like(
            builder.lower(
                root.get(
                    type.getDeclaredSingularAttribute("firstname", String.class)
                )
            ), "%" + keyword.toLowerCase() + "%"
        ), 
        builder.like(
            builder.lower(
                root.get(
                    type.getDeclaredSingularAttribute("lastname", String.class)
                )
            ), "%" + keyword.toLowerCase() + "%"
        )
    )
);
7 голосов
/ 24 ноября 2017

Как я прокомментировал в (в настоящее время) принятом ответе, есть ловушка, использующая, с одной стороны, функцию lower() СУБД, а с другой стороны, String.toLowerCase() java, так как оба метода не гарантируют одинаковый вывод длята же строка ввода.

Наконец-то я нашел гораздо более безопасное (но не пуленепробиваемое) решение, которое позволяет СУБД выполнять все понижения, используя буквальное выражение:

builder.lower(builder.literal("%" + keyword + "%")

Так чтоПолное решение будет выглядеть так:

query.where(
    builder.or(
        builder.like(
            builder.lower(
                root.get(
                    type.getDeclaredSingularAttribute("username", String.class)
                )
            ), builder.lower(builder.literal("%" + keyword + "%")
        ), 
        builder.like(
            builder.lower(
                root.get(
                    type.getDeclaredSingularAttribute("firstname", String.class)
                )
            ), builder.lower(builder.literal("%" + keyword + "%")
        ), 
        builder.like(
            builder.lower(
                root.get(
                    type.getDeclaredSingularAttribute("lastname", String.class)
                )
            ), builder.lower(builder.literal("%" + keyword + "%")
        )
    )
);

Редактировать:
Когда @cavpollo попросил меня привести пример, мне пришлось дважды подумать о своем решении и понять, что оно не намного безопаснее, чем принятый ответ:

DB value* | keyword | accepted answer | my answer
------------------------------------------------
elie     | ELIE    | match           | match
Élie     | Élie    | no match        | match
Élie     | élie    | no match        | no match
élie     | Élie    | match           | no match

Тем не менее, я предпочитаю свое решение, так как оно не сравнивает результаты двух разных функций, которые должны работать одинаково.Я применяю одну и ту же функцию ко всем массивам символов, чтобы сравнение результатов стало более «стабильным».

Пуленепробиваемое решение будет включать в себя локаль, так что lower() SQL сможет корректно опускать символы с ударением.(Но это выходит за рамки моих скромных знаний)

* Значение в дБ с PostgreSQL 9.5.1 с языком 'C'

7 голосов
/ 07 мая 2014

Эта работа для меня:

CriteriaBuilder critBuilder = em.getCriteriaBuilder();

CriteriaQuery<CtfLibrary> critQ = critBuilder.createQuery(Users.class);
Root<CtfLibrary> root = critQ.from(Users.class);

Expression<String> path = root.get("lastName");
Expression<String> upper =critBuilder.upper(path);
Predicate ctfPredicate = critBuilder.like(upper,"%stringToFind%")
critQ.where(critBuilder.and(ctfPredicate));
em.createQuery(critQ.select(root)).getResultList();
3 голосов
/ 30 мая 2013

Проще и эффективнее обеспечить соблюдение регистра в базе данных, чем JPA.

  1. В соответствии со стандартами SQL 2003, 2006, 2008 это можно сделать, добавив COLLATE SQL_Latin1_General_CP1_CI_AS ИЛИ COLLATE latin1_general_cs к следующему:

    • Определение столбца

      CREATE TABLE <table name> (
        <column name> <type name> [DEFAULT...] 
                                  [NOT NULL|UNIQUE|PRIMARY KEY|REFERENCES...]
                                  [COLLATE <collation name>], 
        ...
      )
      
    • Определение домена

      CREATE DOMAIN <domain name> [ AS ] <data type>
        [ DEFAULT ... ] [ CHECK ... ] [ COLLATE <collation name> ]
      
    • Определение набора символов

      CREATE CHARACTER SET <character set name>
      [ AS ] GET <character set name> [ COLLATE <collation name> ]
      

    Полное описание приведено выше: http://savage.net.au/SQL/sql-2003-2.bnf.html#column%20definition http://dev.mysql.com/doc/refman/5.1/en/charset-table.html http://msdn.microsoft.com/en-us/library/ms184391.aspx

  2. В Oracle можно установить параметры сеанса / конфигурации NLS

     SQL> ALTER SESSION SET NLS_COMP=LINGUISTIC;
     SQL> ALTER SESSION SET NLS_SORT=BINARY_CI;
     SQL> SELECT ename FROM emp1 WHERE ename LIKE 'McC%e';
    
     ENAME
     ----------------------
     McCoye
     Mccathye
    

    Или в init.ora (или в специфическом для ОС имени для файла параметров инициализации):

    NLS_COMP=LINGUISTIC
    NLS_SORT=BINARY_CI
    

    Двоичные сортировки могут быть без учета регистра или без акцента.Когда вы указываете BINARY_CI в качестве значения для NLS_SORT, он обозначает сортировку с учетом акцента и без учета регистра.BINARY_AI обозначает двоичную сортировку без учета ударения и без учета регистра.Возможно, вы захотите использовать двоичную сортировку, если порядок двоичной сортировки набора символов подходит для используемого набора символов.Используйте параметр сеанса NLS_SORT, чтобы указать сортировку без учета регистра или без учета акцента:

    Append _CI to a sort name for a case-insensitive sort.
    Append _AI to a sort name for an accent-insensitive and case-insensitive sort. 
    

    Например, вы можете установить для NLS_SORT значения следующих типов:

    FRENCH_M_AI
    XGERMAN_CI
    

    НастройкаNLS_SORT для чего-либо, кроме BINARY [с необязательными _CI или _AI] заставляет сортировку использовать полное сканирование таблицы, независимо от пути, выбранного оптимизатором.BINARY является исключением, потому что индексы строятся в соответствии с двоичным порядком ключей.Таким образом, оптимизатор может использовать индекс для удовлетворения предложения ORDER BY, когда для NLS_SORT установлено значение BINARY.Если для NLS_SORT задана какая-либо лингвистическая сортировка, оптимизатор должен включить в план выполнения полное сканирование таблицы и полную сортировку.

    Или, если для NLS_COMP задано значение LINGUISTIC, как указано выше, можно применить настройки сортировкилокально в индексированные столбцы, а не глобально по всей базе данных:

    CREATE INDEX emp_ci_index ON emp (NLSSORT(emp_name, 'NLS_SORT=BINARY_CI'));
    

    Ссылка: ORA 11g Лингвистическая сортировка и поиск строк ORA 11g Настройка среды поддержки глобализации

1 голос
/ 28 мая 2014

Отчаянный обходной путь для OpenJPA 2.3.0 и Postgresql

public class OpenJPAPostgresqlDictionaryPatch extends PostgresDictionary {

  @Override
  public SQLBuffer toOperation(String op, SQLBuffer selects, SQLBuffer from, SQLBuffer where, SQLBuffer group, SQLBuffer having, SQLBuffer order, boolean distinct, long start, long end, String forUpdateClause, boolean subselect) {
    String whereSQL = where.getSQL();
    int p = whereSQL.indexOf("LIKE");
    int offset = 0;
    while (p != -1) {
      where.replaceSqlString(p + offset, p + offset + 4, "ILIKE");
      p = whereSQL.indexOf("LIKE", p + 1);
      offset++;
    }
    return super.toOperation(op, selects, from, where, group, having, order, distinct, start, end, forUpdateClause, subselect);
  }

}

Это хрупкий и уродливый обходной путь для выполнения операции LIKE без учета регистра с базой данных OpenJPA и Postgresql.Он заменяет оператор LIKE на оператор ILIKE в сгенерированном SQL.

Очень плохо, что OpenJPA DBDictionary не позволяет изменять имена операторов.

0 голосов
/ 31 августа 2016

Пожалуйста, рассмотрите возможность использования

CriteriaBuilder.like(Expression<String> x, Expression<String> pattern, char escapeChar);

для сопоставления в любом месте.

...