Введение в 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: история создания, виды драйверов, способы работы и дается описание наиболее часто используемых классов и интерфейсов.
- Что такое JDBC?
- Краткая история JDBC
- Типы JDBC драйверов
- Так как его использовать?
- «Продвинутое» использование JDBC
- Замечание об исключениях
- Что еще?
Что такое JDBC?
Словосочетание JDBC — стало настолько обыденным в среде Java программистов, что уже само-собой разумеется, многие не задумываясь расшифровывают данное сокращение по аналогии с технологией Microsoft ODBC, как Java Database Connectivity. На самом деле это не совсем так, вернее даже совсем не так. JDBC никак не расшифровывается — это просто JDBC и все тут, точка! Каждый может расшифровывать так, как ему заблагорассудится, например Java Driven Basic Connection (Java управляемое основное соединение) или Just Draught Brokerage Company (только что созданная брокерская компания). В общем смысл не в названии, а в сути. Суть JDBC очень проста — это API доступа к табличным данным. Внимательный читатель заметит, что речь не идет о базах данных, а о «табличных данных». Разница на первый взгляд может и не очень существенная, но на самом деле критичная. Например существуют JDBC драйверы доступа к текстовым файлам, таблицам Microsoft Excel, то есть к таким данным, которые ну никак нельзя отнести к базам данных: с поддержкой транзакций, индексов, отношений и проч.
JDBC, важно отметить, поддерживает работу не только с SQL совместимыми СУБД, но также и с практически любыми данными табличного типа. Хотя справедливо все же будет отметить, что мощь JDBC состоит конечно же не в том, чтобы прочитать данные из текстового файла — это как говорится «из пушки по воробъям» — без использования механизма транзакций, доступа к хранимым процедурам и проч. «прелестям» присущим SQL базам данных JDBC не стал бы тем чем он сейчас является.
Технологию JDBC ни в коей мере нельзя называть технологией одной компании Sun Microsystems — родного отца Java. На данный момент технологию JDBC официально поддерживают следующие организации:
- Oracle
- DataDirect Technologies
- BEA
- Fujitsu
- MySQL
- INET Software
- Novell
- Borland
- Pointbase Inc.
- Macromedia
- SAP
В данном контексте слово «поддерживают» имеет вполне конкретный смысл, а именно участие в выработке собственно API в рамках принятого в сообществе Java процесса JSR (Java Specification Request).
Краткая история JDBC
В составе первой оригинальной версии Java (JDK 1.0) JDBC не было, понятие JDBC появилось только c выходом в декабре 1996 года JDK 1.1 — этот первый релиз JDBC принято обозначать как API JDBC 1.0 Все объявления API JDBC находились в пакете java.sql.
Следующий релиз JDBC совпал с появлением так называемого Java 2 — в декабре 1998 года и объявлением о появлении нового раздела Java 2 — J2EE в июне 1999 г. Часть JDBC, «оставшаяся» в J2SE, теперь стала называться как JDBC Core API, гарантировалось, что любой JVM должен был реализовывать эту базовую функциональность. Кроме этого появилось расширение JDBC в пакете javax.sql, входившая в J2EE под названием JDBC Optional Package — необязательная функциональность использовавшаяся в основном для серверных приложений.
Стандартный пакет JDBC 2.0 по сравнению с версией 1.0 включает:
- Пакетные (batch) обновления;
- Скроллируемые (scrollable) ResultSet;
- Опции для улучшения производительности;
- Поддержка типов данных SQL3;
- Поддержка персистентности объектов;
- Другие мелкие улучшения.
В дополнительный пакет в составе J2EE вошли классы и интерфейсы поддержки:
- DataSource для работы с JNDI;
- Пула соединений;
- Распределенных транзакций;
- RowSet — специальных компонентов JavaBeans.
Отметим здесь же, что вместе с версией JDK 1.3 вышло обновление API JDBC 2.1
API JDBC 3.0 вышел совместно с JDK 1.4, наиболее значительными изменениями следует считать следующие нововведения:
- Поддержка повторного изменения объектов Statement полученных из пула соединений;
- Поддержка промежуточных точек транзакций SavePoint;
- Метаданные для параметров объекта PreparedStatement;
- Поддержка нового типов данных REF и DATALINK — для доступа к внешним данным.
API JDBC 4.0 находится на стадии финального согласования в рамках процессa JSR-221 и его выпуск ожидается совместно с JDK версии 1.5. Декларированные цели JDBC 4.0:
- Возможность управления различными JDBC драйверами;
- Улучшение управления соединениями;
- Управление персистентностью и обновлением JDBC объектов, в частности предполагается использовать механизм шаблонов (generics), который будет введен в JDK 1.5 по аналогии с шаблонами (template) С++;
- Улучшение реализации объектов RowSet;
- Улучшенную поддержку SQL запросов в Java коде;
- Поддержку старых версий JDBC.
Типы JDBC драйверов
В API JDBC интенсивно эксплуатируется концепция интерфейсов — набора методов, которые должны быть реализованы поставщиком того или иного сервиса, в данном случае поставщиком т.н. драйвера JDBC. С программной точки зрения драйвер JDBC есть нечто иное как реализация интерфейсов предусмотренных API JDBC. По способу реализации драйверы подразделяются на 4 типа:
Тип 1
К этому типу относятся драйверы реализованные поверх ODBC драйверов (что такое ODBC мы объяснять здесь не будем, если кто-то не знает отсылаем к первооисточнику). То есть фактически все вызовы API JDBC транслируются в вызовы ODBC, а дальше обработку вызова ведет API ODBC. Иногда еще 1-й тип драйверов называется "JDBC-ODBC bridge". Преимуществом драйверов этого типа, является то что все источники данных доступные с помощью ODBC становятся доступными Java приложению, недостатки такого драйвера: низкая скорость работы, трудности конфигурирования и невозможность поддержки всех возможностей API JDBC.
Тип 2
Ко второму типу относятся драйверы использующие программные части написанные на других языках (как правило на Си). Обычно в этом случае для доступа к базе данных используются библиотеки разработанные производителем, а для их вызова используется JNI — Java интерфейс вызова нативных функций. Примером такого драйвера является т.н. «толстый» OCI-JDBC драйвер для Oracle. Такие драйверы обычно очень быстрые, но опять же так же как и в случае JDBC-ODBC драйверов требуют установки специального ПО на клиентской машине. Для OCI-JDBC драйвера Oracle например требуется установка клиента SQL*NET.
Тип 3
В отличие от предыдущих типов драйверов данный тип драйвера полностью реализуется на Java, но при этом вызовы JDBC транслируются в сетевой протокол (RMI, HTTP и т.д.), который далее транслируется в специфичный протокол базы данных. В чем-то этот драйвер схож с драйверами JDBC-ODBC, отличие в том, что реализуется полностью на Java, за счет чего отсутствует необходимость в установке клиентского ПО.
Тип 4
Также как и драйверы 3-го типа реализуется полностью на Java, но вызовы реализуются напрямую с использованием протокола базы данных, минуя сетевой протокол.
Необходимо здесь также отметить, что несмотря на то что производители драйверов часто декларируют тот или иной тип, полную совместимость и т.д. тем не менее в реальной жизни только около четверти драйверов имеют сертификат соответствия спецификации JDBC. Наиболее полную информацию о существующих драйверах JDBC можно получить посетив корневой ресурс JDBC: http://java.sun.com/products/jdbc/. Здесь помещен список драйверов JDBC с указанием их типа, производителя и наличия сертификатов: http://servlet.java.sun.com/products/jdbc/drivers. База содержит около 200 драйверов и снабжена удобной системой поиска и навигации.
Так как его использовать?
Для читателя добравшегося до этого раздела, как и любого истого программиста всегда волнует вопрос вынесенный в заголовок этой главы. Действительно, прежде чем пускаться в объяснения, что да как и почему имеет смысл написать простейшую программу, аналог волшебного «HelloWorld!», с которого традиционно начинается изучение любого языка или API. Не откладывая дела в долгий ящик давайте рассмотрим такой пример. В качестве тестовой базы, с которым мы будем работать возьмем Excel файл, а для доступа к нему будем использовать драйвер типа JDBC-ODBC бридж. Выбор JDBC-ODBC моста диктуется не любовью автора к продукции софтверного гиганта, а только тем, что начинающему программисту пробовать JDBC на примере Excel файла, проще всего и понятней. Ведь не всегда же у всех есть доступ к SQL Server, Oracle или MySQL. А вот стандартный Windows с установленным ODBC есть практически везде.
Последовательность действий должна быть такой:
-
Создать следующий Excel файл:
-
Во вкладке User DSN, конфигуратора ODBC задать DataSource, который указывает на ваш файл.
Иконка конфигуратора ODBC в Windows 95/98/ME находится в Control Panel, а в Windows2000/XP в папке Administrative Tools, внутри Control Panel.
Драйвер JDBC-ODBC входит в комплект дистрибутива J2SE, поэтому нет необходимости отдельно откуда-то его выкачивать. - Исходный текст, который мы будем использовать будет следующий:
import java.sql.*; public class JDBCTest1 { public static void main(String[] args) { String driver = "sun.jdbc.odbc.JdbcOdbcDriver"; String url = "jdbc:odbc:MyExcel"; String fName; int fId; Object fSalary; Connection con; Statement st; ResultSet rs; try { Class.forName(driver); } catch(ClassNotFoundException cnfe) { System.err.println("Can't find JDBC driver"); cnfe.PrintStackTrace(); System.exit(1); } try { con=DriverManager.getConnection(url, null, null); } catch(SQLException se) { System.err.println("DB connection error"); se.PrintStackTrace(); System.exit(1); } try { st = con.createStatement(); ResultSet rs = st.executeQuery("select id, name, salary from $Sheet1"); while (rs.next()) { fId = rs.getInt("id"); fName = rs.getString(2); fSalary = rs.getObject(3); System.out.println("id=" + id + ", name=" + name+", salary="+fSalary.toString()); } } catch(SQLException se) { System.err.println("JDBC execution error"); se.PrintStackTrace(); System.exit(1); } try { con.close(); } catch(SQLException se) { System.err.println("Connection close error"); se.PrintStackTrace(); System.exit(1); } } }
Структура кода JDBC приложений как правило не сильно отличается от приведенного примера. Здесь для простоты понимания и удобства весь код собран в одном методе, причем как нетрудно догадаться этот метод же и является точкой входа в Java приложение. Как компилировать и запускать это приложение объяснять нет необходимости, если у кого-то есть сложности с этим то я отсылаю его к так называемой первой чашечке Java (слово Java кстати на американском сленге означает кофе…).
Итак давайте внимательно посмотрим на наш код. Условно он состоит из 4-х частей находящихся в разных «try-catch» блоках.
Загрузка драйвера JDBC
Существует несколько способов загрузки драйвера JDBC
- Стандартным способом загрузки драйвера является использование загрузчика вызовом статического метода:
Class.forName(driver);
Для успешной загрузки драйвера необходимо удостовериться, чтобы файл с библиотекой содержащий указанный нами класс драйвера (в нашем случае это sun.jdbc.odbc.JdbcOdbcDriver) находился в переменной окружения CLASSPATH. В случае с нашим драйвером это не должно быть проблемой поскольку наш драйвер входит в состав JDK. Нормальный JDBC совместимый драйвер после загрузки должен создавать экземпляр класса драйвера, но…
- Иногда некоторые JDBC драйвера отказываются грузиться указанным способом, в таком случае рекомендуется использовать следующий код:
Class.forName(driver).newInstance();
В данном случае кроме загрузки драйвера производится еще и неявный вызов конструктора драйвера.
- В принципе, для загрузки драйвера можно использовать даже следующий код с явным вызовом конструктора:
new sun.jdbc.odbc.JdbcOdbcDriver();
- Загрузка драйвера JDBC может производиться и другим способом, например при вызове Java машины можно указать в значении специального системного свойства jdbc.drivers название класса JDBC драйвера:
java -Djdbc.drivers=sun.jdbc.odbc.JdbcOdbcDriver JDBCTest1
В этом случае при первой попытке установления соединения с базой данных менеджер драйверов автоматически сам загрузит класс указанный в системном свойстве jdbc.drivers
Установка соединения с базой данных
Соединение с базой данных производится единственным способом — вызовом метода:
Connection con = DriverManager.getConnection(URL, username, password);
Здесь необходимо обратить особое внимание на так называемый URL драйвера. URL служит для идентификации источника данных драйвером JDBC. В самом общем виде синтаксис URL драйвера следующий:
jdbc:<subprotocol>:<subname>
Такой формат URL вообще говоря является частным случаем синтаксиса обычного URL.
- Первая часть, а именно слово jdbc является зарезервированным и обозначает протокол доступа (так же как и http, ftp и проч.).
- Второе слово <subprotocol> обозначает имя драйвера или название механизма соединения. В нашем случае это odbc
- Третье слово <subname> обозначает источник данных. В нашем случае это просто название ODBC DataSource. Как правило третья часть URL драйвера является вендор-специфичной и ее синтаксис довольно существенно отличается от драйвера к драйверу. Например для «тонкого» драйвера Oracle синтаксис URL выглядит так:
jdbc:oracle:thin:@<host>:<port>:<sid>
А для mySQL так:
jdbc:mysql://<host>:<port>/<database>
Собственно работа
После получения соединения с базой данных (интерфейс Connection), необходимо создать так называемый SQLStatement — специальный Java объект для выполнения SQL запросов. Простейший Statement это так называемый статический — для выполнения статических SQL запросов. В нашем примере он создается так:
st = con.createStatement();
Для выполнения SQL запроса используется метод
Statement.executeQuery(String query);
этот метод возвращает объект типа ResultSet. Для тех кто знаком с идеалогией Oracle PL/SQL — объект ResultSet будет нетрудно понять — он очень похож на CURSOR. По сути это внутренняя таблица с набором записей и внутренним указателем на текущую запись. Поля ResultSet совпадают с полями результирующего SQL запроса. ResultSet бывают так называемые нескроллируемые, скроллируемые, изменяемые и неизменяемые. Скроллируемый означает с возможностью перемещения внутреннего указателя в любом направлении, по умолчанию указатель ResultSet может двигаться только вперед и его содержимое не может меняться. Для перемещения по ResultSet в нашем случае используется цикл while (rs.next()) {}. Внутри цикла происходит опрос значения полей ResultSet — у нас 3 поля id, name и salary. Для доступа к ним можно использовать 2 альтернативных способа: по имени поля или по его порядковому номеру:
-
fId = rs.getInt("id");
доступ по имени поля, причем явно специфицируется тип поля, в данном случае целочисленный
-
fName = rs.getString(2);
доступ по порядковому номеру.
ЗамечаниеПорядковый номер поля начинается с «1», а не с нуля. - В некоторых случаях, когда тип неизвестен или неважен можно использовать метод:
fSalary = rs.getObject(3);
Окончание сеанса работы
Хорошим тоном является закрытие соединения с базой данных и вообще говоря всех открытых Statement и ResultSet. В принципе ResultSet при выполнении новых запросов закрываются автоматически, часто это является причиной скрытых ошибок при программировании со сложными запросами с повторным использованием Statement.
«Продвинутое» использование JDBC
Довольно редко бывает, чтобы использование JDBC в серъезном приложении было ограничено кодом похожим на приведенный пример. Для дальнейшего изучения JDBC нам уже понадобится нормальная SQL база данных: Oracle, MySQL или SQL Server. Приведенные ниже примеры не будут работать на MS Excel таблице с доступом через JDBC-ODBC.
Пакетное выполнение SQL запросов
Иногда требуется выполнить несколько SQL запросов один за другим скажем: вставить запись, обновить другую запись и т. д. Для таких случаев принято использовать механизм пакетного (batch) выполнения SQL запросов:
st = con.createStatement(); st.addBatch("INSERT INTO CUSTOMER VALUES (10, 'John', 1000)"); st.addBatch("UPDATE CUSTOMER SET SALARY = 250 WHERE ID = 1"); st.addBatch("UPDATE CUSTOMER SET SALARY = 350 WHERE ID = 2"); int[] results = st.executeBatch();
Результатом выполнения будет массив целых чисел, каждый элемент массива соотвествует одному запросу, значения элементов расшифровываются так:
- больше или равно «0» выполнение успешное и количество обновленных записей равно значению элемента;
- «-2» выполнение успешное, но количество записей затронутых обновлением неизвестно;
- «-3» выполнение неуспешное, но при этом выполнение следующей команды в пакете продолжено.
Использование прекомпилированных SQL запросов
Для эффективного использования похожих и повторяющихся SQL запросов в JDBC API используется специальный механизм PreparedStatement — по сути это субинтерфейс Statement, который позволяет хранить в памяти прекомпилированный образ SQL выражения экономя тем самым на времени компиляции SQL выражений. Типичный код с использованием PreparedStatement выглядит примерно так:
PreparedStatement pst = con.preparedStatement( "UPDATE CUSTOMER SET SALARY = ? WHERE ID = ?" ); pst.setInt(2, 1); pst.setDouble(1, 100); pst.executeUpdate(); pst.setInt(2, 2); pst.setDouble(1, 200); boolean result = pst.executeUpdate();
Символами «?» здесь отмечены параметры прекомпилированного SQL выражения — в нашем случае это запрос на обновление полей ID и SALARY в таблице CUSTOMER. Далее с помощью методов setXXX() параметрам присваиваются нужные значения и вызывается метод executeUpdate() — для выполнения запроса на обновление. Порядковые номера параметров начинаются с «1» и нумеруются слева-направо.
Вызов хранимых процедур и функций
Вряд ли сейчас можно представить работу с базами данных без использования хранимых процедур и функций, которые выполняются на стороне сервера базы данных и являются неотьемлемой частью современной СУБД.
Рассмотрим пример использования хранимой функции getSumSalary, возвращающей сумму полей SALARY для всех ID, которые меньше величины указанной в аргументе. Для вызова хранимой процедуры/функции используется субинтерфейс PreparedStatement — CallableStatement. Пример вызова нашей хранимой функции будет выглядеть так:
CallableStatement cst = con.callableStatement( "{? = call getSumSalary(?)}" ); cst.setInt(2, 10); cst.registerOutParameter(1, java.sql.Types.DOUBLE); cst.execute(); System.out.println("Sum salary for id < 10 = " + cst.getDouble(1));
Следует обратить внимание на фигурные скобки — это часть синтаксиса вызова хранимых процедур. В некоторых случаях синтаксис вызова хранимых процедур может отличаться, например указанный пример для Oracle можно написать и так:
{begin :1 = getSumSalary(:2); end;}
Замечание об исключениях
В завершение нашего введения в JDBC нельзя не сказать об исключениях. В API JDBC используется 1 универсальный класс исключений SQLException. Данный класс имеет 3 внутренних поля:
- Текстовое описание ошибки — т.н. reason — причина
- Текстовое описание состояния SQLState — описание в стандарте XOPEN
- Код ошибки, как правило соответствует коду ошибки вендора базы данных
Если ошибка произошла в результате другого исключения то имеется специальный метод для получения предыдущего исключения в цепочке исключений (не путать с трассировкой стека):
SQLException getNextException();
Кроме SQLException есть еще 2 класса наследованные от него это:
- BatchUpdateException — выбрасывается во время ошибок произошедших в пакетном режиме
-
SQLWarning – предупреждения СУБД, в отличие от SQLException не выбрасывается, а присоединяется к объекту который выполняется. Для получения предупреждений нужно использовать одну из форм:
Connection.getWarnings(), Statement.getWarnings(), ResultSet.getWarnings()
Что еще?
JDBC практически неисчерпаемая тема, мы здесь не коснулись многих вещей:
- Работа с расширенными SQL типами (BLOB, CLOB, Array и проч.);
- Транзакции;
- Реализация и использование пулов JDBC соединений;
- SQLJ;
- «Обертки» над JDBC, реализации различных моделей DAO т.н. Persistent Layers;
- Использование особенностей JDBC для различных СУБД и драйверов;
- И еще многого
В любом случае автор надеется, что кому-то сможет помочь данная статья, по крайней мере в качестве стартового ресурса.
[an error occurred while processing this directive][an error occurred while processing this directive](none)