2011-10-18 40 views

回答

2

怀疑它。 API遍布所有的RDBMS,并且提供了某些结构,如“LIKE”/“SUBSTRING”,当在Oracle上用于TEXT列时,可以映射为某种形式的结构,但它们可能只是使用标准 SQL。没有符合标准的方法来坚持这一点

+1

所以我想我不得不求助于JPA本地查询使用字符串连接和放弃类型安全。哎哟。 – Ryan

2

我刚刚为openjpa编写了一个OracleTextDictionary,它将普通的'like'运算符转换为'contains'运算符,当参数带有一个“魔法”标记前缀时。

通过这种方式,可以在Oracle文本中使用QueryDSL或Criteria Language(或JPQL)。

字典使用参数中的魔术标记检测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 = "@[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; 
    } 



} 

示例:以下(显然做作)输入产生被调用的参数:

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

作为边注:QueryDsl实际上有一个'包含'操作符,据推测为Lucene后端,jpa和sql后端生成一个'like'语句。

我还没有想出一个重载contains操作符的方法,以便它可以被使用。 (除了重写代码之外,自从我使用与WebSphere捆绑在一起的版本后,我无法做到这一点)。

因此,我使用一个小的静态方法,以使其在使用QuertyDSL时看起来很好。

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

它甚至会更好,如果JPQL可以提供全文搜索引擎的一些抽象(或插件)...

10

标准支持功能()API,允许数据库功能,通过调用名称。

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

EclipseLink还使用FUNC关键字支持JPQL。

+0

这看起来很有前途。我还没有尝试过。 – Ryan

+0

这给了我SQL错误:ORA-29909:辅助操作符的标签不是字面数字。有任何想法吗? – Tomasz

+0

找到它。 query.setHint(QueryHints.BIND_PARAMETERS,HintValues.FALSE); – Tomasz