Можно ли выполнить запрос критериев JPA с использованием оператора Oracle Text, и если да, то как?
Запрос текстовых критериев Oracle в JPA
Ответы (3)
Сомневаюсь. API присутствует во всех СУБД и предоставляет определенные конструкции, такие как «LIKE»/«SUBSTRING», которые могут быть сопоставлены с чем-то в этой форме при использовании в Oracle для столбца TEXT, но опять же они могут просто использовать standard< /em> SQL. Нет стандартного способа настаивать на этом
Criteria поддерживает API function(), который позволяет вызывать функцию базы данных по имени.
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 на самом деле имеет оператор «содержит», предположительно для бэкэнда Lucene, для которого бэкенды jpa и sql генерируют оператор «like».
Я не нашел способа перегрузить оператор contains, чтобы его можно было использовать. (Кроме переписывания кода, чего я не могу сделать, так как использую версию, связанную с WebSphere.)
Итак, я прибегаю к небольшому статическому методу, чтобы он хорошо выглядел при использовании QuertyDSL.
// x.where(c.custName.like(CTX.contains("omg near ponies"))));
Было бы еще лучше, если бы jpql мог предоставить некоторые абстракции (или плагины) для полнотекстовых поисковых систем...