web-dev-qa-db-fra.com

JPA2: insensible à la casse comme la correspondance n'importe où

J'utilise les restrictions Hibernate dans JPA 1.0 (pilote Hibernate). Il est défini Restrictions.ilike("column","keyword", MatchMode.ANYWHERE) qui teste si le mot clé correspond n'importe où à la colonne et il est sensible à la casse. 

Maintenant, j'utilise JPA 2.0 avec EclipseLink comme pilote, donc je dois utiliser JPA 2.0 intégré "Restrictions". J'ai trouvé CriteriaBuilder et la méthode like, j'ai aussi découvert comment faire correspondre le texte n'importe où (bien que ce soit affreux et manuel), mais je n'ai toujours pas trouvé comment le faire sans distinction de casse.

Il y a ma mauvaise solution actuelle:

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

Des questions:

Existe-t-il une fonction similaire à celle du pilote Hibernate? 

Est-ce que j'utilise correctement les critères JPA 2.0? C'est une solution délicate et inconfortable par rapport aux restrictions d'Hibernate.

Ou quelqu'un peut-il m'aider à changer ma solution pour qu'elle soit insensible à la casse, s'il vous plaît?

Merci beaucoup.

38
Gaim

Cela peut sembler un peu gênant au début, mais c'est sûr. Construire des requêtes à partir de chaînes n'est pas, donc vous remarquerez des erreurs au moment de l'exécution plutôt qu'au moment de la compilation. Vous pouvez rendre les requêtes plus lisibles en utilisant des indentations ou en prenant chaque étape séparément, au lieu d'écrire une clause WHERE entière sur une seule ligne.

Pour que votre requête ne respecte pas la casse, convertissez votre mot clé et le champ comparé en minuscules:

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() + "%"
        )
    )
);
73
weltraumpirat

Ce travail pour moi:

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();
6
alpanko

Comme je l'ai commenté dans la réponse (actuellement) acceptée, un piège consiste à utiliser d'une part la fonction lower() du système de gestion de base de données (SGBD) et, d'autre part, la méthode String.toLowerCase() de Java en tant que méthode n'est pas garantie pour fournir le même résultat pour la même chaîne d'entrée.

J'ai finalement trouvé une solution beaucoup plus sûre (mais pas à l'épreuve des balles) qui consiste à laisser le SGBD faire tout l'abaissement en utilisant une expression littérale:

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

La solution complète ressemblerait donc à:

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 + "%")
        )
    )
);

Modifier:
Comme @cavpollo m'a demandé de donner un exemple, j'ai dû réfléchir à deux fois à ma solution et me suis rendu compte que ce n'était pas tellement plus sûr que la réponse acceptée:

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

Néanmoins, je préfère ma solution car elle ne compare pas le résultat à deux fonctions différentes censées fonctionner de la même manière. J'applique la même fonction à tous les tableaux de caractères afin que la comparaison de la sortie devienne plus "stable".

Une solution à l'épreuve des balles impliquerait des paramètres régionaux afin que la fonction lower() de SQL devienne en mesure d'abaisser correctement les caractères accentués. (Mais cela dépasse mon humble connaissance)

* Valeur Db avec PostgreSQL 9.5.1 avec les paramètres régionaux 'C'

5
Ghurdyl

Plus facile et plus efficace que JPA de faire respecter l'insensibilité à la casse dans la base de données. 

  1. En vertu des normes SQL 2003, 2006 et 2008, vous pouvez le faire en ajoutant COLLATE SQL_Latin1_General_CP1_CI_AS OR COLLATE latin1_general_cs à ce qui suit:

    • Colonne Définition

      CREATE TABLE <table name> (
        <column name> <type name> [DEFAULT...] 
                                  [NOT NULL|UNIQUE|PRIMARY KEY|REFERENCES...]
                                  [COLLATE <collation name>], 
        ...
      )
      
    • Définition du domaine

      CREATE DOMAIN <domain name> [ AS ] <data type>
        [ DEFAULT ... ] [ CHECK ... ] [ COLLATE <collation name> ]
      
    • Définition du jeu de caractères

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

    Pour une description complète de ce qui précède, voir: http://savage.net.au/SQL/sql-2003-2.bnf.html#column%20definitionhttp: // dev. mysql.com/doc/refman/5.1/fr/charset-table.htmlhttp://msdn.Microsoft.com/en-us/library/ms184391.aspx

  2. Dans Oracle, vous pouvez définir les paramètres de session/configuration 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
    

    Ou, dans init.ora (ou nom spécifique au système d'exploitation pour le fichier de paramètres d'initialisation):

    NLS_COMP=LINGUISTIC
    NLS_SORT=BINARY_CI
    

    Les tris binaires peuvent être insensibles à la casse ou à l’accent. Lorsque vous spécifiez BINARY_CI en tant que valeur pour NLS_SORT, il désigne un tri sensible à l’accent et ne respectant pas la casse. BINARY_AI désigne un tri binaire insensible à la casse et à l’accent. Vous souhaiterez peut-être utiliser un tri binaire si l'ordre de tri binaire du jeu de caractères est approprié pour le jeu de caractères que vous utilisez ..____. Utilisez le paramètre de session NLS_SORT pour spécifier un tri sans distinction de casse ni d'accent:

    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. 
    

    Par exemple, vous pouvez définir NLS_SORT sur les types de valeurs suivants:

    FRENCH_M_AI
    XGERMAN_CI
    

    Si vous définissez NLS_SORT sur un élément autre que BINARY [avec les options facultatives _CI ou _AI], un tri utilise une analyse complète de la table, quel que soit le chemin choisi par l'optimiseur. BINARY est l'exception car les index sont construits selon un ordre binaire de clés. Ainsi, l'optimiseur peut utiliser un index pour satisfaire la clause ORDER BY lorsque NLS_SORT est défini sur BINARY. Si NLS_SORT est défini sur un tri linguistique, l'optimiseur doit inclure une analyse complète de la table et un tri complet dans le plan d'exécution.

    Ou, si NLS_COMP est défini sur LINGUISTIC, comme ci-dessus, les paramètres de tri peuvent être appliqués localement aux colonnes indexées, plutôt que globalement dans la base de données:

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

    Référence: ORA 11g Tri linguistique et recherche de chaînesORA 11g Mise en place d’un environnement propice à la mondialisation

3
Glen Best

Solution de contournement désespérée pour OpenJPA 2.3.0 et 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);
  }

}

Il s'agit d'une solution de contournement fragile et laide pour effectuer une opération LIKE insensible à la casse avec OpenJPA et la base de données Postgresql. Il remplace l'opérateur LIKE à l'opérateur ILIKE dans le code SQL généré.

Il est dommage qu’OpenJPA DBDictionary n’autorise pas la modification du nom des opérateurs.

1
mnesarco

S'il vous plaît envisager d'utiliser

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

pour correspondre n'importe où.

0
Phuong Tran