Запрос текстовых критериев Oracle в JPA

Можно ли выполнить запрос критериев JPA с помощью оператора содержащего Oracle Text, и если да, то как?

3 ответа

Решение

Сомневаюсь. API существует во всех RDBMS и предоставляет определенные конструкции, такие как "LIKE"/"SUBSTRING", которые могут быть сопоставлены с чем-то такого рода при использовании в Oracle для столбца TEXT, но, опять же, они могут просто использовать стандартный SQL. Нет стандартного способа настаивать на этом

Критерии поддерживают API функции (), который позволяет вызывать функцию базы данных по имени.

qb.gt(qb.function("CONTAINS", root.get("name"), qb.parameter("name"), qb.literal(1)), 1)

EclipseLink также поддерживает это в JPQL, используя ключевое слово FUNC.

Я только что написал OracleTextDictionary для openjpa, который преобразует обычные "подобные" операторы в "содержащие" операторы, когда аргумент начинается с "магического" маркера.

Таким образом, можно использовать QueryDSL или Criteria Language (или JPQL) с текстом Oracle.

Словарь обнаруживает операторы LIKE с магическим маркером в аргументе и переписывает SQL для использования вызова CTX CONTAINS.

Один недостаток состоит в том, что счет не доступен простым способом, но было бы возможно улучшить драйвер, чтобы упорядочить счет. Не стесняйтесь редактировать код:-)

Я бы предположил, что можно перейти в режим гибернации, предполагая, что есть аналогичный механизм для настройки запросов к базе данных для конкретного БД.

package se.grynna.dict;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
import org.apache.openjpa.jdbc.sql.OracleDictionary;
import org.apache.openjpa.jdbc.sql.SQLBuffer;
import org.apache.openjpa.jdbc.sql.Select;

public class OracleTextDictionary extends OracleDictionary {

    public static final String CTX_MAGIC_MARKER = "@CTX@";
    final static Pattern likePattern = Pattern
        .compile("t(\\d+)\\.(\\S+) LIKE (\\?)");


    @Override
    protected SQLBuffer toSelect(SQLBuffer select,
      JDBCFetchConfiguration fetch, SQLBuffer tables, SQLBuffer where,
      SQLBuffer group, SQLBuffer having, SQLBuffer order,
      boolean distinct, boolean forUpdate, long start, long end,Select sel) {

        SQLBuffer sqlBuffer = super.toSelect(select, fetch, tables, where,
          group, having, order, distinct, forUpdate, start, end, sel);

        SQLBuffer tmpBuf = sqlBuffer;

        String sql = tmpBuf.getSQL();

        int label = 1;

        for (Matcher m = likePattern.matcher(sql); m.find(); sql = tmpBuf.getSQL()) {


        int argPos = m.start(3);
        int argIdx = findArgIdx(sql, argPos);
        Object o = tmpBuf.getParameters().get(argIdx);
        if( o == null) break;
        String arg = o.toString();

        if (arg.startsWith(CTX_MAGIC_MARKER)) {

            if (tmpBuf == sqlBuffer) {
                tmpBuf = new SQLBuffer(sqlBuffer);
            }


        arg = arg.substring(CTX_MAGIC_MARKER.length());
        setParameter(tmpBuf, argIdx, arg);

        String aliasNo = m.group(1);
        String colName = m.group(2);

        }

        String replace = String.format("(CONTAINS(t%s.%s,?,%d)>0)",
                    aliasNo, colName, label++);
        tmpBuf.replaceSqlString(m.start(), m.end(), replace);
                m.reset(tmpBuf.getSQL());
        }

      }

    return tmpBuf;
    }

    @SuppressWarnings("unchecked")
    private void setParameter(SQLBuffer tmpBuf, int argIdx, String arg) {
        tmpBuf.getParameters().set(argIdx, arg);

    }

    private int findArgIdx(String sql, int argPos) {
        int count = -1;
        for (int i = 0; i <= argPos; i++) {
            char c = sql.charAt(i);
            if (c == '?') {
                count++;
            }
        }
        return count;
    }



}

Пример: следующий (явно надуманный) входной продукт вызывается с параметрами:

:1 "@CTX@omg near ponies"
:2 "@CTX@rainbow"
:3 "@CTX@rain%"
:4 "abc1%"                     <-- an ordinary like :-)
:5 "@CTX@mushroom%"  

JPQL

select distinct customer
from Customer customer
where customer.custName like :a1 and customer.custName like :a2 and customer.custName like :a1 and customer.custId in (select d.custId
from Customer d
where d.custName like :a3 or d.custName like :a1)

SQL

SELECT t0.custId,
  t0.custName
FROM Customer t0
WHERE ((CONTAINS(t0.custName,?,1)>1)
AND (CONTAINS(t0.custName,?,2)   >1)
AND (CONTAINS(t0.custName,?,3)   >1)
AND t0.custId                   IN
  (SELECT t1.custId
  FROM Customer t1
  WHERE (t1.custName LIKE ?              <---- the like survives....
  OR (CONTAINS(t1.custName,?,1)>1))
  ))
AND ROWNUM <= ?

В качестве примечания: у QueryDsl действительно есть оператор "contains", предположительно для бэкэнда Lucene, для которого бэкэнды jpa и sql генерируют оператор "like".

Я не нашел способ перегрузки оператора содержимого, чтобы его можно было использовать. (За исключением переписывания кода, чего я не могу сделать, поскольку использую версию, поставляемую в комплекте с WebSphere.)

Поэтому я прибегаю к небольшому статическому методу, чтобы он выглядел хорошо при использовании QuertyDSL.

// x.where(c.custName.like(CTX.contains("omg near ponies"))));

Было бы еще лучше, если бы jpql мог предоставить некоторые абстракции (или плагины) для полнотекстовых поисковых систем...

Другие вопросы по тегам