Выбери формат для чтения
Загружаем конспект в формате docx
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
Сервер баз данных
Сама идея сервера баз данных и СУБД в виде отдельной программы появилась по совершенно очевидным причинам. Базы данных мгновенно стали МНОГОПОЛЬЗОВАТЕЛЬСКИМИ. Данные нужны всем, и возможность одновременного доступа к ним является очевидной. Проблема базы данных в виде обычного файла заключается в том, что к этому файлу будет обращаться сразу много программ, каждая из которых захочет внести изменения или получить данные. Организовать такой доступ на уровне файловой системы — по сути, невыполнимая задача.
Во-первых, файл должен быть доступен всем пользователям, что требует перекачку данных по сети и хранение этого файла где-то на сетевом диске.
Во-вторых, попытка одновременной записи в файл несколькими программами обречена на провал. Для организации такого доступа обычной файловой системы явно не достаточно.
В-третьих, организация прав доступа к тем или иным данным тоже становится непосильной задачей.
В-четвертых, надо “разруливать” конфликты при одновременном доступе к одним и тем же данным.
После небольшого анализа, кроме этих вопросов, можно увидеть еще немалое количество проблем, которые надо решить при мультипользовательском доступе к данным.
В итоге было принято (и реализовано) вполне здравое решение — написать специальную программу, которая имеет несколько названий — Система Управления Базами Данных (СУБД), сервер баз данных и т.д.
Суть и цель этой программы — организовать централизованный доступ к данным. Т.е. все запросы на получение или изменение данных от клиентских приложений (клинетов) посылаются (обычно по сети и по протоколу TCP/IP) именно в эту программу. И уже эта программа будет заниматься всеми вышеупомянутыми проблемами:
1 СУБД будет иметь некоторый набор команд, который позволит записывать и получать данные
2 СУБД будет сама работать с файловой системой (нередко у нее бывает своя собственная файловая система для скорости)
3 СУБД предоставит механизмы разграничения доступа к разным данным
4 СУБД будет решать задачи одновременного доступа к данным.
В итоге мы получаем достаточно ясную архитектуру — есть СУБД, которая сосредоточена на работе с данными и есть клиенты, которые могут посылать запросы к СУБД.
При работе с СУБД клиенты должны решить достаточно четкие задачи:
1 Клиент должен соединиться с СУБД. Чаще всего для общения используется сетевой протокол TCP/IP. В момент подключения клиент также передает свой логин/пароль, чтобы СУБД могла его идентифицировать и в дальнейшем позволить (или не позволить) производить те или иные действия над данными.
2 Клиент может посылать команды для изменения/получения данных в СУБД.
3 Данные внутри СУБД хранятся в определенных структурах, и к этим структурам можно обратиться через команды.
SQL базы данных
Можно предположить, что вышеупомянутые задачи и породили именно SQL-базы данных. В них есть удобные и понятные структуры для хранения данных — таблицы. Эти таблицы можно связывать в виде отношений, и тем самым дается возможность хранить достаточно сложно организованные данные. Был придуман специальный язык — SQL (Structured Query Language — структурированный язык запросов). Этот язык хоть и имеет всего 4 команды для манипулирования данными, позволяет создавать очень сложные и заковыристые запросы.
Далее рассмотрим, какие возможности предоставляет язык Java для создания систем управления базами данных.
JDBC — Java Database Connectivity — архитектура
Если попробовать определить JDBC простыми словами, то JDBC представляет собой описание интерфейсов и некоторых классов, которые позволяют работать с базами данных из Java.
Главным принципом архитектуры является унифицированный (универсальный, стандартный) способ общения с разными базами данных. Т.е. с точки зрения приложения на Java общение с Oracle или PostgreSQL не должно отличаться. По возможности совсем не должно отличаться.
Сами SQL-запросы могут отличаться за счет разного набора функций для дат, строк и других. Но это уже строка запроса другая, а алгоритм и набор команд для доставки запроса на SQL-сервер и получение данных от SQL-сервера отличаться не должны.
Наше приложение не должно думать над тем, с какой базой оно работает — все базы должны выглядеть одинаково. Но при всем желании внутреннее устройство передачи данных для разных СУБД разное. Правила передачи байтов для Oracle отличается от правил передачи байтов для MySQL и PostgreSQL. В итоге имеем — с одной стороны все выглядят одинаково, но с другой реализации будут разные. То есть разные реализации, но одинаковый набор функциональности.
Типичный прием реализации - полиморфизм через интерфейсы. Именно на этом и строится архитектура JDBC (см. Рисунок).
Как следует из рисунка, приложение работает с абстракцией JDBC в виде набора интерфейсов. А вот реализация для каждого типа СУБД используется своя. Эта реализация называется “JDBC-драйвер”. Для каждого типа СУБД используется свой JDBC-драйвер — для Oracle свой, для MySQL — свой. Как приложение выбирает, какой надо использовать, мы увидим чуть позже.
Что важно понять сейчас — система JDBC позволяет загрузить JDBC-драйвер для конкретной СУБД и единообразно использовать компоненты этого драйвера за счет того, что мы к этим компонентам обращаемся не напрямую, а через интерфейсы.
Т.е. наше приложение в принципе не различает, обращается оно к Oracle или PostgreSQL — все обращения идут через стандартные интерфейсы, за которыми “прячется” реализация.
Перечислим эти интерфейсы, позже рассмотрим их подробнее.
java.sql.DriverManager
java.sql.Driver
java.sql.Connection
java.sql.Statement
java.sql.PreparedStatement
java.sql.CallableStatement
java.sql.ResultSet
Теперь рассмотрим несложный пример, чтобы понять, как работает JDBC.
JDBC — пример соединения и простого вызова
Попробуем посмотреть на несложном примере, как используется JDBC-драйвер. В нем же мы познакомимся с некоторыми важными интерфейсами и классами.
Предварительно нам необходимо загрузить JDBC-драйвер для PostgreSQL. Для этого надо набрать в поисковике “PostgreSQL JDBC download”, и в первых же строках найдете нужную страницу.
Рассмотрим пример кода, подключающего базу данных. Разумеется, логин, пароль, имя базы данных, имя таблицы у каждого свои.
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() {
try {
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);
try {
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM имя_таблицы");
while (rs.next()) {
String str = rs.getString("contact_id") + ":" + rs.getString(2);
System.out.println("Contact:" + str);
}
rs.close();
stmt.close();
} finally {
con.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Для запуска этой программы необходимо подключить JDBC-драйвер для PostgreSQL.
Начнем разбор нашей программы с самого начала. Итак, в чем же заключается набор вызовов для создания соединения с базой:
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);
1
2
3
4
5
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 является строкой. Первая часть jdbc:postgresql: позволяет идентифицировать, к какому типу СУБД вы подключаетесь — Oracle, MySQL, PostgreSQL, IBM DB2, MS SQL Server. В нашем случае тип базы данных — PostgreSQL.
Вторая часть — //localhost:5432/contactdb — определяет конкретный экземпляр выбранной базы данных. Т.е. если первая часть url указывает, что мы хотим работать с PostgreSQL, то вторая часть указывает, на каком хосте и на каком порту работает конкретный экземпляр PostgreSQL.
Вторая часть помимо IP-адреса и порта включает имя базы данных, с которой вы будете соединяться.
Вернемся к интерфейсу java.sql.Driver. Достаточно очевидно, что сложное приложение на Java может работать с несколькими типами СУБД и одновременно в приложнении участвуют несколько JDBC-драйверов для разных типов СУБД. Так как же класс DriverManager определяет, какой тип СУБД вы собираетесь использовать?
Вернемся к моменту загрузки класса — Class.forName(). Большинство классов в момент своей загрузки выполняют очень важный шаг — они РЕГИСТРИРУЮТСЯ у класса DriverManager. Среди методов этого класса есть registerDriver(Driver driver). Причем метод статический, и создавать экземпляр DriverManager не надо. Таким образом, драйвер под конкретный тип СУБД регистрируется у DriverManager. У этого класса создается список драйверов, каждый из которых реализует интерфейс java.sql.Driver. Далее с помощью метода boolean acceptsURL(String url) класс DriverManager проходит по всему списку зарегистрированных у него драйверов и у каждого спрашивает: “Ты умеешь работать с этим URL?”. Отметим, что драйвер под конкретный тип СУБД работает с уникальным набором — MySQL принимает строку “jdbc:mysql:”, PostgreSQL — “jdbc:postgresql:” и т.д. Т.е. первая часть параметра url, о которой мы говорили немного раньше, как раз и позволяет классу DriverManager выбрать драйвер для определенного типа СУБД.
Потом происходит вызов метода Connection connect(String url, Properties info), именно он позволяет создать соединение — возвращает экземпляр класса, который реализует еще один важный интерфейс — java.sql.Connection. Второй метод использует вторую часть url с адресом, портом и именем базы, а также используется логин и пароль. Реальный класс будет какой-то специальный, под конкретный тип СУБД, но он обязательно должен реализовать интерфейс java.sql.Connection.
java.sql.Connection — это реальное соединение с конкретным экземпляром СУБД определенного типа. Наше соединение готово. Можем продолжать.
Следущий фрагмент кода уже будет проще:
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM JC_CONTACT");
1
2
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM JC_CONTACT");
Первая строка создает еще один важный элемент — запрос, который реализует интерфейс java.sql.Statement. Кроме этого интерфейса, используются тажке java.sql.PreparedStatement и java.sql.CallableStatement, но о них мы поговорим несколько позже.
Что здесь важно отметить — создание запроса делается через обращение к методу объекта java.sql.Connection — createStatement. Каждый производитель СУБД пишет свою реализацию всех интерфейсов.
Т.к. реализация java.sql.Connection будет под определенный тип СУБД, то и реализация java.sql.Statement тоже будет под определенный тип СУБД.
Вторая строка с помощью объекта-запроса java.sql.Statement делает запрос в таблицу st_student и получает еще один важный элемент — объект java.sql.ResultSet.
После получения данных в виде объекта ResultSet, мы можем через его методы “пробежать” по всему набору данных (это очень похоже на итераторы в коллекциях) и выбрать поля из этого набора.
while (rs.next()) {
String str = rs.getString("contact_id") + ":" + rs.getString(2);
System.out.println("Contact:" + str);
}
Упрощенно ResultSet можно рассматривать, как указатель на строку в таблице. Метод rs.next() делает попытку передвинуться на следующую запись. В случае успеха он возвращает true и передвигает указатель на следующую строку. Если строки закончились (или их не было вообще), возвращается false.
Когда мы передвинулись на следующую строку, то с помощью набора методов можно получить значения колонок в строке — мы использовали метод getString() в двух вариантах — один находит колонку по имени, второй — по индексу. Учтите, что номера колонок начинаются с 1, а не с 0, как это делается в массивах и коллекциях. Кроме метода getString() для получения строк, ResultSet имеет методы для получения чисел (цеых и вещественных), дат и много чего еще.
Обратите внимание, что я у всех объектов надо вызывать метод close(). Особенно важным является закрытие Connection. Закрытие Statement просто быстрее освобождает память от ресурсов, которые создавались при запросе. Учтите, что Statement закрывается автоматически при уничтожении объекта. Что же касается ResultSet, то он автоматически закрывается в момент закрытия Statement.
Так как в процессе установки соединения и выполнения запроса могут возникать ошибки, этот код обязательно должен заключаться в try … catch. Сначала создается соединение во внешнем блоке try … catch, и потом уже во внутреннем блоке try … catch выполняется запрос и получаются данные. В этом же блоке в разделе finally происходит закрытие соединения.
Такое построение дает уверенность, что вне зависимости от результата выполнения запроса и получения данных, соединение будет обязательно закрыто.