(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Пишем PL/SQL для функции, похожей на секцию FINALLY в Java

Источник: oracle
Стивен Ферстайн, член-директор Oracle ACE

Автор: Стивен Ферстайн, член-директор Oracle ACE

Я только что вернулся из мира Java в PL/SQL. Одна из возможностей Java, отсутствие которых я реально почувствовал в PL/SQL, это секция метода FINALLY. Как получить подобную функциональность при выходе из PL/SQL?

В отличие от Java, PL/SQL не поддерживает секцию FINALLY. Однако многое из того, что она делает, можно эмулировать с помощью внимательного и упорядоченного использования локальных подпрограмм.

Сначала посмотрим, как FINALLY работает в Java, затем я объясню, почему она была бы полезна в PL/SQL, и, наконец, покажу, как её эмулировать.

В Java секция FINALLY всегда выполняется при завершении секции TRY - даже если возникает необработанное исключение. Секция FINALLY гарантирует, что cleanup-логика не пропущена и не проигнорирована, где бы и как ни завершилась программа. Программист не должен специально включать эту секцию или вызывать её код. Java-машина автоматически выполняет её перед тем, как вернуть управление из метода.

Cleanup-логика, необходимая при выполнении PL/SQL

В PL/SQL есть несколько действий, которые требуют явных cleanup-предложений, включая следующие:

  • Открытие файла с помощью UTL_FILE.FOPEN. Я должен позже закрыть файл, используя UTL_FILE.FCLOSE; иначе он останется открытым до тех пор, пока соединение не завершится или пока не будет вызван UTL_FILE.FCLOSE_ALL, чтобы закрыть все файлы, открытые сессией.
  • Открытие курсора с помощью DBMS_SQL.OPEN_CURSOR. Я должен закрыть курсор, используя DBMS_SQL.CLOSE_CURSOR, или этот курсор останется открытым до тех пор, пока соединение не завершится.
  • Выделение памяти для пакетных переменных. Переменные, объявленные на уровне пакета, сохраняют значения (и память, выделенную для этих значений) на протяжении сессии, даже если блок, в котором значение было присвоено, завершится. Если я не хочу, чтобы эта память продолжала быть занятой значениями переменных, необходимо явно освободить память.

Давайте посмотрим на программу, которая работает с файлами и динамическим SQL, - и проблемы, которые могут возникнуть, если не выполнить после себя полную зачистку. Я буду использовать типичную распространённую поверхностную методологию для небрежной программы (exec_sql_from_file), которая читает файл и выполняет его содержимое, как единичное SQL-предложение, используя DBMS_SQL. Я предполагаю, что это метод только с динамическим SQL-предложением (DDL или DML и без каких-либо bind-переменных).

Вот описание процедуры exec_sql_from_file из Листинга 1:

Листинг 1: exec_sql_from_file (до эмуляции FINALLY)

  1  PROCEDURE exec_sql_from_file (
  2         dir_in    IN     VARCHAR2
  3       , file_in   IN     VARCHAR2
  4  )
  5  IS
  6     l_file         UTL_FILE.file_type;
  7     l_lines       DBMS_SQL.varchar2a;
  8     l_cur         PLS_INTEGER;
  9     l_exec       PLS_INTEGER;
 10  BEGIN
 11     BEGIN
 12        l_file := UTL_FILE.fopen (dir_in, file_in, 'R');
 13
 14        LOOP
 15           UTL_FILE.get_line (l_file, l_lines (l_lines.COUNT + 1));
 16        END LOOP;
 17     EXCEPTION
 18        WHEN NO_DATA_FOUND
 19        THEN
 20             /* Все данные из файла прочитаны. */
 21             NULL;
 22     END;
 23
 24     l_cur := DBMS_SQL.open_cursor;
 25     DBMS_SQL.parse (l_cur
 26                            , l_lines
 27                            , l_lines.FIRST
 28                           , l_lines.LAST
 29                            , TRUE
 30                           , DBMS_SQL.native
 31                             );
 32     l_exec := DBMS_SQL.EXECUTE (l_cur);
 33 END exec_sql_from_file;

Строки 12-22. Использование UTL_FILE для открытия указанного файла, и чтение его содержимого в массив, который объявлен как тип DBMS_SQL.

Строки 18-21. Когда UTL_FILE.GET_LINE считывает конец файла, возникает исключение NO_DATA_FOUND. Это исключение отлавливается и затем используется предложение NULL для того, чтобы сообщить программе о необходимости продолжения.

Строки 24-32. Использование перегрузки DBMS_SQL.PARSE (которая принимает массив строк) для разбора всего содержимого файла и последующего выполнения курсора. Эти строки выполняют динамическую SQL-операцию. Такое применение SQL и перегрузки с массивами будет работать во всех версиях Oracle Database, но заметьте, что в Oracle Database 11g, как DBMS_SQL.PARSE, так и EXECUTE IMMEDIATE, есть и CLOB, поэтому больше не надо будет использовать перегрузку с массивом для очень больших (больше 32K) SQL-предложений.

Итак, в PL/SQL нужно только 33 строки кода для реализации процедуры, которая читает содержимое файла и выполняет его как SQL-предложение. К сожалению, это очень грязный код. Я пренебрёг реализацией этапа зачистки: закрытием файла и закрытием курсора. Как результат, файл остаётся открытым на протяжении моей сессии (или до тех пор, пока я не вызову UTL_FILE.FCLOSE_ALL). Курсор также остаётся открытым до тех пор, пока я не отключусь.

Эмуляция Finally

Теперь я покажу, как самым похожим образом эмулировать поведение выражения FINALLY в PL/SQL с помощью локальных cleanup-подпрограмм.

Чтобы убедиться в том, что очистка выполнена правильно и закрыты все открытые ресурсы, необходимо добавить две строки в конце процедуры (между строками 32 и 33 на Листинге 1):

UTL_FILE.fclose (l_file);
DBMS_SQL.close_cursor (l_cur);

Они выполнятся? Только если никогда не будет проблем с выполнением этой программы.

В реальности весьма вероятно, что если что-то может случиться, то это непременно случится. Поэтому в случае с exec_sql_from_file необходимо добавить обработку исключения для ловушки ошибок, записать информацию об ошибке в журнал, как принято в стандартах моего приложения, очистить последствия программы, и снова инициировать исключение.

Нижеследующий код добавляет описанную ранее cleanup-логику в секцию exception в конце процедуры exec_sql_from_file (между строками 32 и 33 на Листинге 1):

   UTL_FILE.fclose (l_file);
   DBMS_SQL.close_cursor (l_cur);
EXCEPTION
   WHEN OTHERS 
   THEN
      log_error ();
      UTL_FILE.fclose (l_file);
      DBMS_SQL.close_cursor (l_cur);
   RAISE;

Теперь у меня есть весьма умная процедура, которая убирает за собой, независимо от того, завершилась ли она успешно или с ошибкой. Однако лучше было бы не дублировать cleanup-код в нескольких местах.

Далее, код секции exception предполагает, что и файл, и курсор открыты. Если проблема возникает при чтении файла, я никогда не получу даже динамической SQL-части моей программы (строки с 24 по 32). Таким образом, можно попытаться закрыть курсор, который не был открыт, и получить исключение. Ошибка, которая будет инициирована, зависит от версии Oracle Database. (Если применяется Oracle Database 11g, это действие отключит использование DBMS_SQL для всей моей сессии и потребует переподсоединения.)

На самом деле закрыть ресурс необходимо только в том случае, если он открыт, и это усложняет cleanup-код, который необходимо написать. Я мог бы просто добавить этот код в секцию exception, но что будет, если потребуется инициировать это исключение? Необходимо будет и там выполнять зачистку, продублировав ещё больше кода. Моя программа будет намного более изящной и простой в сопровождении, если собрать всю cleanup-логику в одной многократно используемой подпрограмме.

Поэтому я реализую в exec_sql_from_file маленькую локальную подпрограмму, которая выполняет все мои операции по зачистке:

PROCEDURE exec_sql_from_file (
   dir_in    IN   VARCHAR2
 , file_in   IN   VARCHAR2
)
IS
   ... объявления до ...

   PROCEDURE cleanup
   IS
   BEGIN
      IF SQLCODE <> 0
      THEN
         log_error ();
      END IF;

      IF UTL_FILE.is_open (l_file) 
      THEN
         UTL_FILE.fclose (l_file);
      END IF;

      IF DBMS_SQL.is_open (l_cur) 
      THEN
         DBMS_SQL.close_cursor (l_cur);
      END IF;
   END cleanup;

Эта cleanup-программа вызывается в обеих точках выхода из процедуры exec_sql_from_file: успешное завершение (конец исполняемой секции) и возникновение какой-нибудь ошибки (в выражении WHEN OTHERS). Следующий код предполагает, что cleanup-процедура добавлена в процедуру exec_sql_from_file и заменяет последнюю строку exec_sql_from_file Листинга 1 на:

   cleanup ();
EXCEPTION
   WHEN OTHERS
   THEN
      cleanup ();
      RAISE;
END exec_sql_from_file;

Листинг 2 показывает изменённую процедуру exec_sql_from_file с эмуляцией FINALLY.

Листинг 2: exec_sql_from_file (с эмуляцией finally)

PROCEDURE exec_sql_from_file (
   dir_in    IN   VARCHAR2
 , file_in   IN   VARCHAR2
  )
  IS
     l_file    UTL_FILE.file_type;
     l_lines   DBMS_SQL.varchar2a;
     l_cur     PLS_INTEGER;
     l_exec    PLS_INTEGER;

PROCEDURE cleanup
IS
BEGIN
   IF SQLCODE <> 0
   THEN
      log_error ();
   END IF;

   IF UTL_FILE.is_open (l_file) 
   THEN
      UTL_FILE.fclose (l_file);
   END IF;

   IF DBMS_SQL.is_open (l_cur) 
   THEN
       DBMS_SQL.close_cursor (l_cur);
   END IF;
END cleanup;

BEGIN
    l_file := UTL_FILE.fopen (dir_in, file_in, 'R');

    LOOP
       UTL_FILE.get_line (l_file, l_lines (l_lines.COUNT + 1));
    END LOOP;

EXCEPTION
    WHEN NO_DATA_FOUND
    THEN
         /* Все данные из файла прочитаны. */
         NULL;
END;

BEGIN
   l_cur := DBMS_SQL.open_cursor;

   DBMS_SQL.parse (l_cur
                       , l_lines
                       , l_lines.FIRST
                       , l_lines.LAST
                       , TRUE
                       , DBMS_SQL.native
                         );

   l_exec := DBMS_SQL.EXECUTE (l_cur); 

cleanup ();

EXCEPTION
    WHEN OTHERS
    THEN
         cleanup ();
         RAISE;

END exec_sql_from_file;

Такой метод группировки всей cleanup-логики в единственной подпрограмме и затем её вызова в конце исполняемой секции и в каждом обработчике исключений - самый близкий из возможных для эмуляции на PL/SQL выражения FINALLY из Java.

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 19.08.2009 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Oracle Database Standard Edition 2 Named User Plus License
Oracle Database Personal Edition Named User Plus Software Update License & Support
Oracle Database Standard Edition 2 Processor License
Oracle Database Personal Edition Named User Plus License
ARCHICAD 21, локальная лицензия на 12 месяцев
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
СУБД Oracle "с нуля"
Компьютерная библиотека: книги, статьи, полезные ссылки
ЕRP-Форум. Творческие дискуссии о системах автоматизации
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100