[an error occurred while processing this directive] IT • archiv :: Print

IT • archiv


[an error occurred while processing this directive]

[an error occurred while processing this directive]

Создавайте JDBC-приложения, не зависящие от типа данных

[an error occurred while processing this directive](none) [an error occurred while processing this directive](none)[an error occurred while processing this directive] ::
[an error occurred while processing this directive](none)
[an error occurred while processing this directive]([an error occurred while processing this directive] Сеш Венугопал [an error occurred while processing this directive])

[an error occurred while processing this directive](none)

Используйте метаданные JDBC и предварительно скомпилированный SQL, чтобы преобразовать данные в типы данных баз данных.

Tips 'N Tricks
PDF versionPDF версия
Обзор
Программирование баз данных часто требует работы с данными из внешних источников, таких как текстовые файлы. В этой статье Сеш Венугопал представляет улучшенную методику преобразования текстовых данных в фактические типы баз данных с использованием DatabaseMetaData, интерфейса метаданных JDBC "Java Database Connectivity", наряду с выполнением предварительно скомпилированного SQL-кода. Он даёт совет о том, как узнать специфичные для базы данных типы данных во время выполнения и преобразовывать внешние текстовые данные. (В оригинальной версии на английском языке 2500 слов)

Предположим, нужно написать программу, которая заполняет таблицу в базе данных данными из текстового файла. Скажем, таблица содержит информацию об авиабилетах, включая номер билета, дату покупки, дату полёта, место отправления и прибытия и цену билета. Каждая часть информации имеет определенный тип данных, связанный с ней в базе данных, например, числовой тип, текстовый, дату или валюту.

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

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

Когда количество таблиц, которые должны быть заполнены, растёт, приходится писать всё больше кода. Но даже это — только в случае единственной базы данных. Если данные переносятся на другую систему с базой данных, необходимо переписать весь существующий код, чтобы учесть иные типы данных в новой базе данных. Проблема контроля типов при обмене данными между базами данных общеизвестна в программировании баз данных.

Создайте многократно используемый код

Как избежать всего этого переписывания? Код, который Вы пишете, чтобы заполнить таблицу данными из текстового файла, может сделать имя базы данных, имя таблицы, и имя столбца параметрами. Вопрос в том, как можно сделать то же самое для типов данных, чтобы написать класс или коллекцию классов только один раз, чтобы можно было заполнять любой набор таблиц в любой базе данных? Ответ — отложить определение типов данных до выполнения программы, используя интерфейс DatabaseMetaData в пакете java.sql. При написании программы, опираясь на этот интерфейс, Вы избегаете явного определения типов данных в коде программы и создаёте общий, многократно используемый код.

Интерфейс DatabaseMetaData обеспечивает предоставление информации о метаданных базы данных. Метаданные — это данные, описывающие данные. Например, наша таблица базы данных по авиалинии содержит информацию о продажах билетов. Это — данные. В этом сценарии метаданные содержали бы информацию о таких вещах, как количество столбцов в таблице, типы данных этих столбцов, может ли столбец иметь нулевые значения, и так далее. Это — данные о данных. В этой статье сделан акцент на типах данных в столбцах. В дальнейшем обсуждении я покажу, как разработать пригодную для повторного использования библиотеку из трех классов Java, которые организованы в уровнях, от самого близкого к базе данных до самого дальнего. Самый дальний от базы данных уровень становится самым близким к приложению, которое использует библиотеку. Хотя эта библиотека предназначена для заполнения таблицы базы данных, вы можете использовать методы, показанные при её создании, а также часть кода, чтобы создавать библиотеки для независимых от типов данных запросов и обновлений баз данных.

Следуйте сценарию

Предположим, нужно заполнить таблицу с именем emp в базе данных организации. Вот типовой файл данных, содержащий имя таблицы в первой строке, и набор имён столбцов во второй. Каждая последующая строка содержит данные по строкам для указанных столбцов.

emp
hiredate sal ename empno
1996-09-01 1250.00 jackson 7123
1980-01-01 2500.50 walsh 7124
1985-01-01 12345.67 gates 7125

В то время как все входные данные — текст ASCII, данные в этих столбцах будут приведены к следующим типам, специфичным для базы данных:

столбец тип данных
hiredate date
sal decimal
ename ASCII text
empno number

Структура emp показана в верхней строке на рис. 1.

Соответствие исходных столбцов столбцам таблицы базы данных.
Рисунок 1. Соответствие исходных столбцов столбцам таблицы базы данных.

Не все столбцы в таблице должны быть заполнены сразу. В этом примере должны быть заполнены только столбцы, отмеченные «true»; активными столбцами являются empno, ename, hiredate, и sal.

К тому же, порядок, в котором столбцы следуют во входном файле, не обязательно такой же, как порядок, в котором они размещены в таблице базы данных. Массив индексов содержит соответствия столбцов на входе их позициям в таблице. В этом примере массив порядка активных столбцов является массивов индексов. Если i — позиция столбца во входном файле "hiredate — 0, sal — 1 и т.д.", то activeColumnOrder[i] — позиция этого столбца в таблице "activeColumn [0] = 5, что означает, что hiredate — пятый столбец в таблице "emp".

Построение уровней классов библиотеки

Наша библиотека состоит из трех уровней:

Рис. 2 иллюстрирует это разбиение на слои.

Уровни приложения.
Рисунок 2. Уровни приложения.

Эти классы собраны в пакет tablebuild.

Исходные файлы пакета tablebuild можно загрузить по ссылке в разделе «Ресурсы» ниже. Чтобы избежать беспорядка, в этой статье приведены только самые важные части кода.

Уровень 1: класс TableColumns

Класс TableColumns получает информацию о заданных столбцах таблицы базы данных, запрашивая метаданные у этой базы данных. Он хранит информацию о столбцах в двух параллельных массивах: массив строк columnNames и массив коротких целых чисел columnTypeCodes.

Типы всех столбцов в данной таблице могут быть получены с помощью метода getColumns класса DatabaseMetaData:

public abstract ResultSet getColumns"String catalog,
                          String schemaPattern,
                          String tableNamePattern,
                          String columnNamePattern"
 throws SQLException

Метод getColumns берет четыре параметра: имя каталога, имя схемы, имя таблицы, и имя столбца. Последние три из них, с суффиксом «Pattern» "см. код выше", интересны тем, что они позволяют искать все целевые строки, соответствующие выражению-шаблону. Другими словами, через эти параметры Вы определяете критерии поиска. Этим говорится следующее:

Найти все столбцы, удовлетворяющие следующим критериям: они принадлежат данному каталогу, ЛЮБОЙ из схем, которые соответствуют данному образцу схемы, и ЛЮБОЙ из таблиц, которые соответствуют данному шаблону имени таблицы. Их имена также соответствуют данному образцу имени столбца.

Шаблон определён синтаксисом, подобным используемому в выражениях SQL, в которых фраза LIKE используется для подбора имён. В частности, символ подчеркивания "_" в выражении-шаблоне соответствует любому символу целевой строки, а символ процента "%" соответствует любому количеству последовательных символов в целевой строке. Например, шаблон j_b дал бы целевые строки «job» и «jab», в то время как шаблон j%b даст любую целевую строку, начинающуюся с «j» и заканчивающуюся на «b», с любым количеством "включая ноль" символом между ними.

Следующее — вызов метода getColumns в конструкторе TableColumns, где dbMeta — экземпляр DatabaseMetaData для данной базы данных.

ResultSet rset = dbMeta.getColumns"null, null, 
                    table.toUpperCase"", "%"";

Заметьте, что значения имени каталога и параметров шаблона схемы — null. Значение null указывает, что параметр должен быть удалён из критериев поиска. Также обратите внимание, что мы специально представляем имя данной таблицы в верхнем регистре для шаблона имени таблицы. Неясно, является ли это требованием JDBC или предпочтением драйвера JDBC. Я пережил несколько неприятных моментов, наблюдая все виды других ошибок, прежде чем обнаружить, что при имени таблицы в верхнем регистре мой код работает. Наконец, универсальный шаблон "%" определен для шаблона имени столбца. Всё это означает: «Получить ВСЕ столбцы в ДАННОЙ таблице.»

Java.sql.ResultSet, возвращаемый методом getColumns, состоит из одной строки на соответствующий столбец. Каждая строка состоит из 18 описательных полей, которые являются столбцами результирующего множества. Релевантные поля для нашего класса — имя столбца и код типа столбца, которые являются полями номер четыре и пять соответственно. Код типа столбца — константа в классе java.sql.Types, представляющем SQL-тип столбца

В этом примере вызов getColumns с emp в качестве имени таблицы возвратил бы описание всех восьми столбцов, по одной в строке результирующего множества. Коды типа для столбцов извлекаются и хранятся в массиве columnTypeCodes для использования классом TableMediator. Сейчас мы исследуем этот процесс.

Уровень 2: TableMediator

Класс TableMediator имеет следующие функциональные возможности.

  • Он создаёт подготовленное предложение "подготовленное SQL-предложение с параметрами, значения которых подставляются во время выполнения" для заполнения строки таблицы. Параметры предложения соответствуют значениям активных столбцов.
  • Он выполняет предложение для данного набора активных столбцов.

Класс TableMediator использует класс TableColumns, чтобы получить полную информацию о столбцах данной таблицы, а затем сопоставляет эту информацию с заданным набором активных столбцов, чтобы получить конфигурацию, показанную на рис. 1.

Метод PrepareRowInserts создаёт требуемое подготовленное предложение. В этом примере подготовленное предложение выглядело бы следующим образом:

 insert into emp "empno, ename, job, mgr, hiredate, sal, comm, deptno"
             values "?, ?, ?, ?, ?, ?, ?, ?"

Все столбцы в таблице будут заполнены для каждой строки, а значения неактивных столбцов "которых нет во входном файле" устанавливаются в null.

Первый ключ к тому, чтобы сделать вашу библиотеку переносимой, это использование интерфейса DatabaseMetaData, чтобы узнать типы столбцов, как в классе TableColumns. Второй — использование родового метода setObject интерфейса java.sql.PreparedStatement.

ps.setObject"activeColumnOrder [i], columnValues[i]";

Эта строка кода появляется в методе insertRow класса TableMediator, а ps — ранее созданный экземпляр PreparedStatement.

Метод SetObject принимает два параметра: первый — позиция параметра в связанном подготовленном предложении, а второй — значение этого параметра. "Помните, что эти параметры представляют столбцы в таблице.". В общем, вторым параметром может быть любой тип объекта; здесь это строка. Драйвер JDBC отвечает за преобразование этой строки в соответствующий тип базы данных.

В этом предложении мы не использовали типы столбца, узнанные ранее. Однако нам нужны типы столбцов для значений, которые являются null — например, чтобы заполнить столбец, для которого никакое значение не предоставлено. В таком случае мы могли бы использовать вспомогательный метод setNull в интерфейсе PreparedStatement. Вот как он используется в методе insertRow класса TableMediator.

ps.setNull"i, tc.columnTypeCodes[i]";

Это предложение устанавливает значение параметра подготовленного предложения i в null, но эта установка требует, чтобы тип соответствующего столбца был послан как дополнительный, второй параметр.

Уровень 3: Класс TableBuilder

Как только классы TableColumns и TableMediator созданы, код для заполнения таблицы входными данными становится простым, как в классе TableBuilder.

Класс TableBuilder включает следующие функциональные возможности.

  • Он читает имя таблицы из первой строки текстового файла и создает экземпляр класса TableMediator.
  • Он читает имена столбцов из второй строки текстового файла и просит экземпляр TableMediator создать список активных столбцов для этих столбцов.
  • Он просит, чтобы экземпляр TableMediator подготовился к вставке строк в таблицу.
  • Он читает данные столбца из каждой последующей строки текстового файла и просит образец TableMediator вставить это данные в следующую строку таблицы.

Использование библиотеки tablebuild

При наличии библиотеки tablebuild очень просто написать приложение, заполняющее таблицу базы данных. Вот пример такого приложения

import java.sql.*; 
import java.io.*; 
import tablebuild.*;

public class PopulateTable 
{
public static final String usage = "usage: java PopulateTable " +
               "";

public static void main(String[] args)
throws SQLException, ClassNotFoundException, IOException
{             
    if (args.length != 1) {
        System.err.println(usage);
        System.exit(1);
    }

    Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

    Connection conn = DriverManager.getConnection(
                "jdbc:odbc:orgdb");
          
    BufferedReader br = new BufferedReader(
                new FileReader(args[0]));

    // tab-delimited input data file
    TableBuilder tb = new TableBuilder(conn, br, "\t");

    tb.buildTableInfo();

    tb.buildActiveColumns();

    tb.buildTable();
}
}

Пример использует мост JDBC-ODBC, чтобы управлять базой данных Microsoft Access, источник данных которой называется orgdb. Я выбрал этот мост в качестве иллюстрации, главным образом, потому что он служит эталонной реализацией драйверов JDBC для большинства возможностей JDBC.

Чтобы завершать пример, вы можете запустить вышеупомянутое приложение, чтобы заполнить таблицу emp источника данных orgdb. Если t источник данных. Если имя входного файла — empfile, приложение запускается следующим образом:

java PopulateTable empfile

Таким образом, столбцы hiredate, sal, ename и empno заполняются исходными данными с преобразованием каждого входного значения данных в требуемый тип, специфичный для базы данных.

Если у вас есть новый источник данных, нет необходимости изменять какой-либо из классов tablebuild или приложение, пока сохраняется одинаковый входной формат — то есть, первая строка содержит имя таблицы, вторая строка содержит имена столбцов, а каждая последующая строка содержит данные для всех столбцов некоторой строки. Разделители столбцов могут быть любыми, так как они задаются как параметр для конструктора TableBuilder.

Если изменяется сама база данных, нужно только изменить строки кода в приложении, в котором устанавливается подключение к базе данных. Опять же, не нужно трогать ни один из классов tablebuild; новое подключение просто передаётся конструктору TableBuilder в качестве параметра.

Протестируйте ваш драйвер JDBC

В этой статье вы увидели, как создать независимую от типов данных библиотеку классов, чтобы заполнить столбцы таблицы в базе данных. Ключевой момент состоит в том, чтобы возложить всё управление на реализацию интерфейсов java.sql.DatabaseMetaData и java.sql.PreparedStatement в драйвере JDBC. Вы можете использовать методы, показанные в этой статье, для ввода данных в базу, а также для запросов и изменений базы данных.

Важное предупреждение: способность использовать динамическое определение типов, а также преобразование типов, в конечном счете зависит от способностей драйвера JDBC, который вы используете. Например, некоторые драйверы JDBC могут не поддерживать возможность преобразования типов метода setObject из PreparedStatement. Кроме того, различные драйверы JDBC в разной степени реализуют интерфейс DatabaseMetaData. Общее правило в программировании JDBC: когда возможности, включённые в спецификации JDBC, не реализуются, подозревать следует драйвер JDBC. Чаще всего, хороший драйвер, соответствующий JDBC, должен решить проблему, преодолевая очевидные ограничения в переносимости или производительности.

Об авторе

Сеш Венугопал получил докторскую степень по информатике в Университете Рутгерса. Он управляет собственный компанией по консультированию в области информационных технологий и образования, Intecus Inc., которая специализируется на основанных на www-технологии системах, использующих платформу Java. Он также подрабатывает в Lucent Technologies. Он занимался программным обеспечением на Java и образованием с первого его выпуска. Он написал учебник, Data Structures: An Object-Oriented Approach with Java, рассчитанный на университетскую программу по информатике. Эту книгу можно прочитать в Интернете: http://www.intecus.com/bookpage.html.

Ресурсы

Reprinted with permission from the January 2001 edition of JavaWorld magazine. Copyright © ITworld.com, Inc., an IDG Communications company.
View the original article at: http://www.javaworld.com/javaworld/javatips/jw-javatip82.html

[an error occurred while processing this directive]
[an error occurred while processing this directive] Перевод на русский © Дмитрий Габинский, 2003
< Вернуться на caйт :: Copyright © 1999 — 2010, IT • archiv.