Глава 12
JDBC
Пространство — иллюзия,
дисковое пространство — тем более.
Драйверы, соединения и запросы
API
JDBC (Java DataBase Connectivity) — стандартный прикладной интер-
фейс языка Java для организации взаимодействия между приложением и СУБД.
Взаимодействие осуществляется с помощью драйверов JDBC, обеспечиваю-
щих реализацию общих интерфейсов для конкретных СУБД и конкретных
протоколов. В JDBC определяются четыре типа драйверов:
1. Драйвер, использующий другой прикладной интерфейс взаимодействия с СУБД,
в частности, ODBC (так называемый JDBC-ODBC — мост). Стандартный драй-
вер первого типа sun.jdbc.odbc.JdbcOdbcDriver входит в JDK.
2. Драйвер, работающий через внешние native библиотеки клиента СУБД.
3. Драйвер, работающий по сетевому и независимому от СУБД протоколу
с промежуточным Java-сервером, который, в свою очередь, подключается
к нужной СУБД.
4. Сетевой драйвер, работающий напрямую с нужной СУБД и не требующий
установки native-библиотек.
Предпочтение естественным образом отдается второму типу, однако если
приложение выполняется на машине, на которой не предполагается установка
клиента СУБД, то выбор производится между третьим и четвертым типами.
Причем четвертый тип работает напрямую с СУБД по ее протоколу, поэтому
можно предположить, что драйвер четвертого типа будет более эффективным
по сравнению с третьим типом с точки зрения производительности. Первый же
тип, как правило, используется редко, т. е. в тех случаях, когда у СУБД нет сво-
его драйвера JDBC, но присутствует драйвер ODBC.
JDBC предоставляет интерфейс для разработчиков, использующих различ-
ные СУБД. С помощью JDBC отсылаются SQL-запросы только к реляцион-
ным базам данных (БД), для которых существуют драйверы, знающие способ
общения с реальным сервером базы данных.
Последовательность действий для выполнения первого запроса.
JDBC
343
1. Подключение библиотеки с классом-драйвером базы данных.
Дополнительно требуется подключить к проекту библиотеку, содержащую
драйвер, поместив ее предварительно в папку /lib приложения.
mysql-connector-java-[номер версии]-bin.jar для СУБД MySQL,
ojdbc[номер версии].jar для СУБД Oracle.
2. Установка соединения с БД.
Для установки соединения с БД вызывается статический метод getConnection()
класса java.sql.DriverManager. В качестве параметров методу передаются
URL базы данных, логин пользователя БД и пароль доступа. Загрузка класса
драйвера базы данных при отсутствии ссылки на экземпляр этого класса
в JDBC 4.1 происходит автоматически при установке соединения экземпляром
DriverManager. Метод возвращает объект Connection. URL базы данных, со-
стоящий из типа и адреса физического расположения БД, может создаваться
в виде отдельной строки или извлекаться из файла ресурсов. Соответственно:
Connection cn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testphones",
"root",
"pass");
Connection cn = DriverManager.getConnection("jdbc:oracle:thin:@//localhost:1521:testphones",
"system",
"pass");
В результате будет возвращен объект Connection и будет одно установлен-
ное соединение с БД с именем testphones. Класс DriverManager предоставля-
ет средства для управления набором драйверов баз данных. С помощью метода
getDrivers() можно получить список всех доступных драйверов.
До появления JDBC 4.0 объект драйвера СУБД нужно было создавать явно
с помощью вызова соответственно:
Class.forName("com.mysql.jdbc.Driver");
Class.forName("oracle.jdbc.OracleDriver");
или зарегистрировать драйвер
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
DriverManager.registerDriver(new oracle.jdbc.OracleDriver());
В большинстве случаев в этом нет необходимости, так как экземпляр драй-
вера загружается автоматически при попытке получения соединения с БД.
3. Создание объекта для передачи запросов.
После создания объекта Connection и установки соединения можно начи-
нать работу с БД с помощью операторов SQL. Для выполнения запросов при-
меняется объект Statement, создаваемый вызовом метода createStatement()
класса Connection.
Statement st = cn.createStatement();
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
344
Объект класса Statement используется для выполнения SQL-запроса без его
предварительной подготовки. Могут применяться также объекты классов
PreparedStatement и CallableStatement для выполнения подготовленных за-
просов и хранимых процедур.
4. Выполнение запроса.
Созданные объекты можно использовать для выполнения запроса SQL, переда-
вая его в один из методов execute(String sql), executeBatch(), executeQuery(String
sql) или executeUpdate(String sql). Результаты выполнения запроса помеща-
ются в объект ResultSet:
/* выборка всех данных таблицы phonebook */
ResultSet rs = st.executeQuery("SELECT * FROM phonebook");
Для добавления, удаления или изменения информации в таблице запрос по-
мещается в метод executeUpdate().
5. Обработка результатов выполнения запроса производится методами интер-
фейса ResultSet, где самыми распространенными являются next
(), first(), previous(),
last() для навигации по строками таблицы результатов и группа методов по до-
ступу к информации вида getString(int pos), а также аналогичные методы, на-
чинающиеся с getТип(int pos) ( getInt(int pos), getFloat(int pos) и др.) и updateТип().
Среди них следует выделить методы getClob(int pos) и getBlob(int pos), позво-
ляющие извлекать из полей таблицы специфические объекты (Character Large
Object, Binary Large Object), которые могут быть, например, графическими или
архивными файлами. Эффективным способом извлечения значения поля из та-
блицы ответа является обращение к этому полю по его имени в строке резуль-
татов методами типа int getInt(String columnLabel), String getString(String
columnLabel), Object getObject(String columnLabel) и подобными им. Интерфейс
располагает большим числом методов по доступу к таблице результатов, поэ-
тому рекомендуется изучить его достаточно тщательно.
При первом вызове метода next() указатель перемещается на таблицу ре-
зультатов выборки в позицию первой строки таблицы ответа. Когда строки за-
кончатся, метод возвратит значение false.
6. Закрытие соединения, statement
st.close(); // закрывает также и ResultSet
cn.close();
После того, как база больше не нужна, соединение закрывается. Для того,
чтобы правильно пользоваться приведенными методами, программисту требу-
ется знать типы полей БД. В распределенных системах это знание предполага-
ется изначально. В Java 7 для объектов-ресурсов, требующих закрытия, реали-
зована технология try with resources.
JDBC
345
СУБД MySQL
СУБД MySQL совместима c JDBC и будет применяться для создания учеб-
ных баз данных. Версия CУБД может быть загружена с сайта www.mysql.com
.
Для корректной установки необходимо следовать инструкциям мастера. В про-
цессе установки следует создать администратора СУБД с именем root и паро-
лем, например, pass. Если планируется разворачивать реально работающее
приложе ние, необходимо исключить тривиальных пользователей сервера БД
(иначе злоумышленники могут получить полный доступ к БД). Для запуска
следует использовать команду из папки /mysql/bin:
mysqld-nt -standalone
Если не появится сообщение об ошибке, то СУБД MySQL запущена. Для
создания базы данных и ее таблиц используются команды языка SQL.
Простое соединение и простой запрос
Теперь следует воспользоваться всеми предыдущими инструкциями и со-
здать пользовательскую БД с именем testphones и одной таблицей PHONEBOOK.
Таблица должна содержать три поля: числовое (первичный ключ) —
IDPHONEBOOK, символьное — LASTNAME и числовое — PHONE и не-
сколько занесенных записей.
IDPHONEBOOK
LASTNAME
PHONE
1
Каптур
7756544
2
Artukevich
6861880
При создании таблицы следует задавать кодировку UTF-8, поддерживаю-
щую хранение символов кириллицы.
Приложение, осуществляющее простейший запрос на выбор всей информа-
ции из таблицы, выглядит следующим образом.
/* # 1 # простое соединение с БД и простой запрос # SimpleJDBCRunner.java */
package
by.bsu.data.main;
import
java.sql.Connection;
import
java.sql.DriverManager;
import
java.sql.Statement;
import
java.sql.ResultSet;
import
java.sql.SQLException;
import
java.util.ArrayList;
import
java.util.Properties;
import
by.bsu.data.subject.Abonent;
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
346
public
class SimpleJDBCRunner {
public
static void main(String[ ] args) {
String url = "jdbc:mysql://localhost:3306/testphones";
Properties prop = new Properties();
prop.put("user",
"root");
prop.put("password",
"pass");
prop.put("autoReconnect",
"true");
prop.put("characterEncoding",
"UTF-8");
prop.put("useUnicode",
"true");
Connection cn = null;
DriverManager.registerDriver( new com.mysql.jdbc.Driver());
try { // 1 блок
cn
=
DriverManager. getConnection(url, prop);
Statement
st
=
null
;
try { // 2 блок
st
=
cn.createStatement();
ResultSet
rs
=
null
;
try { // 3 блок
rs = st.executeQuery("SELECT * FROM phonebook");
ArrayList lst = new ArrayList<>();
while (rs.next()) {
int id = rs.getInt(1);
int phone = rs.getInt(3);
String name = rs.getString(2);
lst.add(new Abonent(id, phone, name));
}
if (lst.size() > 0) {
System.out.println(lst);
}
else
{
System.out.println("Not found");
}
}
finally
{ // для 3-го блока try
/*
* закрыть ResultSet, если он был открыт
* или ошибка произошла во время
* чтения из него данных
*/
if (rs != null) { // был ли создан ResultSet
rs.close();
}
else
{
System.err.println(
"ошибка во время чтения из БД");
}
}
}
finally
{
/*
* закрыть Statement, если он был открыт или ошибка
* произошла во время создания Statement
*/
if (st != null) { // для 2-го блока try
JDBC
347
st.close();
}
else
{
System.err.println("Statement не создан");
}
}
}
catch
(SQLException e) { // для 1-го блока try
System.err.println("DB connection error: " + e);
/*
* вывод сообщения о всех SQLException
*/
}
finally
{
/*
* закрыть Connection, если он был открыт
*/
if (cn != null) {
try {
cn.close();
}
catch
(SQLException e) {
System.err.println("Сonnection close error: " + e);
}
}
}
}
}
В несложном приложении достаточно контролировать закрытие соедине-
ния, так как незакрытое или «провисшее» соединение снижает быстродейст-
вие системы.
Класс Abonent, используемый приложением для хранения информации, из-
влеченной из БД, выглядит очень просто:
/* # 2 # класс с информацией # Entity.java # Abonent.java */
package
by.bsu.data.subject;
import
java.io.Serializable;
public
abstract class Entity implements Serializable, Cloneable {
private
int id;
public
Entity() {
}
public
Entity(int id) {
this.id = id;
}
public
int getId() {
return id;
}
public
void setId(int id) {
this.id = id;
}
}
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
348
package
by.bsu.data.subject;
public
class Abonent extends Entity {
private
int phone;
private
String lastname;
public
Abonent() {
}
public
Abonent(int id, int phone, String lastname) {
super(id);
this.phone = phone;
this.lastname = lastname;
}
public
int getPhone() {
return phone;
}
public
void setPhone(int phone) {
this.phone = phone;
}
public
String getLastname() {
return lastname;
}
public
void setLastname(String lastname) {
this.lastname = lastname;
}
@Override
public
String toString() {
return "Abonent [id=" + id + ", phone=" + phone +
", lastname=" + lastname + "]";
}
}
Параметры соединения можно задавать несколькими способами: с помо-
щью прямой передачи значений в коде класса, а также с помощью файлов
properties или xml. Окончательный выбор производится в зависимости от кон-
фигурации проекта.
Чтение параметров соединения с базой данных и получение соединения
следует вынести в отдельный класс. Класс ConnectorDB использует файл ре-
сурсов database.properties, в котором хранятся, как правило, параметры под-
ключения к БД, такие, как логин и пароль доступа. Например:
db.driver = com.mysql.jdbc.Driver
db.user = root
db.password = pass
db.poolsize = 32
db.url = jdbc:mysql://localhost:3306/testphones
db.useUnicode = true
db.encoding = UTF-8
Код класса ConnectorDB выглядит следующим образом:
JDBC
349
/* # 3 # установка соединения с БД # ConnectorDB.java */
package
by.bsu.data.connect;
import
java.sql.Connection;
import
java.sql.DriverManager;
import
java.sql.SQLException;
import
java.util.ResourceBundle;
public
class ConnectorDB {
public
static Connection getConnection() throws SQLException {
ResourceBundle resource = ResourceBundle. getBundle("database");
String url = resource.getString("db.url");
String user = resource.getString("db.user");
String pass = resource.getString("db.password");
return DriverManager. getConnection(url, user, pass);
}
}
В таком случае получение соединения с БД сведется к вызову
Connection cn = ConnectorDB. getConnection();
Метаданные
Существует целый ряд методов интерфейсов ResultSetMetaData
и DatabaseMetaData для интроспекции объектов. С помощью этих методов
можно получить список таблиц, определить типы, свойства и количество столб-
цов БД. Для строк подобных методов нет.
Получить объект ResultSetMetaData можно следующим образом:
ResultSetMetaData rsMetaData = rs.getMetaData();
Некоторые методы интерфейса ResultSetMetaData:
int getColumnCount() — возвращает число столбцов набора результатов
объекта ResultSet;
String getColumnName(int column) — возвращает имя указанного столбца
объекта ResultSet;
int getColumnType(int column) — возвращает тип данных указанного столб-
ца объекта ResultSet и т. д.
Получить объект DatabaseMetaData можно следующим образом:
DatabaseMetaData dbMetaData = cn.getMetaData();
Некоторые методы весьма обширного интерфейса DatabaseMetaData:
String getDatabaseProductName() — возвращает название СУБД;
String getDatabaseProductVersion() — возвращает номер версии СУБД;
String getDriverName() — возвращает имя драйвера JDBC;
String getUserName() — возвращает имя пользователя БД;
String getURL() — возвращает местонахождение источника данных;
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
350
ResultSet getTables() — возвращает набор типов таблиц, доступных для
данной БД, и т. д.
Подготовленные запросы и хранимые процедуры
Для представления запросов существует еще два типа объектов
PreparedStatement и CallableStatement. Объекты первого типа используются
при выполнении часто повторяющихся запросов SQL. Такой оператор предва-
рительно готовится и хранится в объекте, что ускоряет обмен информацией
с базой данных при многократном выполнении однотипных запросов. Второй
интерфейс используется для выполнения хранимых процедур, созданных сред-
ствами самой СУБД.
При использовании PreparedStatement невозможен sql injection attacks.
То есть если существует возможность передачи в запрос информации в виде
строки, то следует использовать для выполнения такого запроса объект
PreparedStatement.
Для подготовки SQL-запроса, в котором отсутствуют конкретные параме-
тры, используется метод prepareStatement(String sql) интерфейса Connection,
возвращающий объект PreparedStatement.
String sql = "INSERT INTO phonebook(idphonebook, lastname, phone) VALUES(?, ?, ?)";
PreparedStatement ps = cn.prepareStatement(sql);
Установка входных значений конкретных параметров этого объекта произ-
водится с помощью методов setString(int index, String x), setInt(int index, int x)
и подобных им, после чего и осуществляется непосредственное выполнение
запроса методами int executeUpdate(), ResultSet executeQuery().
/* # 4 # подготовка запроса на добавление информации # DataBaseHelper.java */
package
by.bsu.data.connect;
import
java.sql.Connection;
import
java.sql.PreparedStatement;
import
java.sql.SQLException;
import
by.bsu.data.subject.Abonent;
public
class DataBaseHelper {
private
final static String SQL_INSERT =
"INSERT INTO phonebook(idphonebook, lastname, phone ) VALUES(?,?,?)";
private
Connection connect;
public DataBaseHelper() throws SQLException {
connect = ConnectorDB.getConnection();
}
public
PreparedStatement getPreparedStatement(){
PreparedStatement ps = null;
try {
ps
=
connect.prepareStatement(SQL_INSERT);
}
catch
(SQLException e) {
JDBC
351
e.printStackTrace();
}
return ps;
}
public
boolean insertAbonent(PreparedStatement ps, Abonent ab) {
boolean flag = false;
try {
ps.setInt(1,
ab.getId());
ps.setString(2,
ab.getName());
ps.setInt(3,
ab.getPhone());
ps.executeUpdate();
flag
=
true
;
}
catch
(SQLException e) {
e.printStackTrace();
}
return flag;
}
public
void closeStatement(PreparedStatement ps) {
if (ps != null) {
try {
ps.close();
}
catch
(SQLException e) {
e.printStackTrace();
}
}
}
}
Так как данный оператор предварительно подготовлен, то он выполняется
быстрее обычных операторов, ему соответствующих. Оценить преимущества
во времени можно, выполнив большое число повторяемых запросов с предва-
рительной подготовкой запроса и без нее.
/* # 5 # добавление нескольких записей в БД # PreparedJDBCRunner.java */
package
by.bsu.data.main;
import
java.sql.PreparedStatement;
import
java.sql.SQLException;
import
java.util.ArrayList;
import
by.bsu.data.connect.DataBaseHelper;
import
by.bsu.data.subject.Abonent;
public
class PreparedJDBCRunner {
public
static void main(String[] args) {
ArrayList list = new ArrayList() {
{
add(new Abonent(87, 1658468, "Кожух Дмитрий"));
add(new Abonent(51, 8866711, "Буйкевич Александр"));
}
};
DataBaseHelper helper = null;
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
352
PreparedStatement statement = null;
try
{
helper
=
new
DataBaseHelper();
statement
=
helper.getPreparedStatement();
for
(Abonent abonent : list) {
helper.insertAbonent(statement,
abonent);
}
}
catch
(SQLException e) {
e.printStackTrace();
}
finally
{
helper.closeStatement(statement);
}
}
}
Интерфейс CallableStatement расширяет возможности интерфейса
PreparedStatement и обеспечивает выполнение хранимых процедур.
Хранимая процедура — это, в общем случае, именованная последователь-
ность команд SQL, рассматриваемых как единое целое и выполняющаяся
в адресном пространстве процессов СУБД, который можно вызвать извне
(в зависимости от политики доступа используемой СУБД). В данном случае
хранимая процедура будет рассматриваться в более узком смысле как последо-
вательность команд SQL, хранимых в БД и доступных любому пользователю
этой СУБД. Механизм создания и настройки хранимых процедур зависит
от конкретной базы данных. Для создания объекта CallableStatement вызыва-
ется метод prepareCall() объекта Connection.
Интерфейс CallableStatement позволяет исполнять хранимые проце дуры,
которые находятся непосредственно в БД. Одна из особенностей этого процес-
са в том, что CallableStatement способен обрабатывать не только входные (IN)
параметры, но и выходящие (OUT) и смешанные (INOUT) параметры. Тип
выходного параметра должен быть зарегистрирован с помощью метода
registerOutParameter(). После установки входных и выходных параметров
вызываются методы execute(), executeQuery() или executeUpdate().
Пусть в БД существует хранимая процедура getlastname, которая по уни-
кальному номеру телефона для каждой записи в таблице phonebook будет воз-
вращать соответствующее ему имя:
CREATE PROCEDURE getlastname (p_phone IN INT, p_lastname OUT VARCHAR) AS
BEGIN
SELECT lastname INTO p_lastname FROM phonebook WHERE phone = p_phone;
END
Тогда для получения имени через вызов данной процедуры необходимо ис-
полнить java-код вида:
final
String SQL = "{call getlastname (?, ?)}";
CallableStatement cs = cn.prepareCall(SQL);
JDBC
353
// передача значения входного параметра
cs.setInt(1, 1658468);
// регистрация возвращаемого параметра
cs.registerOutParameter(2, java.sql.Types. VARCHAR);
cs.execute();
String lastName = cs.getString(2);
В JDBC также существует механизм batch-команд, который позволяет запу-
скать на исполнение в БД массив запросов SQL вместе, как одну единицу.
// turn off autocommit
cn.setAutoCommit( false);
Statement st = con.createStatement();
st.addBatch("INSERT INTO phonebook VALUES (55, 5642032, 'Гончаров ')");
st.addBatch("INSERT INTO location VALUES (260, 'Minsk')");
st.addBatch("INSERT INTO student_department VALUES (1000, 260)");
// submit a batch of update commands for execution
int
[ ] updateCounts = stmt.executeBatch();
Если используется объект PreparedStatement, batch-команда состоит из па-
раметризованного SQL-запроса и ассоциируемого с ним множества параметров.
Метод executeBatch() интерфейса PreparedStatement возвращает массив
чисел, причем каждое характеризует число строк, которые были изменены кон-
кретным запросом из batch-команды.
Пусть существует список объектов типа Abonent со стандартным набором
методов getТип()/setТип() для каждого из его полей, и необходимо внести их зна-
чения в БД. Многократное выполнение методов execute() или executeUpdate()
становится неэффективным, и в данном случае лучше использовать схему
batch-команд:
try
{
ArrayList abonents = new ArrayList<>(); // заполнение списка
PreparedStatement statement = con.prepareStatement("INSERT INTO phonebook VALUES(?,?,?)");
for
(Abonent abonent : abonents) {
statement.setInt(0, abonent.getId());
statement.setInt(1, abonent.getPhone());
statement.setString(2, abonent.getLastname());
statement.addBatch();
}
updateCounts = statement.executeBatch();
} catch (BatchUpdateException e) {
e.printStackTrace();
}
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
354
Транзакции
При проектировании распределенных систем часто возникают ситуации,
когда сбой в системе или какой-либо ее периферийной части может привести
к потере информации или к финансовым потерям. Простейшим примером мо-
жет служить пример с перечислением денег с одного счета на другой. Если
сбой произошел в тот момент, когда операция снятия денег с одного счета
уже произведена, а операция зачисления на другой счет еще не произведена,
то система, допускающая такие ситуации, должна быть признана не отвечаю-
щей требо ваниям заказчика. Или должны выполняться обе операции, или
не выполняться вовсе. Такие две операции трактуют как одну и называют
транзакцией.
Транзакция, или деловая операция, определяется как единица работы, обла-
дающая свойствами ACID:
• Атомарность — две или более операций выполняются все или не выполня-
ется ни одна. Успешно завершенные транзакции фиксируются, в случае не-
удачного завершения происходит откат всей транзакции.
• Согласованность — при возникновении сбоя система возвращается в состо-
яние до начала неудавшейся транзакции. Если транзакция завершается
успешно, то проверка согласованности удостоверяется в успешном завер-
шении всех операций транзакции.
• Изолированность — во время выполнения транзакции все объекты-сущно-
сти, участвующие в ней, должны быть синхронизированы.
• Долговечность — все изменения, произведенные с данными во время тран-
закции, сохраняются, например, в базе данных. Это позволяет восстанавли-
вать систему.
Для фиксации результатов работы SQL-операторов, логически выполняе-
мых в рамках некоторой транзакции, используется SQL-оператор COMMIT.
В API JDBC эта операция выполняется по умолчанию после каждого вызова
методов executeQuery() и executeUpdate(). Если же необходимо сгруппиро-
вать запросы и только после этого выполнить операцию COMMIT, сначала вы-
зывается метод setAutoCommit(boolean param) интерфейса Connection с па-
раметром false, в результате выполнения которого текущее соединение с БД
переходит в режим неавтоматического подтверждения операций. После этого
выполнение любого запроса на изменение информации в таблицах базы дан-
ных не приведет к необратимым последствиям, пока операция COMMIT не бу-
дет выполнена непосредственно. Подтверждает выполнение SQL-запросов
метод commit() интер фейса Connection, в результате действия которого все
изменения таблицы производятся как одно логическое действие. Если же тран-
закция не выполнена, то методом rollback() отменяются действия всех запро-
сов SQL, начиная от последнего вызова commit(). В следующем примере ин-
формация добавляется в таблицу в режиме действия транзакции, подтвердить
JDBC
355
или отменить действия которой можно, снимая или добавляя комментарий
в строках вызова методов commit() и rollback().
/* # 6 # транзакция по переводу денег со счета на счет # SingletonEngine.java */
package
com.epam.logic;
import
java.sql.Connection;
import
java.sql.DriverManager;
import
java.sql.ResultSet;
import
java.sql.SQLException;
import
java.sql.Statement;
public
class SingletonEngine {
private
Connection connectionTo;
private
Connection connectionFrom;
private
static SingletonEngine instance = null;
public
synchronized static SingletonEngine getInstance() {
if
(instance == null) {
instance = new SingletonEngine();
instance.getConnectionTo();
instance.getConnectionFrom();
}
return
instance;
}
private
Connection getConnectionFrom() {
try
{
Class.forName("com.mysql.jdbc.Driver");
connectionFrom
=
DriverManager.getConnection(
"jdbc:mysql://localhost:3306/testFrom", "root", "pass");
connectionFrom.setAutoCommit(false);
}
catch
(SQLException e) {
System.err.println("SQLException: " + e.getMessage()
+ "SQLState: " + e.getSQLState());
}
catch
(ClassNotFoundException ex) {
System.out.println("Driver not found");
}
return
connectionFrom;
}
private
Connection getConnectionTo() {
final
String connectToAdress = "jdbc:mysql://10.162.4.151:3306/testTo";
try
{
Class.forName("com.mysql.jdbc.Driver");
connectionTo = DriverManager.getConnection( connectToAdress, "root", "pass");
connectionTo.setAutoCommit(false);
}
catch
(SQLException e) {
System.err.println("SQLException: " + e.getMessage()
+ "SQLState: " + e.getSQLState());
}
catch
(ClassNotFoundException e) {
System.err.println("Driver not found");
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
356
}
return connectionTo;
}
public
void transfer(String summa) throws SQLException {
Statement stFrom = null;
Statement stTo = null;
try {
int sum = Integer. parseInt(summa);
if (sum <= 0) {
throw new NumberFormatException("less or equals zero");
}
stFrom
=
connectionFrom.createStatement();
stTo
=
connectionTo.createStatement();
// транзакция из 4-х запросов
ResultSet rsFrom =
stFrom.executeQuery("SELECT balance from table_from");
ResultSet
rsTo
=
stTo.executeQuery("SELECT balance from table_to");
int accountFrom = 0;
while (rsFrom.next()) {
accountFrom
=
rsFrom.getInt(1);
}
int resultFrom= 0;
if (accountFrom >= sum) {
resultFrom = accountFrom - sum;
}
else
{
throw new SQLException("Invalid balance");
}
int accountTo = 0;
while (rsTo.next()) {
accountTo
=
rsTo.getInt(1);
}
int resultTo = accountTo + sum;
stFrom.executeUpdate(
"UPDATE table_from SET balance=" + resultFrom);
stTo.executeUpdate("UPDATE table_to SET balance=" + resultTo);
// завершение транзакции
connectionFrom.commit();
connectionTo.commit();
System. out.println("remaining on :" + resultFrom + " rub");
}
catch
(SQLException e) {
System. err.println("SQLState: " + e.getSQLState()
+ "Error Message: " + e.getMessage());
// откат транзакции при ошибке
connectionFrom.rollback();
connectionTo.rollback();
}
catch
(NumberFormatException e) {
System. err.println("Invalid summa: " + summa);
}
finally
{
if (stFrom != null) {
JDBC
357
try {
stFrom.close();
}
catch
(SQLException ex) {
ex.printStackTrace();
}
}
if (stTo != null) {
try {
stTo.close();
}
catch
(SQLException ex) {
ex.printStackTrace();
}
}
}
}
}
Для транзакций существует несколько типов чтения:
• грязное чтение (dirty reads) происходит, когда транзакциям разрешено ви-
деть несохраненные изменения данных. Иными словами, изменения, сде-
ланные в одной транзакции, видны вне ее до того, как она была сохранена.
Если изменения не будут сохранены, то, вероятно, другие транзакции вы-
полняли работу на основе некорректных данных;
• неповторяющееся чтение (nonrepeatable reads) происходит, когда транзак-
ция А читает строку, транзакция Б изменяет эту строку, транзакция А чита-
ет ту же строку и получает обновленные данные;
• фантомное чтение (phantom reads) происходит, когда транзакция А считывает
все строки, удовлетворяющие WHERE-условию, транзакция Б вставляет но-
вую или удаляет одну из строк, которая удовлетворяет этому условию, тран-
закция А еще раз считывает все строки, удовлетворяющие WHERE-условию,
уже вместе с новой строкой или недосчитавшись старой.
JDBC удовлетворяет уровням изоляции транзакций, определенным в стан-
дарте SQL:2003.
Уровни изоляции транзакций определены в виде констант интерфейса
Connection (по возрастанию уровня ограничения):
• TRANSACTION_NONE — информирует о том, что драйвер не поддержи-
вает транзакции;
• TRANSACTION_READ_UNCOMMITTED — позволяет транзакциям ви-
деть несохраненные изменения данных, что разрешает грязное, неповторя-
ющееся и фантомное чтения;
• TRANSACTION_READ_COMMITTED — означает, что любое изменение,
сделанное в транзакции, не видно вне ее, пока она не сохранена. Это предот-
вращает грязное чтение, но разрешает неповторяющееся и фантомное;
• TRANSACTION_REPEATABLE_READ — запрещает грязное и неповто-
ряющееся чтение, но фантомное разрешено;
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
358
• TRANSACTION_SERIALIZABLE — определяет, что грязное, неповторя-
ющееся и фантомное чтения запрещены.
Метод boolean supportsTransactionIsolationLevel(int level) интерфейса
DatabaseMetaData определяет, поддерживается ли заданный уровень изоля-
ции транзакций.
В свою очередь, методы интерфейса Connection определяют доступ к уров-
ню изоляции:
int getTransactionIsolation() — возвращает текущий уровень изоляции;
void setTransactionIsolation(int level) — устанавливает необходимый уро-
вень.
Точки сохранения
Точки сохранения, представляемые классом java.sql.Savepoint, дают до-
полнительный контроль над транзакциями, привязывая изменения СУБД
к конкретной точке в области транзакции. Установкой точки сохранения обо-
значается логическая точка внутри транзакции, которая может быть использо-
вана для отката данных. Таким образом, если произойдет ошибка, можно выз-
вать метод rollback(Savepoint point) для отмены всех изменений, которые
были сделаны после точки сохранения. Метод boolean supportsSavepoints()
интерфейса DatabaseMetaData используется для того, чтобы определить,
поддерживает ли точки сохранения драйвер JDBC и сама СУБД. Методы
setSavepoint(String name) и setSavepoint() возвращают объект Savepoint ин-
терфейса Connection и используются для установки именованой или не-
именованой точки сохранения во время текущей транзакции. При этом новая
транзакция будет начата, если в момент вызова setSavepoint() не будет актив-
ной транзакции.
Data Access Object
При создании информационной системы выявляются некоторые слои, кото-
рые отвечают за взаимодействие различных частей приложения. Связь с базой
данных является важной частью любой системы, поэтому всегда выделяется
Достарыңызбен бөлісу: |