Java и базы данных
Для хранения данных мы можем использовать различные базы данных — Oracle, MS SQL Server, MySQL, Postgres и т.д. Все эти системы упраления базами данных имеют свои особенности. Главное, что их объединяет это взаимодействие с хранилищем данных посредством команд SQL. И чтобы определить единый механизм взаимодействия с этими СУБД в Java еще начиная с 1996 был введен специальный прикладной интерфейс API, который называется JDBC .
То есть если мы хотим в приложении на языке Java взаимодействовать с базой данных, то необходимо использовать функциональные возможности JDBC. Данный API входит в состав Java (на текущий момент это версия JDBC 4.3), в частности, для работы с JDBC в программе Java достаточно подключить пакет java.sql . Для работы в Java EE есть аналогичный пакет javax.sql , который расширяет возможности JDBC.
Однако не все базы данных могут поддерживаться через JDBC. Для работы с определенной СУБД также необходим специальный драйвер. Каждый разработчик определенной СУБД обычно предоставляет свой драйвер для работы с JDBC. То есть если мы хотим работать с MySQL, то нам потребуется специальный драйвер для работы именно MySQL. Как правило, большиство драйверов доступны в свободном доступе на сайтах соответствующих СУБД. Обычно они представляют JAR-файлы. И преимущество JDBC как раз и состоит в том, что мы абстрагируемся от строения конкретной базы данных, а используем унифицированный интерфейс, который един для всех.
Для взаимодействия с базой данных через JDBC используются запросы SQL. В то же время возможности SQL для работы с каждой конкретной СУБД могут отличаться. Например, в MS SQL Server это T-SQL, в Oracle — это PL/SQL. Но в целом эти разновидности языка SQL не сильно отличаются.
Особенности запуска программы
На процесс компиляции необходимость работы с БД никак не сказывается, но влияет на процесс запуска программы. При запуске программы в командной строке необходимо указать путь к JAR-файлу драйвера после параметра -classpath .
java -classpath путь_к_файлу_драйвера:путь_к_классу_программы главный_класс_программы
Например, в папке C:\Java располагаются файл программы — Program.java, скомпилированный класс Program и файл драйвер, допустим, MySQL — mysql-connector-java-8.0.11.jar. Для выполнения класса Program мы можем использовать следующую команду:
java -classpath c:\Java\mysql-connector-java-8.0.11.jar;c:\Java Program
Если C:\Java является текущим каталогом, то мы можем сократить команду:
java -classpath mysql-connector-java-8.0.11.jar;. Program
В принципе мы можем и не использовать параметр -classpath, и запустить програму на выполнение обычным способом с помощью команды «java Program». Но в этом случае путь к драйверу должен быть добавлен в переменную Path.
50 лучших вопросов и ответов на собеседовании JDBC (2023 г.)
Вот вопросы и ответы на собеседовании по Java Database Connectivity (JDBC) для новичков и опытных кандидатов, желающих получить работу своей мечты.
1) Что такое JDBC?
JDBC означает подключение к базе данных Java. JDBC — это Java API, который взаимодействует с базой данных и выполняет SQL-запрос.
2) Что такое драйвер JDBC и сколько драйверов JDBC доступно?
Драйвер JDBC содержит классы и интерфейсы, которые помогают приложениям и базам данных Java.
Существует 4 типа драйверов JDBC.
- Драйвер типа 1 или драйвер моста JDBC-ODBC.
- Драйвер типа 2 или роднойAPI (Программный интерфейс приложения), частично Java-драйвер.
- Драйвер типа 3 или сетевой протокол, чистый драйвер Java.
- Драйвер типа 4 или чистый драйвер Java с собственным протоколом.
3) Как я могу подключиться MySQL или Oracle с Java?
//for MySQL < try < Class.forName ("com.mysql.jdbc.Driver"); con=DriverManager.getConnection ("jdbc: mysql://localhost:3306/dbanme", "username", "password"); Statement st=con.createstatement (); Resultset rs=st.executeQuery ("select * from user"); rs.next (); catch (ClassNotFoundException e) < System.err.println ("ClassNotFoundException in get Connection," + e.getMessage()); >catch (SQLException e) < System.err.println ("SQLException in getConnection," + e.getMessage ()) ;>//for oracle < try < Class.forName ("oracle.jdbc.driver.OracleDriver"); con= DriverManager.getConnection ("jdbc:oracle:thin:@localhost:1521:dbname", "username", "password"); Statement st=con.createstatement(); Resultset rs=st.executeQuery("select * from user"); Rs.next(); >catch (ClassNotFoundException e) < System.err.println("ClassNotFoundException in get Connection," +e.getMessage()); >catch (SQLException e) < System.err.println("SQLException in getConnection, " + e.getMessage()); >return con; >
Объяснение кода
Class.forName создает экземпляр драйвера JDBC и регистрируется в DriverManager.
Метод getConnection() всегда устанавливает соединение с базой данных.
Нам нужно создать объект Statement на основе вышеуказанного объекта соединения. Оператор вернет объект набора результатов. ResultSet.next () означает, что набор результатов все еще возвращает строку.
4) Какой драйвер JDBC самый быстрый?
Драйвер типа 4 или драйвер Native-protocol, чистый Java-драйвер, является самым быстрым драйвером.
5) Каковы компоненты API JDBC?
Существует четыре типа компонентов.

- JDBC-API
- Диспетчер драйверов JDBC
- Набор тестов JDBC
- Мост JDBC-ODBC
6) Каковы утверждения JDBC?
Существует 3 типа операторов JDBC, как показано ниже:
- заявление: Он выполнится SQL запрос (статический SQL-запрос) к базе данных.
- Подготовленное заявление: Используется, когда мы хотим повторно выполнить оператор SQL. Входные данные являются динамическими и вводятся во время выполнения.
- Вызываемое заявление: Используется, когда мы хотим выполнить хранимые процедуры.
7) Как мы можем выполнять хранимые процедуры?
Хранимые процедуры можно выполнить с помощью оператора JDBCcallable. Вот код.
Connection conn = null; CallableStatement callStmt = conn.prepareCall(""); callStmt.setString(1, "abcdefg");
8) Каковы преимущества использования ReadedStatement в Java?
Подготовленный оператор используется для многократного выполнения одних и тех же операторов SQL. Подготовленный оператор компилируется только один раз, хотя он использовал «n» раз.
9) Что такое набор результатов?
Интерфейс java.sql.ResultSet означает набор результатов SQL-запроса. Это означает, что курсор указывает на строку таблицы; он указывает на перед первой строкой.
10) Каковы типы ResultSet?
Доступно три типа ResultSet. Если мы не объявляем какой-либо ResultSet, это означает, что мы вызываем TYPE_FORWARD_ONLY.
- TYPE_FORWARD_ONLY: курсор может двигаться только вперед.
- TYPE_SCROLL_INSENSITIVE: курсор может перемещаться вперед и назад, но нечувствительно.
- TYPE_SCROLL_SENSITIVE: курсор может перемещаться вперед и назад, но это чувствительно.
11) Объясните разницу между RowSet и ResultSet в JDBC?
При подключении дескриптора ResultSet к БД мы не можем сделать Result как сериализованный объект.
Из-за вышеуказанной проблемы мы не можем передать набор результатов по сети.
RowSet расширяет интерфейс ResultSet, поэтому он содержит все методы из ResultSet. RowSet сериализуется.
Итак, мы можем передать набор строк из одного класса в другой, поскольку он не имеет связи с базой данных.
12) Зачем вам использовать setAutoCommit(false) в JDBC?
Если вы хотите отключить автоматическую фиксацию, установите Connection.setAutoCommit(false)
13) Что такое предупреждения базы данных в JDBC и как мы можем обрабатывать предупреждения базы данных в JDBC?
Предупреждение SQL или предупреждение базы данных является подклассом класса SQLException. Мы можем справиться с этим, используя метод getWarnings() для Connection, Statement и ResultSet.
14) Могу ли я получить нулевой набор результатов?
Нет, мы не можем получить нулевой набор результатов. ResultSet.next() может вернуть значение null, если следующая запись не содержит строки.
15) Что вы подразумеваете под метаданными и почему мы их используем?
Метаданные означают данные или информацию о других данных. Мы используем метаданные, чтобы получить версию продукта базы данных, имя драйвера, общее количество таблиц и представлений.
16) В чем разница между выполнением, ExecuteQuery и ExecuteUpdate в JDBC?
Execute(): его можно использовать для любого типа SQL-запроса.
ExecuteQuery(): его можно использовать для запроса выбора.
ExecuteUpdate(): его можно использовать для изменения/обновления таблицы.
17) Что такое пул соединений с базой данных? Преимущества использования пула соединений?
Пул соединений означает, что соединения будут храниться в кеше, и мы сможем повторно использовать их в будущем.
- Это быстрее
- Пулы соединений становятся проще для диагностики и анализа соединений с базой данных.
18) Какова функция класса DriverManager?
Это интерфейс между пользователем и драйверами. DriverManager отслеживает всю активность между базой данных и соответствующим драйвером.
19) В чем смысл пакетных обновлений?
Пакетное обновление означает одновременное выполнение набора/группы SQL-запросов.
Пакетные обновления можно использовать только для вставки, обновления и удаления, но не для запроса выбора.
20) Сколько пакетов доступно в JDBC API?
В JDBC API доступны два типа пакетов.
21) Каков тип возвращаемого значения для выполнения, ExecuteQuery и ExecuteUpdate?
Возвращаемый тип выполнения — логический.
Тип возвращаемого значения ExecuteQuery — объект ResultSet.
Тип возвращаемого значения ExecuteUpdate — int.
22) Индекс набора результатов начинается с 0 или 1?
Индекс набора результатов начинается с 1.
23) Какова роль Class.forName при загрузке драйверов?
Class.forName создает экземпляр драйвера JDBC и регистрируется в DriverManager.
24) Мост JDBC-ODBC многопоточный или нет?
Нет, мост JDBC-ODBC использует синхронизированные методы для сериализации всех вызовов, сделанных к ODBC.
25) Какой интерфейс управляет транзакциями в JDBC?
Интерфейс подключения отвечает за управление транзакциями в JDBC. Он предоставляет методы фиксации(), отката() и т. д.
26) Почему возникает ошибка «Нет подходящего драйвера»?
Нет подходящего драйвера» возникает, когда мы вызываем метод DriverManager.getConnection,
это может произойти по следующим причинам:
- невозможно загрузить точные драйверы JDBC перед вызовом метода getConnection.
- Возможно, это недействительный или неправильный URL-адрес JDBC.
27) Подготовленные заявления выполняются быстрее. Почему?
Выполнение подготовленного оператора происходит быстрее, чем прямое выполнение, поскольку оператор компилируется только один раз. Подготовленные операторы и драйвер JDBC соединяются друг с другом во время выполнения, и накладные расходы на соединение отсутствуют.
28) Можно ли подключиться к нескольким базам данных? Можем ли мы обновить или извлечь данные из двух, трех или многих баз данных, используя один оператор?
Да, возможно одновременное подключение к нескольким базам данных, но это зависит от конкретного драйвера.
Чтобы обновить и извлечь данные из другой базы данных, мы можем использовать один оператор. Но нам нужно промежуточное программное обеспечение для работы с несколькими базами данных или с одной базой данных.
29) Скажите мне разницу между setMaxRows(int) и SetFetchSize(int)?
| setMaxRows (целое) | SetFetchSize (целое)? |
| Определяет, сколько строк результирующий набор может содержать одновременно | Определяет количество строк, которые будут считаны из базы данных. |
30) Расскажите мне о специальных символах?
Специальному символу предшествует escape-символ. Пример —
SELECT NAME FROM TABLE WHERE NAME LIKE ‘\_%’
31) Что означает «грязное чтение» в базе данных?
Грязное чтение означает «прочитать значение, которое может быть правильным, а может и нет».
32) Что вы подразумеваете под двухфазными коммитами?
Двухфазная фиксация используется в процессе распределенных транзакций. Если какая-либо транзакция выполняется и она повлияет на несколько баз данных. Для синхронизации всех баз данных друг с другом будут использоваться двухфазные фиксации.
33) Сколько систем блокировки в JDBC?
В JDBC доступны два типа блокировки, с помощью которых мы можем обрабатывать более одного пользователя.
Если два пользователя просматривают одну и ту же запись, блокировка не выполняется. Если один пользователь обновляет запись, а второй пользователь также обновляет ту же запись. В это время мы собираемся использовать блокировку.
- Оптимистическая блокировка: он заблокирует запись только тогда, когда мы собираемся «обновить».
- Пессимистическая блокировка: запись будет заблокирована для просмотра, обновления и фиксации времени.
34) Каковы исключения в JDBC?
В JDBC существует четыре типа исключений.
- Исключение пакетного обновления
- Усечение данных
- SQL-исключение
- Предупреждение SQL
35) Дайте инструкции по подключению к БД с помощью JDBC?
Существует два способа подключения базы данных с помощью JDBC.
Он загрузит класс драйвера с помощью class.forName(driver class) и Class.forName().
После загрузки он передаст управление DriverManager.
DriverManager.getConnection() создаст соединение для доступа к базе данных.
Для DataSource нет необходимости использовать DriverManager с помощью JNDI. Он будет искать источник данных на сервере службы имен. Метод DataSource.getConnection() вернет объект Connection в БД.
36) Объясните архитектуру JDBC?
JDBC API поддерживает как двухуровневые, так и трехуровневые модели базы данных.
В двухуровневой модели приложение Java взаимодействует с источником данных.

В трехуровневой модели команды перенаправляются на «средний уровень» сервисов. После этого команды отправляются в источник данных.

37) Какие новые функции доступны в JDBC 4.0?
- Автоматическая загрузка по классу драйвера JDBC
- Расширенное управление соединениями
- RowId SQL включен
- Набор данных, реализованный SQL с использованием аннотаций.
- Улучшения обработки исключений SQL
- Поддержка SQL XML файлов
38) Какие пакеты используются в JDBC?
В JDBC используется 8 пакетов –
- sql.Driver
- Связь
- заявление
- Подготовленное заявление
- CallableStatement
- Набор результатов
- Результатсетметадата
- База данныхМетаДанные
39) Сколько RowSet доступно в JDBC?
Доступны два типа наборов строк:
- : – Подключенный объект RowSet мгновенно подключается к базе данных. Если приложение завершает работу, подключенный объект RowSet также завершается.
- Отключенные – Отключенный объект RowSet подключается к базе данных после выполнения необходимого запроса.
40) В чем смысл связи?
Интерфейс подключения состоит из методов взаимодействия с базой данных.
41) Объясните точку сохранения JDBC?
Точка сохранения представляет собой точку, к которой текущая транзакция может вернуться. Вместо отката всех изменений можно выбрать откат только некоторых из них.
42) Перечислите преимущества использования DataSource?
Источник данных разделяет работу между администратором и программистом/разработчиком.
Администратор создает объект DataSource и связывает его с реестром JNDI. Программист/разработчик извлекает объект DataSource из реестра. Затем он установит соединение с базой данных.
43) По какой причине нам нужен JdbcRowSet, подобный оболочке ResultSet?
Мы можем использовать объект ResultSet в качестве компонента JavaBeans.
- JdbcRowSet также можно использовать как компонент JavaBeans. Вот почему его можно создать и настроить во время проектирования или компиляции и выполнить во время выполнения.
- Все объекты jdbcRowSet можно прокручивать и обновлять.
44) Сколькими способами мы можем просмотреть набор результатов?
Существует 2 способа просмотра ResultSet.
Пример: getInt(String columnsName), getInt(int columnsIndex)
45) Сколькими способами вы можете обновить набор результатов?
Следующие методы помогут вам обновить набор результатов.
- обновитьстроку()
- deleteRow ()
- обновитьСтрока()
- отменитьRowUpdates()
- insertRow ()
46) Почему нам следует закрывать соединения с базой данных в Java?
Рекомендуется закрыть набор результатов, оператор и соединение. Если соединение исходит из пула, при закрытии соединение отправляется обратно в пул для повторного использования. Мы делаем это в блокеfinally<>, потому что если возникнет какое-либо исключение, у нас все равно будет возможность закрыть его.
47) Почему мы используем типы данных blob в JDBC?
Они используются для хранения большого объема данных в базе данных, таких как изображения, фильмы и т. д.
48) Как установить атрибут Concurrency в ResultSet?
Есть два уровня параллелизма
- CONCUR_READ_ONLY – предназначен только для чтения.
- CONCUR_UPDATABLE — предназначен как для чтения, так и для обновления.
49) В чем разница между курсорами клиентской и серверной базы данных?
Курсор на стороне сервера означает, что данные и результаты сохраняются на сервере. Только тогда, когда запрошенные данные отправляются клиенту.
Курсор на стороне клиента означает все данные, отправленные в местоположение клиента.
50) Как вставлять изображения в базу данных с помощью JDBC?
Изображения в базе данных используют тип данных BLOB, при этом изображение сохраняется в виде потока байтов. Ниже код показывает, как вставить изображение в БД.
Connection con = null; PreparedStatement prs = null; InputStream inputstrm = null; Class.forName("oracle.jdbc.driver.OracleDriver"); con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:mydb","username","yourpassword"); //localhost is your machine or hostname //1521 is your oracle database port number //mydb is your oracle database name. prs = con.prepareCall("insert into emp_profile values (. )"); prs.setInt(1, 1223); inputstrm = new FileInputStream(new File("emp_img.jpg")); prs.setBinaryStream(2, inputstrm); int count = prs.executeUpdate();
Эти вопросы для собеседования также помогут вам в устной речи.
Собеседование по Java EE — SQL, JDBC (вопросы и ответы)
Вопросы и ответы для собеседования о применении SQL, JDBC в Java разработке.
к списку вопросов раздела JEE
Вопросы
1. ANSI SQL
2. Основные элементы баз данных – таблицы, процедуры, функции, констрейнты и т.д..
3. Как вы понимаете null в базах данных?
4. Агрегатные функции, как они работают с null. Не забудьте о group by и having
5. Каким образом лучше добавлять большое количество записей в таблицу?
6. Что такое первая нормальная форма и процесс нормализации? Какие бывают нормальные формы?
7. В чем смысл индекса СУБД, как они устроены, как хранятся? Как бы вы реализовали тот же функционал?
8. Что такое JDBC API и когда его используют?
9. Что такое JDBC Driver и какие различные типы драйверов JDBC вы знаете?
10. Как JDBC API помогает достичь слабой связи между Java программой и JDBC Drivers API?
11. Что такое JDBC Connection? Покажите шаги для подключения программы к базе данных.
12. Как используется JDBC DriverManager class?
13. Как получить информацию о сервере базы данных из java программы?
14. Что такое JDBC Statement?
15. Какие различия между execute, executeQuery, executeUpdate?
16. Что такое JDBC PreparedStatement?
17. Как установить NULL значения в JDBC PreparedStatement?
18. Как используется метод getGeneratedKeys() в Statement?
19. Какие преимущества в использовании PreparedStatement над Statement?
20. Какие есть ограничения PreparedStatement и как их преодолеть?
21. Что такое JDBC ResultSet?
22. Какие существуют различные типы JDBC ResultSet?
23. Как используются методы setFetchSize() и SetMaxRows() в Statement?
24. Как вызвать Stored Procedures используя JDBC API?
25. Что такое JDBC Batch Processing и какие его преимущества?
26. Что такое JDBC Transaction Management и зачем он нужен?
27. Как откатить JDBC транзакцию?
28. Что такое JDBC Savepoint и как он используется?
29. Расскажите о JDBC DataSource. Какие преимущества он дает?
30. Как создать JDBC пул соединений используя JDBC DataSource и JNDI в Apache Tomcat Server?
31. Расскажите про Apache DBCP API.
32. Какие вы знаете уровни изоляции соединений в JDBC?
33. Что вы знаете о JDBC RowSet? Какие существуют различные типы RowSet?
34. В чем разница между ResultSet и RowSet?
35. Приведите пример наиболее распространенных исключений в JDBC.
36. Расскажите о типах данных CLOB и BLOB в JDBC.
37. Что вы знаете о «грязном чтении» (dirty read) в JDBC? Какой уровень изоляции предотвращает этот тип чтения?
38. Какие есть две фазы commit?
39. Приведите пример различных типов блокировки в JDBC.
40. Как вы понимаете DDL и DML выражения?
41. Какая разница между java.util.Date и java.sql.Date?
42. Как вставить изображение или необработанные данные в базу данных?
43. Что вы можете рассказать о фантомном чтении? Какой уровень изоляции его предотвращает?
44. Что такое SQL Warning? Как возвратить SQL предупреждения в JDBC программе?
45. Как запустить Oracle Stored Procedure с объектами базы данных IN/OUT?
46. Приведите пример возникновения java.sql.SQLException: No suitable driver found.
47. Best Practices в JDBC.
Ответы
1. ANSI SQL
SQL (structured query language — «язык структурированных запросов») — формальный непроцедурный язык программирования, применяемый для создания, модификации и управления данными в произвольной реляционной базе данных, управляемой соответствующей системой управления базами данных (СУБД). SQL основывается на исчислении кортежей. Стандарт SQL определяется с помощью кода ANSI.
*Вопрос «расскажите о SQL» очень широкий и не вписывается в рамки этой статьи. К прочтению любая информация из интернета, например:
SQL : ОБЗОР: http://www.sql.ru/docs/sql/u_sql/ch2.shtml
Wiki: https://ru.wikipedia.org/wiki/SQL
2. Основные элементы баз данных – таблицы, процедуры, функции, констрейнты и т.д.
Поле — это минимальный элемент базы данных, содержащий один неделимый квант информации. Каждое поле характеризуется именем и типом хранящихся в нем данных.
Запись — это совокупность нескольких разнородных полей, описывающая некоторую сущность предметной области.
Таблица базы данных — это набор однородных записей.
Хранимая процедура — объект базы данных, представляющий собой набор SQL-инструкций, который компилируется один раз и хранится на сервере. Хранимые процедуры очень похожи на обыкновенные процедуры языков высокого уровня, у них могут быть входные и выходные параметры и локальные переменные, в них могут производиться числовые вычисления и операции над символьными данными, результаты которых могут присваиваться переменным и параметрам. В хранимых процедурах могут выполняться стандартные операции с базами данных (как DDL, так и DML). Кроме того, в хранимых процедурах возможны циклы и ветвления, то есть в них могут использоваться инструкции управления процессом исполнения.
JDBC или с чего всё начинается

В современном мире без хранения данных никак. И история работы с базами данных началась уже очень давно, с появления JDBC. Предлагаю вспомнить то, без чего не обходися ни один современный фрэймворк, построенный поверх JDBC. Кроме того, даже работая с ними временами может понадобится возможность «вернуться к корням». Надеюсь, обзор поможет как вступительное слово или поможет что-то освежить в памяти.

Вступление
Одна из основных целей языка программирования — хранение и обработка информации. Чтобы лучше понять работу хранения данных стоит немного времени выделить на теорию и архитектуру приложений. Например, можно ознакомиться с литературой, а именно с книгой «Software Architect’s Handbook: Become a successful software architect by implementing effective arch. » авторства Joseph Ingeno. Как сказано, есть некий Data Tier или «Слой данных». Он включает в себя место хранения данных (например, SQL базу данных) и средства для работы с хранилищем данных (например, JDBC, о котором и пойдёт речь). Так же на сайте Microsoft есть статья: «Проектирование уровня сохраняемости инфраструктуры» в которой описывается архитектурное решение выделения из Data Tier дополнительного слоя — Persistence Layer. В таком случае Data Tier — это уровень хранения самих данных, в то время как Persistence Layer — это некоторый уровень абстракции для работы с данными из хранилища с уровня Data Tier. К уровню Persistence Layer можно отнести шаблон «DAO» или различные ORM. Но ORM — это тема отдельного разговора. Как Вы могли уже понять, вначале появился Data Tier. Ещё с времён JDK 1.1 в Java мире появился JDBC (Java DataBase Connectivity — соединение с базами данных на Java). Это стандарт взаимодействия Java-приложений с различными СУБД, реализованный в виде пакетов java.sql и javax.sql, входящих в состав Java SE:

Данный стандарт описан специфкицией «JSR 221 JDBC 4.1 API». Данная спецификация рассказывает нам о том, что JDBC API предоставляет программный доступ к реляционным базам данных из программ, написанных на Java. Так же рассказывает о том, что JDBC API является частью платформы Java и входит поэтому в Java SE и Java EE. JDBC API представлен двумя пакетами: java.sql and javax.sql. Давайте тогда с ними и познакомимся.

Начало работы
Чтобы понять что такое вообще JDBC API нам понадобится Java приложение. Удобнее всего воспользоваться одной из систем сборки проектов. Например, воспользуемся Gradle. Более подробно про Gradle можно прочитать в небольшом обзоре: «Краткое знакомство с Gradle». Для начала инициализируем новый Gradle проект. Так как функциональность Gradle реализуется через плагины, то для инициализации нам нужно воспользоваться «Gradle Build Init Plugin»:
gradle init --type java-application
Откроем после этого билд скрипт — файл build.gradle, где описывается наш проект и то, как с ним нужно работать. Нас интересует блок «dependencies«, где описываются зависимости — то есть те библиотеки/фрэймворки/api, без которых мы не можем работать и от которых мы зависим. По умолчанию мы увидим что-то вроде:
dependencies < // This dependency is found on compile classpath of this component and consumers. implementation 'com.google.guava:guava:26.0-jre' // Use JUnit test framework testImplementation 'junit:junit:4.12' >
Почему мы тут это видим? Это зависимости нашего проекта, которые нам сгенерировал автоматически Gradle при создании проекта. А так же потому что guava — это отдельная библиотека, не входящая в комплект с Java SE. JUnit так же не входит в комплект с Java SE. Но JDBC у нас есть «из коробки», то есть входит в состав Java SE. Получается JDBC у нас есть. Отлично. Что же нам ещё надо? Есть такая замечательная схема:

Как мы видим, и это логично, база данных является внешним компонентом, которого нет изначально в Java SE. Это объясняется просто — существует огромное количество баз данных и работать вы можете захотеть с любой. Например, есть PostgreSQL, Oracle, MySQL, H2. Каждая из этих баз данных поставляется отдельной компанией, которые называются поставщиками баз данных или database vendors. Каждая база данных написана на каком-то своём языке программирования (не обязательно Java). Чтобы с базой данных можно было работать из Java приложения поставщик базы данных пишет особый драйвер, который является своего образа адаптером. Такие JDBC совместимые (то есть у которых есть JDBC драйвер) ещё называют «JDBC-Compliant Database». Тут можно провести аналогию с компьютерными устройствами. Например, в блокноте есть кнопка «Печать». Каждый раз когда вы её нажимаете программа сообщает операционной системе, что приложение блокнот хочет напечатать. И у Вас есть принтер. Чтобы научить разговаривать единообразно вашу операционную систему с принтером Canon или HP понадобятся разные драйверы. Но для Вас, как пользователя, ничего не изменится. Вы по прежнему будете нажимать одну и ту же кнопку. Так и с JDBC. Вы выполняете один и тот же код, просто «под капотом» могут работать разные базы данных. Думаю, тут очень понятный подход. Каждый такой JDBC драйвер — это некоторый артефакт, библиотека, jar файл. Он то и является зависимостью для нашего проекта. Например, мы можем выбрать базу данных «H2 Database» и тогда нам надо добавить зависимость следующим образом:
dependencies < implementation 'com.h2database:h2:1.4.197'
То, как найти зависимость и как её описать указано на официальных сайтах поставщика БД или на "Maven Central". JDBC драйвер не является базой данных, как Вы поняли. А лишь является проводником к ней. Но есть такое понятие, как "In memory databases". Это такие базы данных, которые существуют в памяти на время жизни вашего приложения. Обычно, это часто используют для тестирования или для учебных целей. Это позволяет не ставить отдельный сервер баз данных на машине. Что нам очень даже подойдёт для знакомств с JDBC. Вот и готова наша песочница и мы приступаем.

Connection
- Через DriverManager
- Через DataSource
Connection con = DriverManager.getConnection(url, user, passwd);
Параметры можно взять с сайта выбранной нами базы данных. В нашем случае это H2 — "H2 Cheat Sheet". Перейдём в подготовленный Gradle'ом класс AppTest. Он содержит JUnit тесты. JUnit тест — это метод, который помечен аннотацией @Test . Юнит тесты не являются темой данного обзора, поэтому просто ограничимся пониманием того, что это описанные определённым образом методы, цель которых что-то протестировать. Согласно специфкиации JDBC и сайту H2 проверим, что мы получили подключение к БД. Напишем метод получения подключения:
private Connection getNewConnection() throws SQLException
Теперь напишем тест для этого метода, который проверит, что подключение действительно устанавливается:
@Test public void shouldGetJdbcConnection() throws SQLException < try(Connection connection = getNewConnection()) < assertTrue(connection.isValid(1)); assertFalse(connection.isClosed()); >>
Данный тест при выполнении проверит, что полученное подключение валидное (корректно созданное) и что оно не закрыто. Благодаря использованию конструкции try-with-resources мы освободим ресурсы после того, как они нам больше не нужны. Это убережёт нас от "провисших" соединений и утечек памяти. Так как любые действия с БД требуют подключения, то давайте для остальных тестовых методов, помеченных @Test, обеспечим в начале теста Connection, который мы освободим после теста. Для этого нам понадобится две аннотации: @Before и @After Добавим в класс AppTest новое поле, которое будет хранить JDBC подключение для тестов:
private static Connection connection;
И добавим новые методы:
@Before public void init() throws SQLException < connection = getNewConnection(); >@After public void close() throws SQLException
Теперь, любому тестовому методу гарантируется наличие JDBC connection и он не должен каждый раз сам его создавать.

Statements
- Statement: SQL выражение, которое не содержит параметров
- PreparedStatement : Подготовленное SQL выражение, содержащее входные параметры
- CallableStatement : SQL выражение с возможностью получить возвращаемое значение из хранимых процедур (SQL Stored Procedures).
private int executeUpdate(String query) throws SQLException < Statement statement = connection.createStatement(); // Для Insert, Update, Delete int result = statement.executeUpdate(query); return result; >
Добавим метод создания тестовой таблицы с использованием прошлого метода:
private void createCustomerTable() throws SQLException
Теперь протестируем это:
@Test public void shouldCreateCustomerTable() throws SQLException
Теперь давайте выполним запрос, да ещё и с параметром:
@Test public void shouldSelectData() throws SQLException
JDBC не поддерживает именованные параметры для PreparedStatement, поэтому сами параметры указываются вопросами, а указывая значение мы указываем индекс вопроса (начиная с 1, а не с нуля). В последнем тесте мы получили true как признак того, есть ли результат. Но как представлен результат запроса в JDBC API? А представлен он как ResultSet.

ResultSet
Понятие ResultSet описано в спецификации JDBC API в главе "CHAPTER 15 Result Sets". Прежде всего, там сказано, что ResultSet предоставляет методы для получения и манипуляции результатами выполненных запросов. То есть если метод execute вернул нам true, значит мы можем получить и ResultSet. Давайте вынесем вызов метода createCustomerTable() в метод init, который отмечен как @Before. Теперь доработаем наш тест shouldSelectData:
@Test public void shouldSelectData() throws SQLException < String query = "SELECT * FROM customers WHERE name = ?"; PreparedStatement statement = connection.prepareStatement(query); statement.setString(1, "Brian"); boolean hasResult = statement.execute(); assertTrue(hasResult); // Обработаем результат ResultSet resultSet = statement.getResultSet(); resultSet.next(); int age = resultSet.getInt("age"); assertEquals(33, age); >
Тут стоит отметить, что next — это метод, который двигает так называемый "курсор". Курсор в ResultSet указывает на некоторую строку. Таким образом, чтобы считать строку, на неё нужно этот самый курсор установить. Когда курсор перемещается, то метод перемещения курсора возвращает true, если курсор валидный (правильный, корректный), то есть указывает на данные. Если возвращает false, значит данных нет, то есть курсор не указывает на данные. Если попытаться получить данные с невалидным курсором, то мы получим ошибку: No data is available Ещё интересно, что через ResultSet можно обновлять или даже вставлять строки:
@Test public void shouldInsertInResultSet() throws SQLException
RowSet
JDBC помимо ResultSet вводит такое понятие, как RowSet. Подробнее можно прочитать здесь: "JDBC Basics: Using RowSet Objects". Существуют различные вариации использования. Например, самый простой случай может выглядеть так:
@Test public void shouldUseRowSet() throws SQLException
Как видно, RowSet похож на симбиоз statement (мы указали через него command) и выполнили command. Через него же мы управляем курсором (вызывая метод next) и из него же получаем данные. Интересен не только такой подход, но и возможные реализации. Например, CachedRowSet. Он является "отключённым" (то есть не использует постоянное подключение к БД) и требует явного выполнения синхронизации с БД:
CachedRowSet jdbcRsCached = new CachedRowSetImpl(); jdbcRsCached.acceptChanges(connection);
Подробнее можно прочитать в tutorial на сайте Oracle: "Using CachedRowSetObjects".

Metadata
Кроме запросов, подключение к БД (т.е. экземпляр класса Connection) предоставляет доступ к метаданным - данным о том, как настроена и как устроена наша база данных. Но для начала озвучим несколько ключевых моментов: URL подключения к нашей БД: "jdbc:h2:mem:test". test - это название нашей базы данных. Для JDBC API это каталог. И название будет в верхнем регистре, то есть TEST. Схема по умолчанию (Default schema) для H2 - PUBLIC. Теперь, напишем тест, который показывает все пользовательские таблицы. Почему пользовательские? Потому что в базах данных есть не только пользовательские (те, которые мы сами создали при помощи create table выражений), но и системные таблицы. Они необходимы, чтобы хранить системную информацию о структуре БД. У каждой БД такие системные таблицы могут храниться по-разному. Например, в H2 они хранятся в схеме "INFORMATION_SCHEMA". Интересно, что INFORMATION SCHEMA является общим подходом, но Oracle пошли иным путём. Подробнее можно прочитать здесь: "INFORMATION_SCHEMA и Oracle". Напишем тест, получающий метаданные по пользовательским таблицам:
@Test public void shoudGetMetadata() throws SQLException < // У нас URL = "jdbc:h2:mem:test", где test - название БД // Название БД = catalog DatabaseMetaData metaData = connection.getMetaData(); ResultSet result = metaData.getTables("TEST", "PUBLIC", "%", null); Listtables = new ArrayList<>(); while(result.next()) < tables.add(result.getString(2) + "." + result.getString(3)); >assertTrue(tables.contains("PUBLIC.CUSTOMERS")); >

Пул подключений
Пулу подключений в спецификации JDBC отведен раздел "Chapter 11 Connection Pooling". В нём же и даётся главное обоснование необходимости пула подключений. Каждый Coonection - это физическое подключение к БД. Его создание и закрытие - довольно "дорогая" работа. JDBC предоставляет лишь API для пула соединений. Поэтому, выбор реализации остаётся за нами. Например, к таким реализациям относится HikariCP. Соответственно, нам понадобится добавить пул к нам в зависимости проекта:
dependencies
Теперь надо как-то пул этот задействовать. Для этого нужно выполнить инициализацию источника данных, он же Datasource:
private DataSource getDatasource()
И напишем тест на получение подключения из пула:
@Test public void shouldGetConnectionFromDataSource() throws SQLException < DataSource datasource = getDatasource(); try (Connection con = datasource.getConnection()) < assertTrue(con.isValid(1)); >>

Транзакции
- Data Manipulation Language, он же DML (Insert, Update, Delete)
Транзакция завершается как только завершилось выполнение действия Select Statements
Транзакция завершается тогда, когда ResultSet будет закрыт (ResultSet#close) - CallableStatement и выражения, возвращающие несколько результатов
Когда все ассоциированные ResultSets будут закрыты и все выходные данные получены (включая кол-во апдейтов)
@Test public void shouldCommitTransaction() throws SQLException < connection.setAutoCommit(false); String query = "INSERT INTO customers VALUES (1, 'Max', 20)"; connection.createStatement().executeUpdate(query); connection.commit(); Statement statement = connection.createStatement(); statement.execute("SELECT * FROM customers"); ResultSet resultSet = statement.getResultSet(); int count = 0; while(resultSet.next()) < count++; >assertEquals(2, count); >
Всё просто. Но это так, пока у нас всего одна транзакция. А что делать, когда их несколько? Нужно их изолировать друг от друга. Поэтому, поговорим об уровнях изоляции транзакции и как с ними справляется JDBC.

Уровни изоляции
- Atomicity(Атомарность):
Никакая транзакция не будет зафиксирована в системе частично. Будут либо выполнены все её подоперации, либо не выполнено ни одной. - Consistency(Согласованность):
Каждая успешная транзакция по определению фиксирует только допустимые результаты. - Isolation(Изолированность):
Во время выполнения транзакции параллельные транзакции не должны оказывать влияния на её результат. - Durability(Долговечность):
Если транзакция успешно завершенеа, сделанные в ней изменения не будут отменены из-за какого-либо сбоя.
Далее, используем это в версиях:
dependencies < implementation "com.h2database:h2:$" implementation "com.zaxxer:HikariCP:$" testImplementation "junit:junit:$" >
Вы могли заметить, что версия h2 стала ниже. Позже мы увидим, зачем. Итак, как же применять уровни изолированности? Давайте посмотрим сразу небольшой практический пример:
@Test public void shouldGetReadUncommited() throws SQLException < Connection first = getNewConnection(); assertTrue(first.getMetaData().supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_UNCOMMITTED)); first.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); first.setAutoCommit(false); // Транзакиця на подключение. Поэтому первая транзакция с ReadUncommited вносит изменения String insertQuery = "INSERT INTO customers VALUES (5, 'Max', 15)"; first.createStatement().executeUpdate(insertQuery); // Вторая транзакция пытается их увидеть int rowCount = 0; JdbcRowSet jdbcRs = new JdbcRowSetImpl(getNewConnection()); jdbcRs.setCommand("SELECT * FROM customers"); jdbcRs.execute(); while (jdbcRs.next()) < rowCount++; >assertEquals(2, rowCount); >
Интересно, что данный тест может упасть на вендоре, который не поддерживает TRANSACTION_READ_UNCOMMITTED (например, sqlite или HSQL). А ещё уровень транзакции может просто не сработать. Помните мы указывали версию драйвера H2 Database? Если мы поднимем её до h2Version = '1.4.177' и выше, то READ UNCOMMITTED перестанет работать, хотя код мы не меняли. Это ещё раз доказывает, что выбор вендора и версии драйвера - это не просто буквы, от этого будет в реальности зависеть то, как будут выполняться ваши запросы. Про то, как исправить это поведение в версии 1.4.177 и как это не работает в версиях выше можно прочитать здесь: "Support READ UNCOMMITTED isolation level in MVStore mode".
Итог
- Огненный доклад: "Transactions: myths, surprises and opportunities" от Martin Kleppmann
- Юрий Ткач: "JPA. Транзакции"
- Юрик Ткач: "JDBC - Java для тестировщиков"
- Бесплатный курс на Udemy: "JDBC and MySQL"
- "Обработка объектов CallableStatement"
- IBM Developer: "Java Database Connectivity"
- IBM Knowledge Center: "Getting started with JDBC"
