JPA2: без учета регистра, как соответствие в любом месте
Я использую ограничения Hibernate в JPA 1.0 (драйвер Hibernate ). Там определено Restrictions.ilike("column","keyword", MatchMode.ANYWHERE)
который проверяет, соответствует ли ключевое слово столбцу в любом месте,и оно нечувствительно к регистру.
теперь я использую JPA 2.0 с eclipselink в качестве драйвера, поэтому я должен использовать" ограничения " сборки JPA 2.0. Я нашел CriteriaBuilder
и метод like
, Я также узнал, как сделать его соответствие в любом месте (хотя это aweful и руководство ), но до сих пор я не понял, как это сделать без учета регистра.
есть мое текущее решение aweful:
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.
или может кто-нибудь помочь мне, как изменить мое решение, чтобы быть нечувствительным к регистру, пожалуйста?
Спасибо большое.
6 ответов
сначала это может показаться немного неудобным, но это безопасно для типа. Построение запросов из строк не является, поэтому вы замечаете ошибки во время выполнения, а не во время компиляции. Вы можете сделать запросы более читаемыми, используя отступы или делая каждый шаг отдельно, вместо написания всего предложения 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() + "%"
)
)
);
эта работа для меня :
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();
как я прокомментировал в (в настоящее время) принятом ответе, есть ловушка, использующая с одной стороны СУБД' lower()
функция и, с другой стороны, java String.toLowerCase()
поскольку оба метода не гарантируют предоставление одного и того же вывода для одной и той же входной строки.
я, наконец, нашел гораздо более безопасное (но не пуленепробиваемое) решение, которое заключается в том, чтобы позволить СУБД делать все снижение, используя буквальное выражение:
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 + "%")
)
)
);
Edit:
Как @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
тем не менее, я предпочитаю мое решение, поскольку оно не сравнивает результат двух разных функций, которые должны работать одинаково. Я применяю ту же самую функцию ко всем массивам символов, чтобы сравнение выходных данных стало более "стабильным".
пуленепробиваемое решение включите локаль так, чтобы SQL lower()
стать в состоянии правильно понижать акцентированные символы. (Но это выходит за рамки моего скромного знания)
*значение Db с PostgreSQL 9.5.1 с " C " locale
проще и эффективнее применять случае insensitity в базе данных, чем СПД.
-
в стандартах 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
-
Определение
-
в 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 на что-либо другое, кроме двоичного [с необязательным _CI или _AI] вызывает сортировку для использования полного сканирования таблицы, независимо от пути, выбранного оптимизатором. BINARY является исключением, поскольку индексы строятся в соответствии с двоичным порядком ключей. Таким образом, оптимизатор может использовать индекс для удовлетворения предложения ORDER BY, когда NLS_SORT имеет значение BINARY. Если nls_sort установлен для любой лингвистической сортировки оптимизатор должен включать полную проверку таблицы и полную сортировку в план выполнения.
или, если nls_comp имеет лингвистическое значение, как указано выше, параметры сортировки могут применяться локально к индексированным столбцам, а не глобально по всей базе данных:
CREATE INDEX emp_ci_index ON emp (NLSSORT(emp_name, 'NLS_SORT=BINARY_CI'));
ссылки: Ora 11g лингвистическая сортировка и поиск строк ORA 11g настройка среды поддержки глобализации
отчаянный обходной путь для 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);
}
}
это хрупкий и уродливый обходной путь для выполнения нечувствительных к регистру операций с базой данных OpenJPA и Postgresql. Он заменяет оператор LIKE на оператор ILIKE в сгенерированном SQL.
жаль, что OpenJPA DBDictionary не позволяет изменять имена операторов.
пожалуйста, подумайте об использовании
CriteriaBuilder.like(Expression<String> x, Expression<String> pattern, char escapeChar);
для сопоставления в любом месте.