5 «Переносчик таблиц

advertisement
1
Задачи по созданию утилит для работы с базами данных (через JDBC)
Рассматриваемые утилиты выполняют часто встречающиеся при сопровождении БД
задачи обработки большого количества данных. Утилиты для простоты работают в консольном режиме и должны выводить на консоль (System.out.println(…)) следующее: при неверном формате командной строки – сообщение об использовании программы, при ошибке –
сообщение об ошибке (например, exception.printStackTrace()). Параметры соединения (и, при
необходимости, другие параметры, если их не хочется брать из командной строки) рекомендуется хранить в файле “имя_задачи.ini” в формате “имя=значение”, который можно
читать с помощью класса java.util.Properties методом load(new FileInputStream(“*.ini”)).
Курсивом здесь и ниже выделены необязательные (хотя желательные) требования.
Для каждой задачи ниже указывается примерный уровень сложности задач — по 10балльной системе (первая цифра – только обязательные требования, вторая цифра – вместе с
необязательными требованиями).
Число баллов за задачу вычисляется по формуле 3*(сложность – число недочетов), т.е.
максимум – 30 баллов (но, как правило, максимум недостижим)
Указанные английские названия задач – это рекомендуемые названия Java-классов.
1 «Переносчик схем таблиц», “SchemaReplicator”
Утилита, анализирующая метаданные о таблице из БД №1 и создающая аналогичную
таблицу в БД №2 (БД №1 и №2 могут быть одной и той же БД; в этом случае нужно иметь к
ней 2 подключения под разными пользователями). Имена и типы столбцов таблицы можно
узнавать либо из DatabaseMetaData, либо из ResultSetMetaData, полученном после запроса
типа «select * from …». Следует ограничиться поддержкой лишь числового (NUMBER) и
символьного (VARCHAR) типов данных, а также ограничений типа PRIMARY KEY и
FOREGN KEY (не нужно переносить not null, unique и другие ограничения, а также индексы).
Среди параметров утилиты желательно задавать названия нескольких таблиц, чтобы они
были созданы в цикле (при этом операторы ALTER TABLE для создания FOREGN KEYs
должны быть выполнены после окончания цикла создания таблиц, иначе эти операторы могут попытаться связать таблицу, которая еще не создана). Сложность: 7-9.
2 «Импортёр данных», “DataImporter”.
Простая, но полезная утилита для заполнения базы данных на основе краткой SQLнезависимой информации из файла, название которого является параметром утилиты (такие
файлы редактировать быстрее и нагляднее, чем более громоздкие SQL-файлы с операторами
insert). Файл читается построчно с помощью кода, представленного в приложении 1. Предлагается следующий формат файла. Строка, начинающаяся с определённого символа (например, “&”), является названием таблицы, а за ней следуют несколько строк, каждая из которых – список подлежащих вставке значений атрибутов этой таблицы. Если какой-либо элемент списка начинается со спец. символа (например, “^”), то следующая за ним строка –
имя файла, из которого следует загрузить в БД большой объект (типа LONG RAW через
setBinaryStream(), но не типа BLOB через setBlob(), поскольку загрузка «настоящих» LOB в
Oracle делается достаточно неочевидным образом). Поскольку при написании текстового
файла могут быть допущены ошибки, то при возникновении SQLException в процессе вставки данных следует откатывать назад (rollback()) все выполненные операции (иначе при запуске утилиты в следующий раз эти операции не выполнятся). Сложность: 5-8.
3 «Заполнитель таблиц», “TablePopulator”
Утилита для заполнения БД на этапе разработки информационной системы – с целью
первичного тестирования работающих с ними программ. Вручную данные вводить долго,
2
поэтому они генерируются случайным образом (java.util.Random). Среди параметров утилиты указывается название и число генерируемых строк таблицы. Имена и типы столбцов таблицы можно узнавать либо из DatabaseMetaData, либо из ResultSetMetaData, полученном после запроса типа «select * from …». Следует ограничиться поддержкой числового (NUMBER)
и символьного (VARCHAR) типов данных, а также даты/времени (DATE). При генерации
символьных/числовых данных следует брать из метаданных максимальную длину строки/числа (заданную при определении столбца; ее можно получить только через DatabaseMetaData). Сложность: 4-7.
4 «Дефрагментатор таблиц», “TableDefragmentor”
Утилита для предотвращения сбоя в давно работающих БД (правда, вряд ли она полезна в современной жизни). Значения первичного ключа таблиц генерируются автоматически,
постоянно увеличиваясь (средствами СУБД или приложений). При этом значения, которые
соответствовали удалённым строкам, как правило, никогда более не используются. Если при
создании таблиц использовался небольшой размер целого числа (например, 5 знаков – до
100000), то это рано или поздно приведёт к нарушению работы ввиду частого удаления/добавления строк в БД (например, в таблице всего 1000 строк, а первичный ключ последней строки равен 99999). Утилита должна решать эту проблему, заполняя пробелы в индексации ключа строками из «конца» таблицы (т. е. строками, которые создавались последними). Её параметр – название таблицы, подлежащей дефрагментации. Сложность задачи
определяется необходимостью учета внешних ключей, экспортируемых данной таблицей:
сначала следует вставлять копию строки с новым ключом, затем изменять значения ссылающихся на неё внешних ключей, и лишь затем удалять исходную строку. Сложность: 5-8.
5 «Переносчик таблиц», “TableReplicator”
В некотором смысле, обратная по отношению к 2 задача: утилита, извлекающая данные
из БД. Однако данные записываются не в кратком формате задачи 2, а на языке SQL (DML).
SQL-инструкции либо записываются в текстовый файл (в SQL-скрипт, пригодный для выполнения в любом SQL-клиенте), либо выполняются непосредственно в соединении с БД №2,
имеющей точно такую же схему, что и исходная БД. (Это может быть та же самая БД, в
этом случае нужно иметь к ней 2 подключения под разными пользователями). Соответственно, в файле *.ini утилиты находятся либо параметры соединения с исходной БД и имя
результирующего файла, либо параметры соединений с исходной и результирующей БД. В
БД №2 вставляемые данные могут присутствовать, поэтому при вставке возможны
SQLException – нарушения уникальности первичного ключа, которые нужно игнорировать.
Параметры утилиты — таблицы для переноса (перебираются в цикле). Сложность: 4-6.
Примечание 1: Смысл переносить сразу несколько таблиц таков: таблицы следует отсортировать таким
образом, чтобы не возникало нарушений внешнего ключа: сначала должны обрабатываться родительские таблицы, а затем — дочерние. (Автоматическая сортировка в учебном задании не требуется ввиду своей сложности, но в реальных утилитах она должна быть).
Примечание 2: Практический смысл подобной утилиты может заключаться не только в простом дублировании или резервном копировании БД, но и в приспособлении информационной системы, ранее основанной
на СУБД №1, для работы под СУБД №2; последнее применение основывается на единстве языка DML во всех
СУБД. (К сожалению, не все разработчики СУБД обеспечивают перенос данных между собой и конкурентами).
5.1 Модифицированный «Переносчик таблиц», “UpdatingTableReplicator”
То же самое, что в 5, но вставка строк в таблицу (в случае переноса данных в БД, а не
в файл) подчиняется другому правилу: если БД не пуста и строка с тем же значением ключа
уже существует (т.е. возникает SQLException), то в ней заменяются все неключевые значения (не INSERT, а UPDATE). Сложность: 8.
5.2 «Утилита объединения таблиц», “TableUniter”
То же самое, что в 4, но происходит только вывод в файл (а не в БД №2); зато данные
берутся из двух различных БД (с заведомо одинаковой структурой). Конфликты между
3
строками с одинаковым первичным ключом разрешаются в пользу одной из исходных БД.
Результат – объединение БД в теоретико-множественном смысле. Сложность: 8.
Практический смысл: например, слияние общей БД фирмы с БД, дополненной (относительно общей
БД) в филиале этой фирмы.
5.3 «Утилита слияния таблиц», “TableMerger”
То же самое, что и в 5.2, только конфликты между строками с одинаковым первичным ключом должны приводить к появлению в результирующей БД строк (из исходной БД
№2) с новыми первичными ключами, так что (в отличие от 5.2) число строк в результате
всегда равно сумме исходных чисел строк. Рекомендуется в самом начале обработки таблицы вычислить максимальное значение первичного ключа в обеих БД, чтобы вставлять новые
строки со значениями первичного ключа, превышающими максимальное. Сложность: 9.
Практический смысл: например, слияние БД, созданных в разных филиалах одной фирмы (так, чтобы
не потерять никакие данные).
5.4 «Утилита пересечения таблиц», “TableIntersector”
То же самое, что и в 5.2, но в результирующую БД вставляются только те строки,
которые равны в обеих БД. Равенство строк проверяется по первичному ключу. Результат
– пересечение БД в теоретико-множественном смысле. Сложность: 8.
Практический смысл: например, восстановление утерянной общей БД фирмы, распространённой ранее в нескольких ее филиалах для пополнения данными.
5.5 «Утилита вычитания таблиц», “TableSubtractor”
То же самое, что и в 5.4, но в результирующую БД вставляются только те строки,
которые присутствуют в БД №1, но отсутствуют в БД №2. Результат – разность БД в
теоретико-множественном смысле. Сложность: 8.
Практический смысл: например, выделение специфичных для некоторого филиала фирмы данных с целью их независимого анализа. Или другой пример: создание списка добавлений, сделанных в текущей версии
базы данных по сравнению с версией №2 (пользователь, просмотрев все добавления, может внести в версию
№2 только нужные строки).
5.6 «Утилита сравнения версий таблицы», “TableComparator”
То же самое, что и в 5.5, но не только создание операторов (INSERT) для проведенных
в БД №1 добавлений по сравнению с БД №2, но также удалений и обновлений (DELETE и
UPDATE). Сначала рекомендуется создавать операторы DELETE (если в БД №1 не существует строки, имеющейся в БД №2), а затем операторы INSERT (если в БД №2 не существует строки, имеющейся в БД №1) и UPDATE (если в БД №2 строка с таким же первичным ключом существует, но ее атрибуты не совпадают со строкой из БД №1). Сложность: 10. (если не реализовывать UPDATE, то сложность 9)
Приложение 1 (к задаче 2).
Код, построчно читающий текстовый файл.
public static void main(String[] args){
try{
BufferedReader reader = new BufferedReader(new FileReader(args[0]));
… (Здесь можно считать какие-нибудь ещё данные из файла) …
MyClass obj = new MyClass(…);
obj.executeScript(reader);
reader.close();
}catch(FileNotFoundException e1){ System.out.println("File "+args[0]+" not found");
}catch(IOException e2){ System.out.println("Error in file "+args[0]); }
}
public void executeScript(BufferedReader reader) throws IOException{
while(reader.ready()){ String s = reader.readLine(); … }
}
Download