2011-10-18 10 views

risposta

2

Dubbio. L'API è presente in tutti gli RDBMS e fornisce alcuni costrutti come "LIKE"/"SUBSTRING" che potrebbero essere associati a qualcosa di quel modulo quando vengono utilizzati su Oracle per una colonna TEXT, ma possono usare semplicemente lo standard SQL. Non esiste un modo conforme allo standard per insistere sul fatto che

+1

Quindi suppongo di dover ricorrere a una query nativa JPA utilizzando la concatenazione di stringhe e la sicurezza del tipo forgo. Ahia. – Ryan

2

Ho appena scritto un OracleTextDictionary per openjpa, che converte ordinari operatori "like" in operatori "contiene", quando l'argomento è preceduto da un indicatore "magico".

In questo modo, è possibile utilizzare QueryDSL o Criteria Language (o JPQL) con testo Oracle.

Il dizionario rileva le istruzioni LIKE con un indicatore magico nell'argomento e riscrive l'SQL per utilizzare una chiamata CTX CONTAINS.

Uno svantaggio è che il punteggio non è accessibile in un modo semplice, ma sarebbe possibile migliorare il driver per ordinare dal punteggio. Sentiti libero di modificare il codice :-)

Suppongo che sia possibile portarlo in ibernazione, supponendo che esista un meccanismo simile per l'ottimizzazione delle query di database su un db specifico.

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 = "@[email protected]"; 
    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; 
    } 



} 

Esempio: Il seguente ingresso (ovviamente escogitata) produce viene chiamato con i parametri:

:1 "@[email protected] near ponies" 
:2 "@[email protected]" 
:3 "@[email protected]%" 
:4 "abc1%"      <-- an ordinary like :-) 
:5 "@[email protected]%" 

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 <= ? 

Come nota laterale : QueryDsl effettivamente lo fa avere un operatore 'containtain', presumibilmente per il backend Lucene, per il quale i backend jpa e sql generano una dichiarazione 'like'.

Non ho trovato un modo per sovraccaricare l'operatore contiene, in modo che possa essere utilizzato. (Oltre a riscrivere il codice, cosa che non posso fare dal momento che sto usando la versione fornita con WebSphere.)

Quindi, ricorro ad un piccolo metodo statico per farlo sembrare bello quando si usa QuertyDSL.

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

Sarebbe ancora più bello se jpql potrebbe fornire alcune astrazioni (o plugin) per i motori di ricerca a testo integrale ...

10

criteri supporta un API function() che consente una funzione di database per essere chiamato da nome.

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

EclipseLink supporta anche questo in JPQL utilizzando la parola chiave FUNC.

+0

Sembra promettente. Non l'ho provato però. – Ryan

+0

Ciò mi dà errore SQL: ORA-29909: l'etichetta per l'operatore ancillare non è un numero letterale. Qualche idea? – Tomasz

+0

Trovato. query.setHint (QueryHints.BIND_PARAMETERS, HintValues.FALSE); – Tomasz