JPA2:不区分大小写,就像匹配任何地方一样

Gai*_*aim 47 java criteria eclipselink hibernate-criteria jpa-2.0

我一直在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();
Run Code Online (Sandbox Code Playgroud)

问题:

是否有像Hibernate驱动程序中的任何功能?

我正确使用JPA 2.0标准吗?与Hibernate Restrictions相比,这是一个尴尬和不舒服的解决方案.

或者,任何人都可以帮我改变我的解决方案,使其不区分大小写吗?

非常感谢.

wel*_*rat 86

起初看起来有点尴尬,但它是类型安全的.从字符串构建查询不是,因此您在运行时而不是在编译时发现错误.您可以通过使用缩进或单独执行每个步骤来使查询更具可读性,而不是在一行中编写整个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() + "%"
        )
    )
);
Run Code Online (Sandbox Code Playgroud)

  • 我也是从Hibernate迁移到JPA,我发现JPA的API有点......有时候不清楚.并且似乎有很多不同的方法来完成同样的事情 - 有些方式比其他方式更冗长.我相信这将是形成那些类似语句的"友好"方式:builder.like(builder.lower(root.<String> get("username")),"%"+ keyword.toLowerCase()+"%" ) (7认同)
  • @RolandIllig确实,我写错了,Java toLower正确处理重音字符,PostgreSQL(带有'c'语言环境没有)选择lower('EÉÊÈ')-&gt; eÉÊÈ。我的观点仍然有效,将 java toLower() 与 DB lower() 进行比较可能会导致意外行为。(见我的回答) (2认同)

Ghu*_*dyl 10

正如我在(当前)接受的答案中所评论的那样,一方面使用DBMS的lower()功能存在缺陷,另一方面使用java,String.toLowerCase()因为两种方法都不能保证为同一输入字符串提供相同的输出.

我终于找到了一个更安全(但不是防弹)的解决方案,让DBMS使用文字表达式完成所有降低:

builder.lower(builder.literal("%" + keyword + "%")
Run Code Online (Sandbox Code Playgroud)

所以完整的解决方案看起来像:

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 + "%")
        )
    )
);
Run Code Online (Sandbox Code Playgroud)

编辑:
由于@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
Run Code Online (Sandbox Code Playgroud)

尽管如此,我更喜欢我的解决方案,因为它没有比较两个不同功能的结果.我将相同的函数应用于所有字符数组,以便比较输出变得更"稳定".

防弹解决方案将涉及区域设置,以便SQL lower()能够正确地降低重音字符.(但这超出了我的谦虚知识)

*带有'C'语言环境的PostgreSQL 9.5.1的Db值


Tho*_*ker 10

如果您使用像 Postgres 这样的数据库,ilike它支持提供更好的性能,因为使用该lower()功能所提供的解决方案都不能正确解决问题。

解决方案可以是自定义函数。

您正在编写的 HQL 查询是:

SELECT * FROM User WHERE (function('caseInSensitiveMatching', name, '%test%')) = true
Run Code Online (Sandbox Code Playgroud)

其中caseInSensitiveMatching是我们自定义函数的函数名称。是name您要比较的属性的路径,是%test%您要与之匹配的模式。

目标是将 HQL 查询转换为以下 SQL 查询:

SELECT * FROM User WHERE (name ilike '%test%') = true
Run Code Online (Sandbox Code Playgroud)

为了实现这一点,我们必须使用注册的自定义函数来实现我们自己的方言:

SELECT * FROM User WHERE (function('caseInSensitiveMatching', name, '%test%')) = true
Run Code Online (Sandbox Code Playgroud)

lower与具有 Postgres 可以利用相应列上的索引的功能的版本相比,上述优化在我们的情况下产生了 40 倍的性能提升。在我们的情况下,查询执行时间可以从 4.5 秒减少到 100 毫秒。

lower会妨碍索引的有效使用,因此速度要慢得多。

  • 这是非常正确的,JPA 的默认实现是“lower(left) like lower(right)”来实现不区分大小写的比较,这太糟糕了,尤其是在处理大型表时。不幸的是我只能投 1 票。 (2认同)

小智 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();
Run Code Online (Sandbox Code Playgroud)

  • 您的代码中的哪个位置确保`stringToFind`是大写的? (2认同)