60

JPA 1.0(Hibernateドライバー)でHibernateの制限を使用しています。キーワードがどこかの列に一致するかどうかをテストするものが定義されRestrictions.ilike("column","keyword", MatchMode.ANYWHERE)ており、大文字と小文字は区別されません。

現在、EclipseLinkをドライバーとして使用してJPA 2.0を使用しているため、「制限」組み込みのJPA2.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の制限と比較して厄介で不快な解決策です。

または、大文字と小文字を区別しないようにソリューションを変更する方法を教えてもらえますか?

どうもありがとう。

4

9 に答える 9

108

最初は少し厄介に思えるかもしれませんが、タイプセーフです。文字列からクエリを作成するのはそうではないので、コンパイル時ではなく実行時にエラーが発生します。WHERE句全体を1行で記述する代わりに、インデントを使用するか、各ステップを個別に実行することで、クエリを読みやすくすることができます。

クエリで大文字と小文字を区別しないようにするには、キーワードと比較フィールドの両方を小文字に変換します。

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() + "%"
        )
    )
);
于 2011-01-04T08:02:34.490 に答える
13

(現在)受け入れられている回答でコメントしたように、一方では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が私に例を示すように要求したので、私は自分の解決策について2回考えなければならず、受け入れられた答えよりもそれほど安全ではないことに気づきました:

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

それでも、同じように機能するはずの2つの異なる関数の結果を比較しないので、私のソリューションを好みます。出力の比較がより「安定」するように、すべての文字配列にまったく同じ関数を適用します。

防弾ソリューションには、SQLがlower()アクセント付き文字を正しく下げることができるように、ロケールが含まれます。(しかし、これは私の謙虚な知識を超えています)

*「C」ロケールのPostgreSQL9.5.1でのDb値

于 2017-11-24T16:58:36.500 に答える
8

私のためのこの仕事:

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();
于 2014-05-07T13:22:03.517 に答える
3

JPAよりも、データベース内でケースの影響を受けないようにするのが簡単で効率的です。

  1. SQL 2003、2006、2008標準では、以下にCOLLATE SQL_Latin1_General_CP1_CI_ASORを追加することでこれを行うことができます。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%20definitionhttp://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
    

    または、in init.ora(または初期化パラメータファイルのOS固有の名前):

    NLS_COMP=LINGUISTIC
    NLS_SORT=BINARY_CI
    

    バイナリソートでは、大文字と小文字を区別しない場合とアクセントを区別しない場合があります。NLS_SORTの値としてBINARY_CIを指定すると、アクセントと大文字と小文字を区別しないソートが指定されます。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に設定されている場合に、索引を使用してORDERBY節を満たすことができます。NLS_SORTが任意の言語ソートに設定されている場合、オプティマイザーは実行プランに全表スキャンと完全ソートを含める必要があります。

    または、上記のようにNLS_COMPがLINGUISTICに設定されている場合、ソート設定は、データベース全体ではなく、インデックス付き列にローカルに適用できます。

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

    参照:ORA11g言語の並べ替えと文字列検索 ORA11gグローバリゼーションサポート環境のセットアップ

于 2013-05-30T07:34:50.040 に答える
2

OpenJPA2.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演算子に置き換えます。

OpenJPADBDictionaryがオペレーター名の変更を許可しないのは残念です。

于 2014-05-28T14:09:34.623 に答える
2

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

        }

    }

上記の最適化により、lowerPostgresが対応する列のインデックスを活用できる機能を備えたバージョンと比較して、パフォーマンスが40倍向上しました。この状況では、クエリの実行時間を4.5秒から100ミリ秒に短縮できます。

はインデックスのlower効率的な使用を妨げるため、速度が大幅に低下します。

于 2020-06-16T06:34:01.110 に答える
1

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);
    }
}
于 2021-04-28T11:05:27.540 に答える
0

weltraumpiratの答えとして、ルートの目的のフィールドごとに、次の述語を述語リストに追加します。

criteriaBuilder.like(criteriaBuilder.lower(root.get(<desired field on your root>)), "%" + text.toLowerCase(Locale.ROOT) + "%")

次に、次のように、目的のOR-ANDを使用してTypedQueryを取得します。

entityManager.createQuery(criteriaQuery.where(criteriaBuilder.and(predicateList.toArray(new Predicate[]{}))));
于 2022-03-01T13:14:37.993 に答える
-2

使用を検討してください

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

どこでもマッチングのため。

于 2016-08-31T15:03:34.277 に答える