【问题标题】:Use an Oracle clob in a predicate created from a String > 4k在从 String > 4k 创建的谓词中使用 Oracle clob
【发布时间】:2020-04-01 15:14:27
【问题描述】:

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

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

如果我添加 TO_CLOB() 回合 file_data 它会导致 varchar 的臭名昭著的 Oracle 4k 限制失败(对于

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 String 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,…

这个方法应该可以工作,但它会失败并出现一个 converluted flexmatch 错误,仅供参考:

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 对象获得)来工作(即使在 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}

请注意,如果我去老学校并使用非弹簧连接加上 PreparedStatement,它有一个 setClob() 方法,它工作正常:

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 检索了它们。你的情况不同。
  • @The_impaler 有一个链接到问题中的文档。恐怕这不是一个 api,但它是我们所拥有的。这是一个非常小众的功能。需要的是我正在搜索分子的数字表示,并且您需要一个专业功能来执行此操作。即我的分子是否已经存在于 dcr_mols 表中。
  • 您使用的是哪个 Oracle 版本?
  • @areaus ojdbc6-11.2.1.0.1

标签: java sql spring oracle spring-boot


【解决方案1】:

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

您需要:

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

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

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

【讨论】:

  • @The_impaler 我没有插入 clob。我正在向一个在 select 的谓词中调用的函数提供一个 clob
【解决方案2】:

阅读“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 = 123 的 mol 数据内容。

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

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

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

【讨论】:

  • @The_impaler 谢谢,但这有点像大锤。我们有很多查询要运行,这会使代码复杂化并减慢速度。我已经更新了我的问题,使用了一个老派的答案,如果没有更好的结果,我会不情愿地使用它。
【解决方案3】:

你可以像这样调用你的旧式函数。创建一个类来处理 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());

【讨论】:

    【解决方案4】:

    我不得不恢复使用 PreparedStatement,但通过从 Spring 获取连接并使用 apache commons BeanListHandler 将 ResultSet 映射到对象列表,我已经稍微改进了正常实现

    import org.apache.commons.dbutils.ResultSetHandler;
    import org.apache.commons.dbutils.handlers.BeanListHandler;
    
    @Autowired
    DataSource dataSource;
    
    List<MyDao> myMethod(String fileData){
        String myQuery="select * from dcr_mols WHERE flexmatch(ctab,?,'MATCH=ALL')=1";
    
    try {
        Connection conn = this.dataSource.getConnection()   // 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;
    }
    

    【讨论】:

      【解决方案5】:

      您可以使用 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 分解成这样的部分。
      • fileDataStr 的类型是什么
      • 这是一个java字符串
      • 你能不能把它声明为 CLOB 和 java.sql.Clob fileDataStr = oracle.sql.CLOB.createTemporary(con, false, oracle.sql.CLOB.DURATION_SESSION);
      • 查看我的示例,评论如何将 fileDataStr 声明为 CLOB,其中 con 是连接
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-02
      • 2011-07-02
      • 2017-10-27
      • 2021-12-29
      相关资源
      最近更新 更多