Использовать сгусток Oracle в предикате, созданном из строки> 4k

11

Я пытаюсь создать clob из строки> 4000 символов (предоставленной в переменной связывания file_data) для использования в предикате 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

Сама функция немного сложна, но суть в том, что 2-й параметр должен быть clob. Поэтому мой вопрос заключается в том, как преобразовать переменную Java String bind_variable, содержащую более 4000 символов, в clob в sql (или Java).

Я попробовал метод ниже (который работает при вставке сгустков) в Java (Spring boot 2), используя:

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

Этот метод должен работать, но он терпит неудачу с конвертированной ошибкой flexmatch, которая является FYI:

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) для работы (даже с clobs <4k), поэтому я подозреваю, что сделал что-то глупое. Я пробовал:

 @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();

Но я бы предпочел решение Spring 2 на Java или Sql. Любая помощь, предложения приветствуются.

DS.
источник
Это довольно хороший вопрос +1, отличный от того, что я сделал до сих пор. Можете ли вы указать API документы для flexmatch()функции? Я хотел бы видеть необходимость в этом. Честно говоря, я никогда не использовал большие значения в качестве параметров в WHEREпредложении. Я использовал их INSERTи извлек их используя SELECT. Ваш случай отличается.
The Impaler
@The_impaler есть ссылка на документы в вопросе. Боюсь, это не апи, но это все, что у нас есть. Это очень нишевая функция. Нужно, чтобы я искал цифровое представление молекулы, и вам нужна специальная функция для этого. т.е. есть ли молекула, которую я уже существует, в таблице dcr_mols.
DS.
Какую версию Oracle вы используете?
Areus
@areaus ojdbc6-11.2.1.0.1
DS.

Ответы:

5

Поток это. Вы не можете просто вставить огромное значение в оператор SQL.

Вам нужно будет:

  • Вставьте пустой BLOB в INSERTоператор (используя EMPTY_BLOB ()? ... не совсем помню).
  • Получить выходной поток для пустого блоба.
  • Затем получите входной поток из файла. Пожалуйста, не загружайте весь файл в память.
  • Затем перенесите блоки из входного потока в выходной поток, используя буферизацию. 16 КБ буфер должен делать.
  • Закройте оба потока.

Это стандартный способ работы с массивными данными в Oracle. Там много примеров.

Получение массивных данных ( BLOBи CLOBтипов) работает аналогично. Просто используйте InputStreams в этом случае.

Импалер
источник
@The_impaler Я не вставляю сгусток. Я поставляю clob для функции, которая вызывается в предикате выбора
DS.
1

Читая документацию по API «BIOVIA Direct», на странице 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 (возможно, с использованием потоков), как показано в другом ответе (множество примеров в Интернете). Например, вставьте содержание данных мол с ID = 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;
}
DS.
источник
0

Вы можете объявить fileDataStr как CLOB, используя con, который является соединением

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}
psaraj12
источник
Спасибо, но это очень неловко. Данные могут быть любого размера, поэтому я должен был бы использовать динамический sql для создания кусков, также я не уверен, что вы можете разбить сгусток на такие части, как эта.
DS.
какой тип файлаDataStr
psaraj12
Это строка Java
DS.
можете ли вы объявить его как CLOB и как java.sql.Clob fileDataStr = oracle.sql.CLOB.createTever (con, false, oracle.sql.CLOB.DURATION_SESSION);
psaraj12
см. мой пример в комментарии о том, как объявить fileDataStr как CLOB, где con - это соединение
psaraj12