Data Access Object (DAO). Уровень класса
При проектировании информационной системы выявляются некоторые слои, которые отвечают за взаимодействие различных модулей системы. Соединение с базой данных является одной из важнейшей составляющей приложения. Всегда выделяется часть кода, модуль, отвечающающий за передачу запросов в БД и обработку полученных от неё ответов. В общем случае, определение Data Access Object описывает его как прослойку между БД и системой. DAO абстрагирует сущности системы и делает их отображение на БД, определяет общие методы использования соединения, его получение, закрытие и (или) возвращение в Connection Pool.
Вершиной иерархии DAO является абстрактный класс или интерфейс с описанием общих методов, которые будут использоваться при взаимодействии с базой данных. Как правило, это методы поиска, удаление по ключу, обновление и т.д.
public abstract class AbstractController < public abstract ListgetAll(); public abstract E getEntityById(K id); public abstract E update(E entity); public abstract boolean delete(K id); public abstract boolean create(E entity); >
Набор методов не является завершённым, он зависит от конкретной системы. Фиктивный тип K является ключом сущности, редкая таблица, описывающая сущность, не имеет первичного ключа. Так же, в данном классе будет логичным разместить метод закрытие экземпляра PrepareStatement.
public void closePrepareStatement(PreparedStatement ps) < if (ps != null) < try < ps.close(); >catch (SQLException e) < e.printStackTrace(); >> >
Уровень класса
Реализация DAO на уровне класса подразумевает использование одного единственного коннекта для вызова более чем одного метода унаследованного DAO класса. В этом случае, в вершине иерархии DAO AbstractController, в качестве поля объявляется connection. Абстрактный класс будет выглядеть следующим образом.
public abstract class AbstractController < private Connection connection; private ConnectionPool connectionPool; public AbstractController() < connectionPool = ConnectionPool.getConnectionPool(); connection = connectionPool.getConnection(); >public abstract List getAll(); public abstract E update(E entity); public abstract E getEntityById(K id); public abstract boolean delete(K id); public abstract boolean create(E entity); // Возвращения экземпляра Connection в пул соединений public void returnConnectionInPool() < connectionPool.returnConnection(connection); >// Получение экземпляра PrepareStatement public PreparedStatement getPrepareStatement(String sql) < PreparedStatement ps = null; try < ps = connection.prepareStatement(sql); >catch (SQLException e) < e.printStackTrace(); >return ps; > // Закрытие PrepareStatement public void closePrepareStatement(PreparedStatement ps) < if (ps != null) < try < ps.close(); >catch (SQLException e) < e.printStackTrace(); >> > >
Стоит отметить, что в данном примере мы получаем экземпляр Connection из пула соединений, что соответственно стоит реализовать или воспользоваться уже готовыми решениями. Создаём методы по получению getPrepareStatement(String sql) и его закрытию closePrepareStatement(PreparedStatement ps) . Реализация конкретного DAO класса, при такой логике, никогда не должна закрывать в своих методах соединение с базой данных. Соединение закрывается в той части бизнес-логики, от куда был вызван метод. Пример конкретного DAO класса будет выглядеть следующим образом.
public class UserController extends AbstractController < public static final String SELECT_ALL_USERS = "SELECT * FROM SHEMA.USER"; @Override public ListgetAll() < Listlst = new LinkedList<>(); PreparedStatement ps = getPrepareStatement(SELECT_ALL_PLANET); try < ResultSet rs = ps.executeQuery(); while (rs.next()) < User user = new User(); planet.setId(rs.getInt(1)); planet.setName(rs.getString(2)); lst.add(user); >> catch (SQLException e) < e.printStackTrace(); >finally < closePrepareStatement(ps); >return lst; > @Override public Planet getEntityById(Integer id) < return null; >@Override public boolean delete(Integer id) < return false; >@Override public boolean create(Planet entity) < return false; >>
public class User implements Serializable < private int id; private String name; public int getId() < return id; >public void setId(int id) < this.id = id; >public String getName() < return name; >public void setName(String name) < this.name = name; >@Override public String toString() < return "User'; > >
Экземпляр Connection доступен методу getPrepareStatement(String sql), который в свою очередь доступен любому методу конкретного DAO класса. Стоит помнить, что следует закрывать экземпляр PrepareStatement сразу после его отработки в блоках finally, а возвращать соединение в пул returnConnectionInPool() в части логики системы, где был вызван метод.
Какой класс или интерфейс позволяет отсылать запросы в бд
//Шаг первый: Импорт пакетов
import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.Date;
import java.sql.SQLException;
public class FirstQuery
public static void main(String[] args)
Connection conn = null;
//Шаг второй: Регистрация JDBC драйвера
String driver = «oracle.jdbc.OracleDriver»;
Class.forName(driver);
// Шаг третий : Создание соединения
System.out.println(«Connecting to database . «);
String jdbcUrl = «jdbc:oracle:thin:@localhost:1521:orbis»;
String user = «stud»;
String pass = «stud»;
conn = DriverManager.getConnection(jdbcUrl, user, pass);
// Шаг четвертый : Выполнение запроса
Statement st = conn.createStatement();
String sql;
sql = «SELECT COUNT(*) FROM all_tables»;
ResultSet rs = st.executeQuery(sql);
//Шаг пятый: Извлечение данных из «Результата запроса»
while(rs.next()) <
//
int count = rs.getInt(1);
System.out.println(«Result set is: «+count);
>
// Шаг шестой : Освобождение ресурсов
rs.close();
st.close();
conn.close();
//Шаг седьмой: Обработка исключений
>catch(SQLException se) <
// Обработка ошибок для JDBC
se.printStackTrace();
>catch(Exception e) <
//Обработка ошибок для Class . forName
e.printStackTrace();
>finally <
//finally block used to close resourses
try <
if (conn!=null)
conn.close();
>catch(SQLException se) <
se.printStackTrace();
>//end finally try
>//end try
System.out.println(«GoodBye!»);
>//end main
>//end FirstQuery
Шаг 1: Импортирование классов и интерфейсов.
Как и во всех Java -приложениях необходимо импортировать пакеты, которые содержат необходимые для работы программы классы. Все JDBC интерфейсы и классы находятся в пакетах java . sql и javax . sql . Пакет java . sql содержит основные интерфейсы и классы, которые используются в любых JDBC -приложениях. Пакет javax . sql содержит классы и интерфейсы, необходимые для написания приложений корпоративного уровня. Он является стандартным расширением и применяется при создании и использовании «пула соединений», работы с «наборами строк», распределенными транзакциями и т.д.
- java . sql . DriverManager – Поддерживает совокупность драйверов-объектов и предоставляет их, при необходимости, для осуществления взаимодействия с СУБД.
- java . sql . Connection – Предоставляет физическое соединение с СУБД. Объекты данного класса управляют уровнями транзакций и типами результатов ( ResultSet ), возвращаемых выполняемыми запросами.
- java . sql . Statement – Пересылает SQL -предложение (запрос) в СУБД. Этот интерфейс позволяет отправлять только статические запросы без параметров. Для передачи в СУБД запроса с параметрами используются методы интерфейса java . sql . PreparedStatement .
- java . sql . ResultSet – Получает результат обработанного СУБД запроса и предусматривают методы для построчного извлечения данных результата запроса из объекта.
- java . sql . Date – Отображает тип данных JDBC на SQL DATE тип данных.
- java . sql . SQLException – Обрабатывает ошибки, возвращаемые СУБД, и исключения, возникающие в JDBC программах.
- DriverManager.registerDriver(Driver “объект класса драйвера” );
- Class.forName(String “ имя класса драйвера” ).
try <
DriverManager.registerDriver(new oracle.jdbc.OracleDriver());
> catch (SQLException se) <
se.printStackTrace();
>
Метод Class . forName () является более гибким методом регистрации драйвера, т.к. получает его имя в виде параметра типа String . Это позволяет задавать имя драйвера в командной стоке запуска Java -приложения на выполнение или в файле свойств, читаемым Java -приложением в момент исполнения программы. В приведенном выше примере переменной driver типа String присваивается полное имя класса JDBC драйвера. Данный метод выдает исключение ClassNotFoundException если указанный в качестве параметра драйвер не был найден в момент выполнения программы.
Использование Class.forName(String “имя класса драйвера”).newInstance() является наиболее распространенным способом регистрации драйвера. Созданный таким образом новый объект драйвера саморегистрируется с помощью статического инициализатора, который вызывает метод DriverManager.registerDriver(). Этот механизм позволяет динамически регистрировать драйвера в процессе выполнения программы. В случае регистрации ORACLE драйвера программный код может выглядеть следующим образом:
try <
String driver = “oracle.jdbc.OracleDriver”;
Class.forName(driver).newInstance();
> catch (ClassNotFoundException e) <
//Возникает, если класс драйвера не найден в CLASSPATH
e.print.StackTrace();
>
Для указания имени класса драйвера в качестве системного свойства в командной строке запуска программы на выполнение используется ключ -D. В этом случае JVM будет пытаться загрузить драйвер как часть процесса инициализации. По завершению инициализации будет запущено на выполнение само приложение. При этом регистрации драйвера в самом приложении уже не требуется. Ниже приведен пример командной строки:
java -Djdbc.drivers=oracle.jdbc.OracleDriver FirstQuery
В случае чтения имени класса драйвера из файла свойств, последний должен содержать строчку следующего вида:
Ниже приведен пример программного кода, читающего имя класса драйвера из файла свойств по имени database.properties:
Properties props = new Properties();
FileInputStream in = new FileInputStream(«database.properties»);
props.load(in);
in.close();
String drivers = props.getProperty(«jdbc.drivers»);
System.setProperty(«jdbc.drivers», drivers);
- DriverManager. getDriver();
- DriverManager.getDrivers() .
Enumeration drEnum = DriverManager.getDrivers();
while(drEnum.hasMoreElements()) <
Driver dr = (Driver)drEnum.nextElement();
String str = «Driver class name is: «+dr.getClass().getName();
System.out.println(str);
>
Если по какой либо причине возникла необходимость исключить возможность обращения к той или иной или базе данных, то можно воспользоваться методом DriverManager.deregisterDriver(), которому в качестве параметра указывается объект ранее зарегистрированного драйвера, например:
DriverManager.deregisterDriver(dr)
Шаг 3: Создание соединения с СУБД.
Для того чтобы открыть соединение с базой данных можно использовать один из трех перегруженных методов DriverManager . getConnection ():
DriverManager . getConnection (String url)
DriverManager . getConnection (String url, Properties prop)
DriverManager . getConnection (String url, String username, String password)
- url – указывает на местоположение базы данных и тип драйвера.
- username – имя пользователя, который будет использовать соединение.
- password – пароль пользователя.
- prop – объект типа Properties.
//Usage: java DBConnProp.
//Contents of file database.properties is:
//jdbc.drivers=oracle.jdbc.OracleDriver
//jdbc.url=jdbc:oracle:thin:@localhost:1521:orbis
//user=stud
//password=stud
//
import java.io.*;
import java.sql.*;
import java.util.*;
//
public class DBConnProp <
public static void main(String[] args) throws Exception
Connection db = getConnection();
Statement st = db.createStatement();
ResultSet rs = st.executeQuery(» SELECT COUNT(*) FROM all_tables «);
while (rs.next()) <
System.out.println(rs.getInt(1));
>
rs.close();
st.close();
db.close();
>
public static Connection getConnection() throws Exception <
Properties props = new Properties();
FileInputStream in = new FileInputStream(«ad.database.properties»);
props.load(in);
in.close();
props.list(System.out);
String drivers = props.getProperty(«jdbc.drivers»);
if (drivers != null) <
System.setProperty(«jdbc.drivers», drivers);
System.out.println(«Property \»jdbc.drivers\» 2″> System.getProperty(«jdbc.drivers»));
>
String url = props.getProperty(«jdbc.url»);
return DriverManager.getConnection(url, props);
>
>
Если попытка установки соединения окончится неудачей, то будет создан экземпляр исключения типа SQLException . В случае удачи, вновь созданный объект типа Connection будет представлять физическое соединение с базой данных.
Шаг. 4 : Выполнение SQL запроса.
Для выполнения запроса к базе данных необходимы два объекта. Первый объект необходим для применения интерфейсов Statement , PreparedStatement или CallableStatement . Эти три интерфейса имеют разное назначение. Интерфейс java . sql . Statement используется для выполнения статических запросов без параметров. Интерфейс java . sql . PreparedStatement используется для выполнения запросов с изменяемыми параметрами параметрами. И, наконец, java . sql . CallableStatement используется для выполнения хранимых в базе данных процедур.
Второй необходимый для выполнения запроса объект имеет тип ResultSet . Этот объект содержит результат выполнения запроса и предусматривает ряд итераторов, с помощью которых данные результата можно построчно извлекать из объекта.
В приведенном выше примере инициализируется объект типа Statement с помощью метода createStatement () объекта типа Connection . Далее переменной типа String присваивается SQL -предложение, которое используется в качестве параметра при выполнении метода executeQuery () объекта типа Statement . Данный метод возвращает объект типа ResultSet , содержащий результат выполнения запроса. В случае возникновения какой либо ошибки возникает исключение типа SQLException .
Шаг. 5: Просмотр результата запроса.
Данные результата запроса сохраняются в объекте типа ResultSet в виде таблицы из строк и столбцов. Количество строк определяется критерием выборки, указанном во фразе WHERE SQL -предложения. Порядок следования столбцов результата запроса соответствует порядку их перечисления в SQL -предложении. Тип данных столбцов результата запроса соответствует типу данных столбцов таблицы базы данных.
Для извлечения данных результата запроса из объекта типа ResultSet используются методы getXXX () этого объекта, где XXX соответствует извлекаемому типу данных (например: getInt (), getString (), getDate ()), а в скобках указывается в качестве параметра номер (начиная с 1) или символьное имя столбца. Объект типа ResultSet использует курсор, указывающий на текущую строку результата запроса. Перемещение курсора по строкам результата запроса осуществляется с помощью метода next () объекта типа ResultSet , содержащего этот результат. При этом существует два специальных положения курсора, указывающие на положение перед первой строкой результата запроса и на положение после последней строки результата запроса. Эти положения ничего не содержат и попытка получить из них данные приведет к возникновению исключения типа SQLException . В первоначальный момент сразу после заполнения объекта данными результата запроса курсор установлен в крайнем верхнем положении – перед первой строкой данных. По этому перед выполнением метода getXXX () необходимо переместить курсор на первую строку результата запроса. Обычно это делается с помощью уже упомянутого метода next (), который возвращает значение TRUE , если курсор после его выполнения указывает на строку с данными результата запроса, и FALSE , если курсор указывает на пустое положение после последней строки данных результата запроса. Это позволяет использовать метод в качестве условия при организации цикла перемещения курсора по строкам данных результата запроса.
Шаг. 6: Освобождение ресурсов.
Освобождение ресурсов осуществляется методом close () объекта, представляющего собой тот или иной ресурс, например: результат запроса, соединение с базой и т.д.
JDBC
JDBC — это платформенно независимый промышленный стандарт взаимодействия Java-приложений с реляционными базами данных. Впервые был включен в состав JDK 1.1 в 1997 году. JDBC управляет:
Освойте профессию «Java-разработчик»
- подключением к базе данных;
- выдачей запросов и команд;
- обработкой данных, полученных из базы.
Как работает JDBC
Пакет JDBC состоит из двух главных компонентов:
- API (программного интерфейса), который поддерживает связь между Java-приложением и менеджером JDBC;
- Драйвера JDBC, который поддерживает связь между менеджером JDBC и драйвером базы данных.
Профессия / 14 месяцев
Java-разработчик
Освойте востребованный язык
Соединение с базой устанавливается по особому URL. При этом разработчику не нужно знать специфику конкретной базы — API выступает в качестве посредника между базой и приложением. Это упрощает как процесс создания приложения, так и переход на базу данных другого типа.
Этапы подключения к базе данных
- Установка базы данных на сервер или выбор облачного сервиса, к которому нужно получить доступ.
- Подключение библиотеки JDBC.
- Проверка факта нахождения необходимого драйвера JDBC в classpath.
- Установление соединения с базой данных с помощью библиотеки JDBC.
- Использование установленного соединения для выполнения команд SQL.
- Закрытие соединения после окончания сеанса.
Рассмотрим каждый из этих шагов подробнее.
Установка SQLite
СУБД (система управления базами данных) SQLite отличается компактными размерами и простотой установки — для ее использования не нужна инсталляция дополнительных сервисов. Вся информация хранится в одном файле формата .db, который нужно поместить в папку с программой. Учебную базу можно скачать здесь.
Станьте Java-разработчиком
и создавайте сложные сервисы
на востребованном языке
Импорт JDBC в Java-приложение
Для использования JDBC, как и в случае со всеми остальными приложениями на платформе Java, в системе должен быть установлен JDK. Код для работы с JDBC можно писать как в среде разработки (IDE), так и в обычном текстовом редакторе. Простейшая программа может выглядеть так:
class WhatIsJdbcpublic static void main(String args[])System.out.println(«Hello World»);
>
>
Скомпилируйте этот код с помощью команды:
javac WhatIsJdbc.java
Теперь, когда программа готова, можно импортировать библиотеки JDBC. Для этого вставьте данный ниже код перед строками программы «Hello, World»:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.ResultSet;
import java.sql.Statement;
Каждый из импортированных модулей предоставляет доступ к классам, которые необходимы для взаимодействия Java-приложения с базой данных:
- Connection подготавливает подключение к базе.
- DriverManager обеспечивает подключение. Другая опция — модуль DataSource.
- SQLException обрабатывает SQL-ошибки, возникающие при взаимодействии приложении и базы данных.
- ResultSet и Statement моделируют наборы результатов данных и операторы SQL.
Добавление JDBC-драйвера в classpath
JDBC-драйвер — это класс, обеспечивающий взаимодействие интерфейса JDBC API с базой данных определенного типа. Драйвер для SQLite представляет собой .jar-файл — его нужно добавить в classpath, как показано ниже:
java.exe -classpath /path-to-driver/sqlite-jdbc-3.23.1.jar:. WhatIsJdbc
Установление соединения с базой данных
Теперь в classpath есть доступ к драйверу. Вставьте приведенный ниже код в файл с вашей первой программой:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.ResultSet;
import java.sql.Statement;
class WhatIsJdbcpublic static void main(String[] args) Connection conn = null;
try String url = «jdbc:sqlite:path-to-db/chinook/chinook.db»;
conn = DriverManager.getConnection(url);
System.out.println(«Соединение установлено»);
> catch (SQLException e) throw new Error(«Ошибка при подключении к базе данных», e);
> finally try if (conn != null) conn.close();
>
> catch (SQLException ex) System.out.println(ex.getMessage());
>
>
>
Теперь можно компилировать и запускать код. В случае успешного подключения появится сообщение «Соединение установлено».
Создание запроса к базе данных
В приведенном ниже примере показано, как создать запрос к базе SQLite, используя Connection и Statement:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.ResultSet;
import java.sql.Statement;
class WhatIsJdbcpublic static void main(String[] args) Connection conn = null;
try String url = «jdbc:sqlite:path-to-db-file/chinook/chinook.db»;
conn = DriverManager.getConnection(url);
Statement stmt = null;
String query = «select * from albums»;
try stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(query);
while (rs.next()) String name = rs.getString(«title»);
System.out.println(name);
>
> catch (SQLException e ) throw new Error(«Problem», e);
> finally if (stmt != null) < stmt.close(); >
>
> catch (SQLException e) throw new Error(«Problem», e);
> finally try if (conn != null) conn.close();
>
> catch (SQLException ex) System.out.println(ex.getMessage());
>
>
>
Результатом выполнения этого кода будет вывод списка музыкальных альбомов из учебной базы в консоль.
Возможностей JDBC API достаточно для реализации простых приложений. Для более масштабных решений чаще используют JPA API, который позволяет сохранять Java-объекты в базе данных.
Java-разработчик
Java уже 20 лет в мировом топе языков программирования. На нем создают сложные финансовые сервисы, стриминги и маркетплейсы. Освойте технологии, которые нужны для backend-разработки, за 14 месяцев.
Статьи по теме:
Какой класс или интерфейс позволяет отсылать запросы в бд
Мы приступаем к одному из очень важных разделов программирования на Java — работа с базами данных. Данные являются наверно наиглавнейшей составляющей программирования и вопрос их хранения крайне актуален. Не буду больше говорить о важности этого вопроса — тут можно писать много-много-много разных интересных слов.
Сервер баз данных
Сама идея сервера баз данных и СУБД в виде отдельной программы появилось по совершенно очевидным причинам. Базы данных мгновенно стали МНОГОПОЛЬЗОВАТЕЛЬСКИМИ. Данные нужны всем и возможность одновременного доступа к ним является очевидной. Проблема базы данных в виде обычного файла заключается в том, что к этому файлу будет обращаться сарзу много программ, каждая из которых захочет внести изменения или получить данные. Организовать такой доступ на уровне файловой системы — по сути, невыполнимая задача.
Во-первых — файл должен быть доступен всем пользователям, что требует перекачку данных по сети и хранение этого файла где-то на сетевом диске. Большие объемы данных по сети (пусть даже с высокой скоростью) — кроме слова “отвратительно” у меня ничего не приходит на ум.
Во-вторых — попытка одновременной записи в файл несколькими программами обречена на провал. Для организации такого доступа обычной файловой системы явно не достаточно.
В-третьих — организация прав доступа к тем или иным данным тоже становится непосильной задачей.
В-четвертых — надо “разруливать” конфликты при одновременном доступе к одним и тем же данным.
После небольшого анализа, кроме этих вопросов, можно увидеть еще немалое количество проблем, которые надо решить при мультипользовательском доступе к данным.
В итоге было принято (и реализовано) вполне здравое решение — написать специальную программу, которая имеет несколько названий — Система Управления Базами Данных (СУБД), сервер баз данных и т.д. Я буду называть ее СУБД.
Суть и цель этой программы — организовать централизованный доступ к данным. Т.е. все запросы на получение или изменение данных от клиентских приложений (клинетов) посылаются (обычно по сети и по протоколу TCP/IP) именно в эту программу. И уже эта программа будет заниматься всеми вышеупомянутыми проблемами:
- СУБД будет иметь некоторый набор команд, который позволит записывать и получать данные
- СУБД будет сама работать с файловой системой (нередко у нее бывает своя собственная файловая система для скорости)
- СУБД предоставит механизмы разграничения доступа к разным данным
- СУБД будет решать задачи одновременного доступа к данным
В итоге мы получаем достаточно ясную архитектуру — есть СУБД, которая сосредоточена на работе с данными и есть клиенты, которые могут посылать запросы к СУБД.
При работе с СУБД клиенты должны решить достаточно четкие задачи:
- Клиент должен соединиться с СУБД. Как я уже упоминал, чаще всего для общения используется сетевой протокол TCP/IP. В момент подключения клиент также передает свой логин/пароль, чтобы СУБД могла его идентифицировать и в дальнейшем позволить (или не позволить) производить те или иные действия над данными
- Клиент может посылать команды для изменения/получения данных в СУБД
- Данные внутри СУБД хранятся в определенных структурах и к этим структурам можно обратиться через команды
SQL базы данных
Могу предположить,что вышеупомянутые задачи и породили именно SQL-базы данных. В них есть удобные и понятные структуры для хранения данных — таблицы. Эти таблицы можно связывать в виде отношений и тем самым дается возможность хранить достаточно сложно организованные данные. Был придуман специальный язык — SQL (Structured Query Language — структурированный язык запросов). Этот язык хоть и имеет всего 4 команды для манипулирования данными, позволяет создавать очень сложные и заковыристые запросы.
На сегодняшний день SQL-базы данных являются самыми распространенными. В последние годы наметилась тенденция к использованию баз данных, основанные на других способах хранения и обработки данных, но пока их применение достаточно узконаправлено, хотя в некоторых случаях они действительно помогают решать важные задачи более эффективно, но все-таки пока SQL — самое главное направление баз данных. Почему я про это упоминаю ? Потому, что все наше знакомство с технологией работы с базами данных из Java будет сконцентрировано на SQL базах данных. С основными командами SQL вы можете познакомиться в различных учебниках. Их сейчас достаточно много и в большинстве своем они вполне понятны.
Возможно, что я тоже когда-нибудь внесу свою лепту в рассказы про SQL, но в данном разделе предполагается, что вы уже знакомы с основными идеями построения реляционных баз данных и с самим языком SQL.
JDBC — Java Database Connectivity — архитектура
Если попробовать определить JDBC простыми словами, то JDBC представляет собой описание интерфейсов и некоторых классов, которые позволяют работать с базами данных из Java. Еще раз: JDBC — это набор интерфейсов (и классов), которые позволяют работать с базами данных.
И вот с этого момента я попробую написать более сложное и в тоже время более четкое описание архитектуры JDBC. Главным принципом архитектуры является унифицированный (универсальный, стандартный) способ общения с разными базами данных. Т.е. с точки зрения приложения на Java общение с Oracle или PostgreSQL не должно отличаться. По возможности совсем не должно отличаться.
Сами SQL-запросы могут отличаться за счет разного набора функций для дат, строк и других. Но это уже строка запроса другая, а алгоритм и набор команд для доставки запроса на SQL-сервер и получение данных от SQL-сервера отличаться не должны.
Наше приложение не должно думать над тем, с какой базе оно работает — все базы должны выглядеть одинаково. Но при всем желании внутреннее устройство передачи данных для разных СУБД разное. Правила передачи байтов для Oracle отличается от правил передачи байтов для MySQL и PostgreSQL. В итоге имеем — с одной стороны все выглядят одинаково, но с другой реализации будут разные. Ничего не приходит в голову ?
Еще раз — разные реализации, но одинаковый набор функциональности.
Думаю, что вы уже догадались — типичный полиморфизм через интерфейсы. Именно на этом и строится архитектура JDBC. Смотрим рисунок.
Как следует из рисунка, приложение работает с абстракцией JDBC в виде набора интерфейсов. А вот реализация для каждого типа СУБД используется своя. Эта реализация называется “JDBC-драйвер”. Для каждого типа СУБД используется свой JDBC-драйвер — для Oracle свой, для MySQL — свой. Как приложение выбирает, какой надо использовать, мы увидим чуть позже.
Что важно понять сейчас — система JDBC позволяет загрузить JDBC-драйвер для конкретной СУБД и единообразно использовать компоненты этого драйвера за счет того, что мы к этим компонентам обращаемся не напрямую, а через интерфейсы.
Т.е. наше приложение в принципе не различает, обращается оно к Oracle или PostgreSQL — все обращения идут через стандартные интерфейсы, за которыми “прячется” реализация.
Пока я предлагаю отметить несколько важных интерфейсов, которые мы будем рассматривать позже, но мне бы хотелось, чтобы у вас этот список уже был, чтобы вы могли по мере прочтения отмечать — “да, вот он важный интерфейс/класс и я теперь знаю, куда он встраивается”. Вот они:
Теперь давайте рассмотрим несложный пример и поймем, как работает JDBC.
JDBC — пример соединения и простого вызова
Попробуем посмотреть на несложном примере, как используется JDBC-драйвер. В нем же мы познакомимся с некоторыми важными интерфейсами и классами.
Предварительно нам необходимо загрузить JDBC-драйвер для PostgreSQL. На данный момент это можно сделать со страницы PostgreSQL JDBC Download
Если вы не нашли эту страницу, то просто наберите в поисковике “PostgreSQL JDBC download” и в первых же строках найдете нужную страницу.
Т.к. я пишу эти статьи для JDK 1.7 и 1.8, то я выбрал строку “JDBC41 Postgresql Driver, Version 9.4-1208” — может через пару-тройку лет это будет уже не так.
Если вы выполнили SQL-скрипт из раздела Установка PostgreSQL, который создавал таблицу JC_CONTACT и вставил туда пару строк, то эта программа позволит вам “вытащить” эти данные и показать их на экране. Это конечно же очень простая программа, но на ней мы сможем посмотреть очень важные моменты. Итак, вот код:
package edu . javacourse . database ;
import java . sql . Connection ;
import java . sql . DriverManager ;
import java . sql . ResultSet ;
import java . sql . Statement ;
public class SimpleDb
public static void main ( String [ ] args ) <
SimpleDb m = new SimpleDb ( ) ;
m . testDatabase ( ) ;
private void testDatabase ( ) <
Class . forName ( "org.postgresql.Driver" ) ;
String url = "jdbc:postgresql://localhost:5432/contactdb" ;
String login = "postgres" ;
String password = "postgres" ;
Connection con = DriverManager . getConnection ( url , login , password ) ;
Statement stmt = con . createStatement ( ) ;
ResultSet rs = stmt . executeQuery ( "SELECT * FROM JC_CONTACT" ) ;
while ( rs . next ( ) ) <
String str = rs . getString ( "contact_id" ) + ":" + rs . getString ( 2 ) ;
System . out . println ( "Contact:" + str ) ;
stmt . close ( ) ;
con . close ( ) ;
> catch ( Exception e ) <
e . printStackTrace ( ) ;
Для запуска этой програмы необходимо подключить JDBC-драйвер для PostgreSQL. Прочитайте раздел Что такое JAR-файлы для того, чтобы подключить нужный JAR с JDBC-драйвером к проекту в NetBeans.
Для запуска нашей программы из командной строки достаточно собрать этот код (причем здесь не надо подключать JAR на этапе компиляции — только на момент запуска).
Итак, команда для сборки:
javac edu / javacourse / database / SimpleDb . java
И теперь команда для запуска:
java — cp . ; postgresql — 9.4.1208.jre7.jar edu . javacourse . database . SimpleDb
Для запуска проекта в NetBeans предлагаю вам самостоятельно разобраться, как подключить JAR-файл — пример этого указан в статье Что такое JAR-файлы
Начнем разбор нашей программы с самого начала. Итак, в чем же заключается набор вызовов для создания соединения с базой
Class . forName ( «org.postgresql.Driver» ) ;
String url = «jdbc:postgresql://localhost:5432/contactdb» ;
String login = «postgres» ;
String password = «postgres» ;
Connection con con = DriverManager . getConnection ( url , login , password ) ;
Вызов Class.forName() мы уже встречали, когда разговаривали о рефлексии. Если вы этого не сделали — обязательно прочитайте, иначе многое будет непонятно. Так вот наш вызов загружает один из ключевых классов JDBC, который реализует очень важный интерфейс java.sql.Driver. Почему этот класс так важен, мы разберем чуть ниже.
Следующим важным вызовом явлется DriverManager.getConnection(url, login, password);.
Думаю, что параметры login и password достаточно оччевидны — это логин и пароль для подключения к СУБД. А вот первый параметр — url надо рассмотреть подробно.
Параметр url является строкой и я люблю его разбивать на две части. Первая часть jdbc:postgresql: позволяет идентифицировать, к какому типу СУБД вы подключаетесь — Oracle, MySQL, PostgreSQL, IBM DB2, MS SQL Server. В нашем случае тип базы данных — PostgreSQL.
Вторая часть — //localhost:5432/contactdb — определяет конкретный экземпляр выбранной базы данных. Т.е. если первая часть url указывает, что мы хотим работать с PostgreSQL, то вторая часть указывает на каком хосте и на каком порту (опять вспоминаем основы TCP/IP) работает конкретный экземпляр PostgreSQL. Еще раз — первая часть поределяет только тип, вторая часть — параметры оединения с конкретным экземпляром СУБД.
Как вы можете видеть, вторая часть включает помимо IP-адреса и порта (localhost:3306) включает имя базы данных, с которой вы будете соединяться.
И вот теперь возвращаемся к интерфейсу java.sql.Driver. Достаточно очевидно, что сложное приложение на Java может работать с несколькими типами СУБД и одновременно в приложнении участвуют несколько JDBC-драйверов для разных типов СУБД. Так как же класс DriverManager определяет, какой тип СУБД вы собираетесь использовать ?
Придется нам вернуться к моменту загрузки класса — Class.forName(). Большинство классов в момент своей загрузки выполняют очень важный шаг — они РЕГИСТРИРУЮТСЯ у класса DriverManager. Как они это делают ? Посмотрите документацию на класс DriverManager — например здесь:
DriverManager
Среди методов мы можете найти этот: registerDriver(Driver driver). Причем метод статический и создавать экземпляр DriverManager не надо. Таким образом драйвер под конкретный тип СУБД регистрируется у DriverManager. У этого класса (можно глянуть в исходники) создается список драйверов, каждый из которых реализует интерфейс java.sql.Driver. Что же происходить дальше ? Зайдем в документацию java.sql.Driver. Там есть два очень интересных метода:
- boolean acceptsURL(String url)
- Connection connect(String url, Properties info)
Первый метод как раз и позволяет классу DriverManager пройти по всему списку зарегистрированных у него драйверов и у каждого спросить — “ты умеешь работать с этим URL”. Отметим, что драйвер под конкретный тип СУБД работает с уникальным набором — MySQL принимает строку “jdbc:mysql:”, PostgreSQL — “jdbc:postgresql:” и т.д. Т.е. первая часть параметра url, о которой мы говорили немного раньше, как раз и позволяет классу DriverManager выбрать драйвер для определенного типа СУБД. Первый шаг сделан — мы выбрали нужный драйвер.
И вот тут приходит очередь второго метода — именно он позволяет создать соединение — возвращает экземпляр класса, который реализует еще один важный интерфейс — java.sql.Connection. Второй метод использует вторую часть url с адресом, портом и именем базы, а также используется логин и пароль. Снова обращаю ваше внимание на тот факт, что реальный класс будет какой-то специальный, под конкретный тип СУБД, но он обязательно должен реализовать интерфейс java.sql.Connection.
java.sql.Connection — это реальное соединение с конкретным экземпляром СУБД определенного типа. Наше соединение готово. Можем продолжать.
Следущий фрагмент кода уже будет проще: