在从字符串> 4k创建的谓词中使用Oracle Clob


11

我正在尝试从> 4000个字符(在file_data绑定变量中提供)的字符串创建一个Clob,以在下面的Oracle SELECT谓词中使用:

myQuery=
select *
from dcr_mols
WHERE flexmatch(ctab,:file_data,'MATCH=ALL')=1;

如果我将TO_CLOB()添加到file_data的周围,则它将无法达到臭名昭著的Oracle 4k对varchar的限制(对于<4k的字符串就可以)。错误(在SQL Developer中)为:

ORA-01460: unimplemented or unreasonable conversion requested
01460. 00000 -  "unimplemented or unreasonable conversion requested"

仅供参考,flexmatch函数用于搜索分子,在此处进行了描述:http : //help.accelrysonline.com/ulm/onelab/1.0/content/ulm_pdfs/direct/developers/direct_2016_developersguide.pdf

函数本身有点复杂,但本质是第二个参数必须是Clob。所以我的问题是如何将超过4000个字符的Java字符串bind_variable转换为sql(或Java)中的clob。

我使用以下方法在Java(Spring boot 2)中尝试了以下方法(在插入Clob时有效):

MapSqlParameterSource parameters = new MapSqlParameterSource();
parameters.addValue("file_data", fileDataStr,Types.CLOB);
jdbcNamedParameterTemplate.query(myQuery,parameters,…

此方法应该可以工作,但是会失败,并会产生一个弹性匹配错误,仅供参考:

SQL state [99999]; error code [29902]; ORA-29902: error in executing ODCIIndexStart() routine\nORA-20100: 
MDL-0203: Unable to read from CLOB (csfrm=1, csid=873): 
ORA-22922: nonexistent LOB value\nMDL-0021: Unable to copy LOB to string\nMDL-1051: Molstructure search query is not a valid molecule\nMDL-0976: 
Molecule index search initialization failed\nORA-06512: at \"C$MDLICHEM80.MDL_MXIXMDL\", line 329\nORA-06512: at \"C$MDLICHEM80.MDL_MXIXMDL\", line 309\n; nested exception is java.sql.SQLException: 
ORA-29902: error in executing ODCIIndexStart() routine\nORA-20100: MDL-0203: Unable to read from CLOB (csfrm=1, csid=873): 
ORA-22922: nonexistent LOB value\nMDL-0021: Unable to copy LOB to string\nMDL-1051: Molstructure search query is not a valid molecule\nMDL-0976: 
Molecule index search initialization failed\nORA-06512: at \"C$MDLICHEM80.MDL_MXIXMDL\", line 329\nORA-06512: at \"C$MDLICHEM80.MDL_MXIXMDL\", line 309\n"

注意我正在使用SpringBoot 2,但是我无法使用任何使用OracleConnection的方法(从我的Spring NamedParametersJdbcTemplate对象获得)来工作(即使是在<4k的Clob上),所以我怀疑我做了一些愚蠢的事情。我试过了:

 @Autowired
 NamedParameterJdbcTemplate  jdbcNamedParameterTemplate;
OracleConnection conn =  this.jdbcNamedParameterTemplate.getJdbcTemplate().getDataSource().getConnection().unwrap(OracleConnection.class);
Clob myClob =  conn.createClob();
myClob.setString( 1, fileDataStr);
MapSqlParameterSource parameters = new MapSqlParameterSource();
parameters.addValue("file_data", myClob,Types.CLOB);

application.properties:

spring.datasource.url=jdbc:oracle:thin:@//${ORA_HOST}:${ORA_PORT}/${ORA_SID}
spring.datasource.username=${ORA_USER}
spring.datasource.password=${ORA_PASS}

请注意,如果我去上学,并使用非弹簧连接加上带有setClob()方法的PreparedStatement,它会很好地工作:

OracleDataSource ods = new OracleDataSource();
String url ="jdbc:oracle:thin:@//" + ORA_HOST +":"+ORA_PORT +"/"+ORA_SID;
ods.setURL(url);
ods.setUser(user);
ods.setPassword(passwd);
Connection conn = ods.getConnection();
Clob myClob=conn.createClob();
PreparedStatement ps = conn.prepareStatement("select dcr_number from dcr_mols WHERE flexmatch(ctab,?,'MATCH=ALL')=1");
myClob.setString(1,myMol);
ps.setClob(1,myClob);
ResultSet rs =ps.executeQuery();

但是我更喜欢Java或Sql的Spring 2解决方案。任何帮助,建议表示赞赏。


+1是一个很好的问题,与我到目前为止所做的不同。您可以指出该flexmatch()函数的API文档吗?我希望看到这一点。老实说,我从没在WHERE子句中使用大值作为参数。我已在中使用它们,INSERT并已使用检索它们SELECT。您的情况不同。
Impaler

@The_impaler有一个指向问题文档的链接。我不害怕它,但是这就是我们所拥有的。这是一个非常特殊的功能。我需要搜索分子的数字表示形式,并且您需要专业的功能才能做到这一点。即我在dcr_mols表中是否已经存在该分子。
DS。

您正在使用哪个Oracle版本?
竞技场

@areaus ojdbc6-11.2.1.0.1
DS。

Answers:


5

流式传输。您不能只是将巨大的值粘贴到SQL语句中。

您需要:

  • INSERT语句中插入一个空的BLOB (使用EMPTY_BLOB()?...不太记得)。
  • 获取空Blob的输出流。
  • 然后从文件获取输入流。请不要将整个文件加载到内存中。
  • 然后使用缓冲将块从输入流传输到输出流。16 KB缓冲区应该可以。
  • 关闭两个流。

这是在Oracle中处理海量数据的标准方法。那里有很多例子。

检索海量数据(BLOBCLOB类型)的方法相同。在这种情况下,只需使用InputStreams。


@The_impaler我没有插入Clob。我提供一个CLOB到一个被称为在选择的谓词函数
DS。

1

阅读“ BIOVIA Direct” API的文档,第27页上有一个有趣的示例,摘录如下所示:

select ...
from ...
where flexmatch(
ctab,
(select ctab from nostruct_table),
'all'
)=1

它使用已加载的 CLOB。因此,我想一个不错的解决方案是将CLOB加载到您的表中(或预先将它们全部预加载),然后再使用它们。

步骤#1-将您的CLOB加载到表中:

create table mol_file (
  id number(12) primary key not null,
  content clob
);

insert into mol_file (id, content) values (:id, :content);

并使用您的Java代码来执行CLOB插入(可能使用流),如另一个答案(Internet中的大量示例)所示。例如,插入ID =的mol数据内容123

步骤#2-使用已加载的mol文件运行查询:

select *
from dcr_mols
WHERE flexmatch(
        ctab,
        (select content from mol_file where id = :id),
        'MATCH=ALL'
      ) = 1;

您可以将:id参数设置123为使用之前加载的文件(或任何其他文件)。


@The_impaler谢谢,但这有点像个大锤。我们有很多查询要运行,这会使代码复杂化并降低运行速度。我已经用一个过时的答案更新了我的问题,如果没有更好的答案,我会很不情愿地使用它。
DS。

1

您可以像这样调用旧的时尚功能。创建一个类来处理ResultSet

 class MyPreparedStatementCallback implements PreparedStatementCallback {
    public Object doInPreparedStatement(PreparedStatement preparedStatement)
            throws SQLException, DataAccessException {
        ResultSet rs = preparedStatement.executeQuery();
        List result = new LinkedList();
        rs.close();
        return result;
    }
}

并在方法中使用JdbcTemplate调用查询

 jdbcTemplate.execute(new PreparedStatementCreator() {
        @Override
        public PreparedStatement createPreparedStatement(Connection connection)

                throws SQLException, DataAccessException {

            PreparedStatement ps = connection.prepareStatement("select dcr_number from dcr_mols WHERE flexmatch(ctab,?,'MATCH=ALL')=1");
            Clob myClob =  connection.createClob();
            myClob.setString( 1, fileDataStr);
            MapSqlParameterSource parameters = new MapSqlParameterSource();
            parameters.addValue("file_data", myClob, Types.CLOB);
            ps.setClob(1,myClob);
            return ps;

        };
    }, new MyPreparedStatementCallback());

1

我不得不恢复到使用PreparedStatement,但是通过从Spring获取连接并使用apache commons BeanListHandler将ResultSet映射到对象List,我已经改善了常规实现。

import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;

@Autowired
NamedParameterJdbcTemplate  jdbcTemplate;

List<MyDao> myMethod(String fileData){
    String myQuery="select * from dcr_mols WHERE flexmatch(ctab,?,'MATCH=ALL')=1";

try {
    Connection conn =  this.jdbcTemplate.getJdbcTemplate().getDataSource().getConnection().unwrap(OracleConnection.class);   // Get connection from spring

    Clob myClob =  conn.createClob();   // Open a dB clob 
    myClob.setString( 1, fileData);     // add data to clob
    PreparedStatement ps = conn.prepareStatement(myQuery);
    ps.setClob(1,myClob);              // Add a clob into the PreparedStatement
    ResultSet rs =ps.executeQuery();   // Execute the prepared statement

    //ResultSetHandler<List<MyDao>> handler = new BeanListHandler<MyDao>(MyDao.class);   // Define the ResultSet handler
    ResultSetHandler<List<MyDao>> handler = new BeanListHandler<MyDao>(MyDao.class, new BasicRowProcessor(new GenerousBeanProcessor()));  // This is better than the above handler , because GenerousBeanProcessor removes the requirement for the column names to exactly match the java variables

    List<MyDao> myDaoList = handler.handle(rs);   // Map ResultSet to List of MyDao objects
    }catch (Exception e) {
        e.printStackTrace();
    }

return myDaoList;
}

0

您可以使用con声明fileDataStr为CLOB,即连接

java.sql.Clob fileDataStr = oracle.sql.CLOB.createTemporary
(con, false, oracle.sql.CLOB.DURATION_SESSION);

然后像下面一样使用它

 parameters.addValue("file_data", fileDataStr,Types.CLOB);

另外,如果您在连接字符串中使用SID代替服务名称,请尝试如下更改属性文件

spring.datasource.url=jdbc:oracle:thin:@//${ORA_HOST}:${ORA_PORT}:${ORA_SID}

谢谢,但是那很尴尬。数据可以是任何大小,因此我必须使用动态sql创建块,而且,我不确定您是否可以将Clob分解成这样的部分。
DS。

什么是fileDataStr的类型
psaraj12

这是一个Java String
DS。

您可以将其声明为CLOB还是java.sql.Clob fileDataStr = oracle.sql.CLOB.createTemporary(con,false,oracle.sql.CLOB.DURATION_SESSION);
psaraj12

请参阅我的示例,以评论如何将fileDataStr声明为CL,其中con是连接
psaraj12
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.