我一直在JPA 1.0(Hibernate驱动程序)中使用Hibernate Restrictions。已定义Restrictions.ilike("column","keyword", MatchMode.ANYWHERE)
,用于测试关键字是否与列匹配,并且不区分大小写。
现在,我使用JPA 2.0和EclipseLink作为驱动程序,因此我必须使用“Restrictions”内置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相比,这是一个尴尬和不舒服的解决方案。
或者,有人可以帮我解决如何将我的解决方案更改为不区分大小写吗?
非常感谢。
答案 0 :(得分:81)
起初看起来有点尴尬,但它是类型安全的。从字符串构建查询不是,因此您在运行时而不是在编译时发现错误。您可以通过使用缩进或单独执行每个步骤来使查询更具可读性,而不是在一行中编写整个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() + "%"
)
)
);
答案 1 :(得分:7)
这项工作对我来说:
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();
答案 2 :(得分:7)
正如我在(目前)接受的答案中所评论的那样,一方面使用DBMS'lower()
函数,另一方面使用java String.toLowerCase()
,因为这两种方法都不能保证提供相同输入字符串的相同输出。
我终于找到了一个更安全(但不是防弹)的解决方案,让DBMS使用文字表达式完成所有降级:
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
尽管如此,我更喜欢我的解决方案,因为它没有比较两个不同功能的结果。我将相同的函数应用于所有字符数组,以便比较输出变得更“稳定”。
防弹解决方案将涉及区域设置,以便SQL的lower()
能够正确地降低重音字符。 (但这超出了我的谦虚知识)
* Db值
答案 3 :(得分:3)
比JPA更容易,更有效地在数据库中强制执行不区分大小写。
根据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设置为除BINARY之外的任何内容[使用可选的_CI或_AI]会导致排序使用全表扫描,而不管优化程序选择的路径如何。 BINARY是例外,因为索引是根据键的二进制顺序构建的。因此,当NLS_SORT设置为BINARY时,优化器可以使用索引来满足ORDER BY子句。如果将NLS_SORT设置为任何语言排序,则优化程序必须在执行计划中包括完整表扫描和完整排序。
或者,如果NLS_COMP设置为LINGUISTIC,如上所述,那么排序设置可以本地应用于索引列,而不是全局应用于数据库:
CREATE INDEX emp_ci_index ON emp (NLSSORT(emp_name, 'NLS_SORT=BINARY_CI'));
参考:ORA 11g Linguistic Sorting and String Searching ORA 11g Setting Up a Globalization Support Environment
答案 4 :(得分:1)
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操作,这是一个脆弱而丑陋的解决方法。它在生成的SQL中将LIKE运算符替换为ILIKE运算符。
OpenJPA DBDictionary不允许更改运营商名称太糟糕了。
答案 5 :(得分:0)
请考虑使用
rule-result
在任何地方进行匹配。
答案 6 :(得分:0)
如果您使用支持ilike
的Postgres之类的数据库,则该数据库提供的性能要好于使用lower()
函数,而所提供的解决方案均无法正确解决该问题。
解决方案可以是自定义功能。
您正在编写的HQL查询是:
SELECT * FROM User WHERE (function('caseInSensitiveMatching', name, '%test%')) = true
caseInSensitiveMatching
是自定义函数的函数名。 name
是要与之进行比较的属性的路径,而%test%
是要与之进行匹配的模式。
目标是将HQL查询转换为以下SQL查询:
SELECT * FROM User WHERE (name ilike '%test%') = true
要实现此目的,我们必须使用注册的自定义功能来实现自己的方言:
public class CustomPostgreSQL9Dialect extends PostgreSQL9Dialect {
/**
* Default constructor.
*/
public CustomPostgreSQL9Dialect() {
super();
registerFunction("caseInSensitiveMatching", new CaseInSensitiveMatchingSqlFunction());
}
private class CaseInSensitiveMatchingSqlFunction implements SQLFunction {
@Override
public boolean hasArguments() {
return true;
}
@Override
public boolean hasParenthesesIfNoArguments() {
return true;
}
@Override
public Type getReturnType(Type firstArgumentType, Mapping mapping) throws QueryException {
return StandardBasicTypes.BOOLEAN;
}
@Override
public String render(Type firstArgumentType, @SuppressWarnings("rawtypes") List arguments,
SessionFactoryImplementor factory) throws QueryException {
if (arguments.size() != 2) {
throw new IllegalStateException(
"The 'caseInSensitiveMatching' function requires exactly two arguments.");
}
StringBuilder buffer = new StringBuilder();
buffer.append("(").append(arguments.get(0)).append(" ilike ").append(arguments.get(1)).append(")");
return buffer.toString();
}
}
}
在我们的情况下,与使用lower
函数的版本相比,上述优化使性能提高了40倍,因为Postgres可以利用相应列上的索引。在我们的情况下,查询执行时间可以从4.5秒减少到100毫秒。
lower
阻止了索引的有效使用,因此它要慢得多。
答案 7 :(得分:0)
要将 approach of Thomas Hunziker 与 hibernate 的标准构建器一起使用,您可以提供如下所示的特定谓词实现
public class ILikePredicate extends AbstractSimplePredicate implements Serializable {
private final Expression<String> matchExpression;
private final Expression<String> pattern;
public ILikePredicate(
CriteriaBuilderImpl criteriaBuilder,
Expression<String> matchExpression,
Expression<String> pattern) {
super(criteriaBuilder);
this.matchExpression = matchExpression;
this.pattern = pattern;
}
public ILikePredicate(
CriteriaBuilderImpl criteriaBuilder,
Expression<String> matchExpression,
String pattern) {
this(criteriaBuilder, matchExpression, new LiteralExpression<>(criteriaBuilder, pattern));
}
public Expression<String> getMatchExpression() {
return matchExpression;
}
public Expression<String> getPattern() {
return pattern;
}
@Override
public void registerParameters(ParameterRegistry registry) {
Helper.possibleParameter(getMatchExpression(), registry);
Helper.possibleParameter(getPattern(), registry);
}
@Override
public String render(boolean isNegated, RenderingContext renderingContext) {
String match = ((Renderable) getMatchExpression()).render(renderingContext);
String pattern = ((Renderable) getPattern()).render(renderingContext);
return String.format("function('caseInSensitiveMatching', %s, %s) = %s", match, pattern, !isNegated);
}
}