Java se сервлеты и jsp паттерны Gof junit Log4j ant



Pdf көрінісі
бет21/22
Дата11.12.2019
өлшемі8,99 Mb.
#53432
1   ...   14   15   16   17   18   19   20   21   22
Байланысты:
JAVA Methods Programming v2.march2015 (2)

Глава 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
При создании информационной системы выявляются некоторые слои, кото-
рые отвечают за взаимодействие различных частей приложения. Связь с базой 
данных является важной частью любой системы, поэтому всегда выделяется 


Достарыңызбен бөлісу:
1   ...   14   15   16   17   18   19   20   21   22




©engime.org 2024
әкімшілігінің қараңыз

    Басты бет