Последовательность действий

1. Загрузка класса драйвера базы данных при отсутствии экземпляра этого класса.

Например:

String driverName = "org.gjt.mm.mysql.Driver";

для СУБД MySQL,

String driverName = "sun.jdbc.odbc.JdbcOdbcDriver";

для СУБД MSAccess или

String driverName = "org.postgreesql.Driver";

для СУБД PostgreeSQL.

После этого выполняется собственно загрузка драйвера в память:

Class.forName(driverName);

и становится возможным соединение с СУБД.

Эти же действия можно выполнить, импортируя библиотеку и создавая объект явно. Например, для СУБД DB2 от IBM объект-драйвер можно создать следующим образом:

new com.ibm.db2.jdbc.net.DB2Driver();

2. Установка соединения с БД.

Для установки соединения с БД вызывается статический метод
getConnection()
классаDriverManager. В качестве параметров методу передаются URL базы данных, логин пользователя БД и пароль доступа. Метод возвращает объект Connection. URL базы данных, состоящий из типа и адреса физического расположения БД, может создаваться в виде отдельной строки или извлекаться из файла ресурсов. Например:

Connection cn = DriverManager.getConnection( "jdbc:mysql://localhost/my_db", "root", "pass");

В результате будет возвращен объект Connection и будет одно установленное соединение с БД my_db. Класс DriverManager предоставляет средства для управления набором драйверов баз данных. С помощью метода
registerDriver() драйверы регистрируются, а методом getDrivers() можно получить список всех драйверов.

3. Создание объекта для передачи запросов.

После создания объектаConnection и установки соединения можно начинать работу с БД с помощью операторов SQL. Для выполнения запросов применяется объект Statement, создаваемый вызовом метода createStatement()классаConnection.

Statement st = cn.createStatement();

Объект класса Statement используется для выполнения SQL-запроса без его предварительной подготовки. Могут применяться также объекты классов PreparedStatement и CallableStatement для выполнения подготовленных запросов и хранимых процедур. Созданные объекты можно использовать для выполнения запроса SQL, передавая его в один из методов
executeQuery(String sql)
или executeUpdate(String sql).

4. Выполнение запроса.

Результаты выполнения запроса помещаются в объект ResultSet:

ResultSet rs = st.executeQuery(

"SELECT * FROM my_table");//выборка всех данных таблицы my_table

Для добавления, удаления или изменения информации в таблице вместо метода executeQuery() запрос помещается в метод executeUpdate().

5. Обработка результатов выполнения запроса производится методами интерфейса ResultSet, где самыми распространенными являются next() и
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), которые могут быть, например, графическими или архивными файлами. Эффективным способом извлечения значения поля из таблицы ответа является обращение к этому полю по его позиции в строке.

При первом вызове методаnext() указатель перемещается на таблицу результатов выборки в позицию первой строки таблицы ответа. Когда строки закончатся, метод возвратит значение false.

6. Закрытие соединения

Cn.close();

После того как база больше не нужна, соединение закрывается.

Для того чтобы правильно пользоваться приведенными методами, программисту требуется знать типы полей БД. В распределенных системах это знание предполагается изначально.

СУБД MySQL

СУБД MySQL совместима c JDBC и будет применяться для создания экспериментальных БД. Последняя версия CУБД может быть загружена с сайта www.mysql.com. Для корректной установки необходимо следовать инструкциям мастера установки. Каталог лучше выбирать по умолчанию. В процессе установки следует создать администратора СУБД с именем root и паролем pass. Если планируется разворачивать реально работающее приложе­ние, необходимо исключить тривиальных пользователей сервера БД (иначе злоумышленники
могут получить полный доступ к БД). Для запуска следует использовать команду из папки /mysql/bin:

Mysqld-nt -standalone

Если не появится сообщение об ошибке, то СУБД MySQL запущена. Для создания БД и таблиц используются команды языка SQL.

Дополнительно требуется подключить библиотеку, содержащую драйвер MySQL

mysql-connector-java-3.1.12.jar,

и разместить ее в каталоге /WEB-INF/lib проекта.

Простое соединение и простой запрос

Теперь следует воспользоваться всеми предыдущими инструкциями и создать пользовательскую БД с именем db2 и одной таблицей users. Таблица должна содержать два поля: символьное – name и числовое – phone и несколько занесенных записей. Сервлет, осуществляющий простейший запрос на выбор всей информации из таблицы, выглядит следующим образом.

/* пример # 1 : соединение с базой : ServletToBase.java */

package chapt20;

import java.io.*;

import java.sql.*;

import javax.servlet.*;

import javax.servlet.http.*;

 

public class ServletToBase extends HttpServlet {

public void doGet(HttpServletRequest req,

HttpServletResponse resp)

throws ServletException {

performTask(req, resp);

}

public void doPost(HttpServletRequest req,

HttpServletResponse resp)

throws ServletException {

performTask(req, resp);

}

 

public void showInfo(PrintWriter out, ResultSet rs)

throws SQLException {

out.print("From DataBase:");

while (rs.next()) {

out.print("<br>Name:-> " + rs.getString(1)

+ " Phone:-> " + rs.getInt(2));

}

}

public void performTask(HttpServletRequest req,

HttpServletResponse resp) {

resp.setContentType("text/html; charset=Cp1251");

PrintWriter out = null;

try {//1

out = resp.getWriter();

try {//2

 

Class.forName("org.gjt.mm.mysql.Driver");

// для MSAccess

/* return "sun.jdbc.odbc.JdbcOdbcDriver" */

// для PostgreeSQL

/* return " org.postgreesql.Driver " */

Connection cn = null;

try {//3

cn =

DriverManager.getConnection("jdbc:mysql://localhost/db2",

"root", "pass");

// для MSAccess

/* return "jdbc:odbc:db2"; */

// для PostgreeSQL

/* return "jdbc:postgreesql://localhost/db2"; */

Statement st = null;

try {//4

st = cn.createStatement();

ResultSet rs = null;

try {//5

rs = st.executeQuery("SELECT * FROM users");

out.print("From DataBase:");

while (rs.next()) {

out.print("<br>Name:-> " + rs.getString(1)

+ " Phone:-> " + rs.getInt(2));

}

} finally {// для 5-го блока try

/* закрыть ResultSet, если он был открыт и ошибка

произошла во время чтения из него данных */

// проверка успел ли создаться ResultSet

if (rs != null) rs.close();

Else

out.print("ошибка во время чтения данных из БД");

}

} finally {// для 4-го блока try

/* закрыть Statement, если он был открыт и ошибка

произошла во время создания ResultSet */

// проверка успел ли создаться Statement

if (st != null) st.close();

else out.print("Statement не создан");

}

} finally {// для 3-го блока try

/* закрыть Connection, если он был открыт и ошибка произошла

во время создания ResultSet или создания и использования

Statement */

// проверка - успел ли создаться Connection

if (cn != null) cn.close();

else out.print("Connection не создан");

}

} catch (ClassNotFoundException e) {// для 2-го блока try

out.print("ошибка во время загрузки драйвера БД");

}

}

/* вывод сообщения о всех SQLException и IOException в блоках finally, */

/* поэтому следующие блоки catch оставлены пустыми */

catch (SQLException e) {

}// для 1-го блока try

catch (IOException e) {

}// для 1-го блока try

finally {// для 1-го блока try

/* закрыть PrintWriter, если он был инициализирован и ошибка

произошла во время работы с БД */

// проверка, успел ли инициализироваться PrintWriter

if (out != null) out.close();

Else

out.print("PrintWriter не проинициализирован");

}

}

}

В несложном приложении достаточно контролировать закрытие соединения, так как незакрытое (“провисшее”) соединение снижает быстродействие системы.

Еще один способ соединения с базой данных возможен с использованием файла ресурсов database.properties, в котором хранятся, как правило, путь к БД, логин и пароль доступа. Например:

url=jdbc:mysql://localhost/my_db?useUnicode=true&

characterEncoding=Cp1251

driver=org.gjt.mm.mysql.Driver

user=root

password=pass

В этом случае соединение создается в классе бизнес-логики, отвечающем
за взаимодействие с базой данных, с помощью следующего кода:

public Connection getConnection()

throws SQLException {

 

ResourceBundle resource =

ResourceBundle.getBundle("database");

String url = resource.getString("url");

String driver = resource.getString("driver");

String user = resource.getString("user");

String pass = resource.getString("password");

try {

Class.forName(driver).newInstance();

} catch (ClassNotFoundException e) {

throw new SQLException("Драйвер не загружен!");

} catch (InstantiationException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

}

return DriverManager.getConnection(url, user, pass);

}

Объект класса ResourceBundle, содержащий ссылки на все внешниересурсы проекта, создается с помощью вызова статического метода
getBundle(String filename), с параметром в виде имени необходимого файла ресурсов. Если требуемый файл отсутствует, то генерируется исключи­тельная ситуация MissingResourceException. Для чтения из объекта ресурсов используется метод getString(String name), извлекающий информацию по указанному в параметре ключу. В классе ResourceBundle определен ряд полезных методов, в том числе метод getKeys(), возвращающий объект Enumeration, который применяется для последовательного обращения к элементам. Методы getObject(String key) и getStringArray(String key) извлекают соответственно объект и массив строк по передаваемому ключу.

Метаданные

Существует целый ряд методов интерфейсов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() – возвращает местонахождение источника данных;

ResultSet getTables() – возвращает набор типов таблиц, доступных для данной БД, и т.д.

Подготовленные запросы и хранимые процедуры

Для представления запросов существуют еще два типа объектов PreparedStatement и CallableStatement. Объекты первого типа используются при выполнении часто повторяющихся запросов SQL. Такой оператор предварительно готовится и хранится в объекте, что ускоряет обмен информацией с базой данных. Второй интерфейс используется для выполнения хранимых процедур, созданных средствами самой СУБД.

Для подготовки SQL-запроса, в котором отсутствуют конкретные параметры, используется метод prepareStatement(String sql)интерфейса Connection, возвращающий объект PreparedStatement. Установка входных значений конкретных параметров этого объекта производится с помощью методов
setString(), setInt() и подобных им, после чего и осуществляется
непосредственное выполнение запроса методами executeUpdate(),
executeQuery().Так как данный оператор предварительно подготовлен, то он выполняется быстрее обычных операторов, ему соответствующих. Оценить пре-
имущества во времени можно, выполнив большое число повторяемых запросов
с предварительной подготовкой запроса и без нее.

/* пример # 2 : создание и выполнение подготовленного запроса :

PreparedStatementServlet.java */

package chapt20;

import java.io.*;

import java.sql.*;

import javax.servlet.*;

import javax.servlet.http.*;

public class PreparedStatementServlet extends HttpServlet {

protected void doGet(HttpServletRequest req,

HttpServletResponse resp)

throws ServletException, IOException {

performTask(req, resp);

}

protected void doPost(HttpServletRequest req,

HttpServletResponse resp)

throws ServletException, IOException {

performTask(req, resp);

}

protected void performTask(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException {

resp.setContentType("text/html");

PrintWriter out = resp.getWriter();

try {

Class.forName("org.gjt.mm.mysql.Driver");

Connection cn = null;

try {

cn = DriverManager.getConnection( "jdbc:mysql://localhost/db3","root","");

PreparedStatement ps = null;

String sql =

"INSERTINTO emp(id,name,surname,salary) VALUES(?,?,?,?)";

//компиляция (подготовка) запроса

ps = cn.prepareStatement(sql);

Rec.insert(ps, 2203, "Иван", "Петров", 230);

Rec.insert(ps, 2308, "John", "Black", 450);

Rec.insert(ps, 2505, "Mike", "Call", 620);

out.println("COMPLETE");

} finally {

if (cn != null) cn.close();

}

} catch (Exception e) {

e.printStackTrace();

}

out.close();

}

}

class Rec {

static void insert(PreparedStatement ps, int id, String name, String surname, int salary)

throws SQLException {

//установка входных параметров

ps.setInt(1, id);

ps.setString(2, name);

ps.setString(3, surname);

ps.setInt(4, salary);

//выполнение подготовленного запроса

Ps.executeUpdate();

}

}

Результатом выполнения данной программы будет добавление в базу данных db3 трех записей и вывод в окно браузера слова COMPLETE.

Интерфейс CallableStatementрасширяет возможности интерфейса PreparedStatement и обеспечивает выполнение хранимых процедур.

Хранимая процедура – это в общем случае именованная последовательность команд SQL, рассматриваемых как единое целое, и выполняющаяся в адресном пространстве процессов СУБД, который можно вызвать извне (в зависимости от политики доступа используемой СУБД). В данном случае хранимая процедура будет рассматриваться в более узком смысле как последовательность команд SQL, хранимых в БД и доступных любому пользователю этой СУБД. Механизм создания и настройки хранимых процедур зависит от конкретной базы данных. Для создания объекта CallableStatement вызывается метод
prepareCall() объекта Connection.

Интерфейс CallableStatement позволяет исполнять хранимые проце­дуры, которые находятся непосредственно в БД. Одна из особенностей этого процесса в том, что CallableStatement способен обрабатывать не только входные (IN) параметры, но и выходящие (OUT) и смешанные (INOUT) параметры. Тип выходного параметра должен быть зарегистрирован методом registerOutParameter(). После установки входных и выходных параметров вызываются методы execute(), executeQuery() или executeUpdate().

Пусть в БД существует хранимая процедура getempname, которая по уникальному для каждой записи в таблице employee числу SSN будет возвращать соответствующее ему имя:

CREATE PROCEDURE getempname

(emp_ssn IN INT, emp_name OUT VARCHAR) AS

BEGIN

SELECT name

INTO emp_name

FROM employee

WHERE SSN = EMP_SSN;

END

Тогда для получения имени служащего employee через вызов данной процедуры необходимо исполнить java-код вида:

String SQL = "{call getempname (?,?)}";

CallableStatement cs = conn.prepareCall(SQL);

cs.setInt(1,822301);

//регистрация выходящего параметра

cs.registerOutParameter(2,java.sql.Types.VARCHAR);

cs.execute();

String empName = cs.getString(2);

System.out.println("Employee with SSN:" + ssn

+ " is " + empName);

В результате будет выведено:

Employee with SSN:822301 is Spiridonov

В JDBC также существует механизм batch-команд, который позволяет запускать на исполнение в БД массив запросов SQL вместе, как одну единицу.

// turn off autocommit

con.setAutoCommit(false);

Statement stmt = con.createStatement();

stmt.addBatch("INSERT INTO employee VALUES

(10, 'Joe ')");

stmt.addBatch("INSERT INTO location VALUES

(260, 'Minsk')");

stmt.addBatch("INSERT INTO emp_dept VALUES

(1000, 260)");

// submit a batch of update commands for execution

int[] updateCounts = stmt.executeBatch();

Если используется объект PreparedStatement, batch-команда состоит из параметризованного SQL-запроса и ассоциируемого с ним множества параметров.

Метод PreparedStatement.executeBatch() возвращает массив чисел, причем каждое характеризует число строк, которые были изменены конкретным запросом из batch-команды.

Пусть существует массив объектов типа Employee со стандартным набором методов getТип()/setТип() для каждого из его полей, и необходимо внести их значения в БД. Многократное выполнение методов execute() или executeUpdate() становится неэффективным, и в данном случае лучше использовать схему batch-команд:

try {

Employee[] employees = new Employee[10];

PreparedStatement statement =

con.prepareStatement("INSERT INTO employee VALUES

(?,?,?,?,?)");

for (int i = 0; i < employees.length; i++) {

Employee currEmployee = employees[i];

statement.setInt(1, currEmployee.getSSN());

statement.setString(2, currEmployee.getName());

statement.setDouble(3, currEmployee.getSalary());

statement.setString(4,currEmployee.getHireDate());

statement.setInt(5, currEmployee.getLoc_Id());

statement.addBatch();

}

updateCounts = statement.executeBatch();

} catch (BatchUpdateException e) {

e.printStackTrace();

}

Транзакции

При проектировании распределенных систем часто возникают ситуации,
когда сбой в системе или какой-либо ее периферийной части может привести
к потере информации или к финансовым потерям. Простейшим примером может служить пример с перечислением денег с одного счета на другой. Если сбой произошел в тот момент, когда операция снятия денег с одного счета уже произведена, а операция зачисления на другой счет еще не произведена, то система, позволяющая такие ситуации, должна быть признана не отвечающей требо­ваниям заказчика. Или должны выполняться обе операции, или не выполняться вовсе.
Такие две операции трактуют как одну и называют транзакцией.

Транзакцию (деловую операцию) определяют как единицу работы, обладающую свойствами ACID:

· Атомарность – две или более операций выполняются все или не выполняется ни одна. Успешно завершенные транзакции фиксируются, в случае неудачного завершения происходит откат всей транзакции.

· Согласованность – при возникновении сбоя система возвращается в состояние до начала неудавшейся транзакции. Если транзакция завершается успешно, то проверка согласованности удостоверяется в успешном завершении всех операций транзакции.

· Изолированность – во время выполнения транзакции все объекты-сущности, участвующие в ней, должны быть синхронизированы.

· Долговечность – все изменения, произведенные с данными во время транзакции, сохраняются, например, в базе данных. Это позволяет восстанавливать систему.

Для фиксации результатов работы SQL-операторов, логически выполняемых в рамках некоторой транзакции, используется SQL-оператор COMMIT. В API JDBC эта операция выполняется по умолчанию после каждого вызова методов
executeQuery()и executeUpdate(). Если же необходимо сгруппировать запросы и только после этого выполнить операцию COMMIT, сначала вызывается метод setAutoCommit(boolean param)интерфейса Connection с параметром false, в результате выполнения которого текущее соединение с БД переходит в режим неавтоматического подтверждения операций. После этого выполнение любого запроса на изменение информации в таблицах базы данных не приведет к необратимым последствиям, пока операция COMMIT не будет выполнена непосредственно. Подтверждает выполнение SQL-запросов метод commit() интер­фейса Connection, в результате действия которого все изменения таблицы производятся как одно логическое действие. Если же транзакция не выполнена, то методом rollback() отменяются действия всех запросов SQL, начиная от последнего вызова commit(). В следующем примере информация добавляется в таблицу
в режиме действия транзакции, подтвердить или отменить действия которой можно, снимая или добавляя комментарий в строках вызова методов commit()
иrollback().

<!-- пример # 3 : вызов сервлета : index.jsp -->

<%@ page language="java" contentType="text/html; charset=windows-1251" pageEncoding="windows-1251"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional //EN">

<html><head>

<meta http-equiv="Content-Type"

content="text/html; charset=windows-1251">

<title>Simple Transaction Demo</title>

</head>

<body>

<form name="students" method="POST"

action="SQLTransactionServlet">

 

id:<br/>

<input type="text" name="id" value=""><br/>

Name:<br/>

<input type="text" name="name" value=""><br/>

 

Course:<br/>

<select name="course">

<option>Java SE 6

<option>XML

<option>Struts

</select><br/>

 

<input type="submit" value="Submit">

</form>

</body></html>

/* пример # 4 : выполнение транзакции : метод perform() сервлета

SQLTransactionServlet.java */

public void taskPerform(HttpServletRequest request,

HttpServletResponse response)

throws ServletException, IOException {

response.setContentType("text/html; charset=Cp1251");

PrintWriter out = null;

Connection cn = null;

try {

out = response.getWriter();

String id = request.getParameter("id");

String name = request.getParameter("name");

String course = request.getParameter("course");

out.print("ID студента: " + id + ", " + name +<br>");

cn = getConnection();

cn.setAutoCommit(false);

Statement st = cn.createStatement();

try {

String upd;

upd =

"INSERT INTO student (id, name) VALUES ('"

+ id + "', '" + name + "')";

st.executeUpdate(upd);

out.print("Внесены данные в students: "

+ id + ", " + name + "<br>");

 

upd =

"INSERT INTO course(id_student, name_course) VALUES('"

+ id + "','" + course + "')";

st.executeUpdate(upd);

out.print("Внесены данные в course: " + id

+ ", " + course + "<br>");

 

cn.commit();// подтверждение

out.print("<b>Данные внесены - транзакция завершена"

+ "</b><br>");

} catch (SQLException e) {

cn.rollback();// откат

out.println("<b>Произведен откат транзакции:"

+ e.getMessage() + "</b>");

} finally {

if (cn != null)

cn.close();

}

} catch (SQLException e) {

out.println("<b>ошибка при закрытии соединения:"

+ e.getMessage());

}

}

Если таблицы student и course базы данных db1до изменения выглядели, например, следующим образом,

id namе   id_student name_course
Goncharenko   Java SE 6

Рис. 20.2.Таблицы до выполнения запроса

то после внесения изменений и их подтверждения они примут вид:

id namе   id_student name_course
Goncharenko   Java SE 6
Petrov   XML

Рис. 20.3. Таблицы после подтверждения выполнения запросов

ID студента: 83, Petrov
Внесены данные в students: 83, Petrov
Внесены данные в course: 83, XML
Данные внесены - транзакция завершена

Приведенный пример в полной мере не отражает принципы транзакции, но демонстрирует способы ее поддержки методами языка Java.

Для транзакций существует несколько типов чтения:

· Грязное чтение (dirty reads) происходит, когда транзакциям разрешено видеть несохраненные изменения данных. Иными словами, изменения, сделанные в одной транзакции, видны вне ее до того, как она была сохранена. Если изменения не будут сохранены, то, вероятно, другие транзакции выполняли работу на основе некорректных данных;

· Непроверяющееся чтение (nonrepeatable reads) происходит, когда транзакция А читает строку, транзакция Б изменяет эту строку, транзакция А читает ту же строку и получает обновленные данные;

· Фантомное чтение (phantom reads) происходит, когда транзакция А считывает все строки, удовлетворяющие WHERE-условию, транзакция Б вставляет новую или удаляет одну из строк, которая удовлетворяет этому условию, транзакция А еще раз считывает все строки, удовлетворяющие WHERE-условию, уже вместе с новой строкой или недосчитавшись старой.

JDBC удовлетворяет четырем уровням изоляции транзакций, определенным
в стандарте SQL:2003.

Уровни изоляции транзакций определены в виде констант интерфейса Connection (по возрастанию уровня ограничения):

· TRANSACTION_NONE – информирует о том, что драйвер не поддерживает транзакции;

· TRANSACTION_READ_UNCOMMITTED – позволяет транзакциям видеть несохраненные изменения данных, что разрешает грязное, непроверяющееся и фантомное чтения;

· TRANSACTION_READ_COMMITTED – означает, что любое изменение, сделанное в транзакции, не видно вне неё, пока она не сохранена. Это предотвращает грязное чтение, но разрешает непроверяющееся и фантомное;

· TRANSACTION_REPEATABLE_READ – запрещает грязное и непроверяющееся, но фантомное чтение разрешено;

· TRANSACTION_SERIALIZABLE – определяет, что грязное, непроверяющееся и фантомное чтения запрещены.

Метод boolean supportsTransactionIsolationLevel(int level)интерфейса DatabaseMetaData определяет, поддерживается ли заданный уровень изоляции транзакций.

В свою очередь, методы интерфейса Connection определяют доступ к уровню изоляции:

int getTransactionIsolation() – возвращает текущий уровень изоляции;

void setTransactionIsolation(int level) – устанавливает нужный уровень.

Точки сохранения

Точки сохранения дают дополнительный контроль над транзакциями. Установкой точки сохранения обозначается логическая точка внутри транзакции, которая может быть использована для отката данных. Таким образом, если произойдет ошибка, можно вызвать метод rollback()для отмены всех изменений, которые были сделаны после точки сохранения.

Метод boolean supportsSavepoints()интерфейса DatabaseMetaData используется для того, чтобы определить, поддерживает ли точки сохранения драйвер JDBC и сама СУБД.

Методы setSavepoint(String name) и setSavepoint() (оба возвращают объект Savepoint) интерфейса Connection используются для установки именованной или неименованной точки сохранения во время текущей транзакции. При этом новая транзакция будет начата, если в момент вызова setSavepoint() не будет активной транзакции.

/* пример # 5 : применение точек сохранения : SavepointServlet.java */

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

import java.io.PrintWriter;

import java.sql.*;

 

public class SavepointServlet extends HttpServlet {

protected void doGet(HttpServletRequest req,

HttpServletResponse resp)

throws ServletException, IOException {

performTask(req, resp);

}

protected void doPost(HttpServletRequest req,

HttpServletResponse resp)

throws ServletException, IOException {

performTask(req, resp);

}

protected void performTask(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException {

resp.setContentType("text/html; charset=Cp1251");

PrintWriter out = resp.getWriter();

try {

Class.forName("org.gjt.mm.mysql.Driver");

Connection cn = null;

Savepoint savepoint = null;

try {

cn = DriverManager.getConnection( "jdbc:mysql://localhost/db3","root","pass");

cn.setAutoCommit(false);

out.print("<b>Соединение с БД...</b>");

out.print("<br>");

Statement stmt = cn.createStatement();

String trueSQL =

"INSERT INTO emp (id,name,surname,salary) "

+ "VALUES(2607,'Петя','Иванов',540)";

stmt.executeUpdate(trueSQL);

//установка точки сохранения

savepoint =

cn.setSavepoint("savepoint1");

//выполнение некорректного запроса

String wrongSQL =

"INSERT INTO (id,name,surname,salary) "

+ "VALUES(2607,'Петя','Иванов',540)";

stmt.executeUpdate(wrongSQL);

} catch (SQLException ex) {

out.print(ex + "<br>");

Cn.rollback(savepoint);

out.print("<b>Откат к точке сохранения: "

+ savepoint + "</b>");

} finally {

if (cn != null) cn.close();

}

} catch (Exception e) {

e.printStackTrace();

}

out.close();

}

}

В результате в браузер будет выведено:

Соединение с БД...

java.sql.SQLException: You have an error in your SQL syntax...

Откат к точке сохранения: savepoint1

При этом результаты выполнения запроса trueSQL будут сохранены перед попыткой повторения транзакции.

Пул соединений

При большом количестве клиентов, работающих с приложением, к его базе данных выполняется большое количество запросов. Установление соединения с БД является дорогостоящей (по требуемым ресурсам) операцией. Эффективным способом решения данной проблемы является организация пула (pool) используемых соединений, которые не закрываются физически, а хранятся в очереди и предоставляются повторно для других запросов.

Пул соединений – это одна из стратегий предоставления соединений приложению (не единственная, да и самих стратегий организации пула существует несколько).

Пул соединений можно организовать с помощью server.xml дескрипторного файла Аpache Tomcat в виде:

<Context docBase="FirstProject" path="/FirstProject"
reloadable="true" source="com.ibm.etools.webtools.server: FirstProject">

<!—создание пул соединений для СУБД MySQL -->

<Resource auth="Container" name="jdbc/db1"

type="javax.sql.DataSource"/>

<ResourceParams name="jdbc/db1">

<parameter>

<name>factory</name>

<value>org.apache.commons.dbcp.BasicDataSourceFactory</value>

</parameter>

<parameter>

<name>driverClassName</name>

<value>org.gjt.mm.mysql.Driver</value>

</parameter>

<!—url-адрес соединения JDBC для конкретной базы данных db1

Аргумент autoReconnect=true, заданный для url-адреса драйвера JDBC, должен автоматически разорвать соединение, если mysqld его закроет. По умолчанию mysqld закроет соединение через 8 часов.-->

<parameter>

<name>url</name>

<value>jdbc:mysql://localhost:3306/db1?autoReconnect=true

</value>

</parameter>

<parameter>

<name>username</name>

<value>root</value>

</parameter>

<parameter>

<name>password</name>

<value>pass</value>

</parameter>

<parameter>

<name>maxActive</name>

<value>500</value>

</parameter>

<parameter>

<name>maxIdle</name>

<value>10</value>

</parameter>

<parameter>

<name>maxWait</name>

<value>10000</value>

</parameter>

<parameter>

<name>removeAbandoned</name>

<value>true</value>

</parameter>

<parameter>

<name>removeAbandonedTimeout</name>

<value>60</value>

</parameter>

<parameter>

<name>logAbandoned</name>

<value>true</value>

</parameter>

</ResourceParams>

</Context>

Найти и запустить данный пул соединений можно с помощью JNDI.

Разделяемый доступ к источнику данных можно организовать, например, путем объявления статической переменной типа DataSource из пакета javax.sql, однако в J2EE принято использовать для этих целей каталог. Источ­ник данных типа DataSource – это компонент, предоставляющий соединение
с приложением СУБД.

Класс InitialContext, как часть JNDI API, обеспечивает работу с каталогом именованных объектов. В этом каталоге можно связать объект источ­ника данных DataSource с некоторым именем (не только с именем БД, но и вообще
с любым), предварительно создав объект DataSource.

Затем созданный объект можно получить с помощью метода lookup() по его имени. Методу lookup() передается имя, всегда начинающееся с имени корневого контекста.

javax.naming.Context ct =

newjavax.naming.InitialContext();

DataSource ds = (DataSource)ct.lookup("java:jdbc/db1");

Connection cn = ds.getConnection("root", "pass");

После выполнения запроса соединение завершается, и его объект возвращается обратно в пул вызовом:

cn.close();

Некоторые производители СУБД для облегчения создания пула соединений определяют собственный класс на основе интерфейса DataSource. В этом случае пул соединений может быть создан, например, следующим образом:

import COM.ibm.db2.jdbc.DB2DataSource;

...

DB2DataSource ds = new DB2DataSource();

ds.setServerName("//localhost:50000/db1");

Connection cn = ds.getConnection("db2inst1", "pass");

Драйвер определяется автоматически в объекте DB2DataSource.

Задания к главе 20

Вариант А

В каждом из заданий необходимо выполнить следующие действия:

· Организацию соединения с базой данных вынести в отдельный класс, метод которого возвращает соединение.

· Создать БД. Привести таблицы к одной из нормированных форм.

· Создать класс для выполнения запросов на извлечение информации из БД с использованием компилированных запросов.

· Создать класс на добавление информации.

· Создать HTML-документ с полями для формирования запроса.

· Результаты выполнения запроса передать клиенту в виде HTML-документа.

1. Файловая система. В БД хранится информация о дереве каталогов файловой системы – каталоги, подкаталоги, файлы.

Для каталогов необходимо хранить:

– родительский каталог;

– название.

Для файлов необходимо хранить:

– родительский каталог;

– название;

– место, занимаемое на диске.

· Определить полный путь заданного файла (каталога).

· Подсчитать количество файлов в заданном каталоге, включая вложенные файлы и каталоги.

· Подсчитать место, занимаемое на диске содержимым заданного каталога.

· Найти в базе файлы по заданной маске с выдачей полного пути.

· Переместить файлы и подкаталоги из одного каталога в другой.

· Удалить файлы и каталоги заданного каталога.

2. Видеотека. В БД хранится информация о домашней видеотеке – фильмы, актеры, режиссеры.

Для фильмов необходимо хранить:

– название;

– имена актеров;

– дату выхода;

– страну, в которой выпущен фильм.

Для актеров и режиссеров необходимо хранить:

– ФИО;

– дату рождения.

· Найти все фильмы, вышедшие на экран в текущем и прошлом году.

· Вывести информацию об актерах, снимавшихся в заданном фильме.

· Вывести информацию об актерах, снимавшихся как минимум в N фильмах.

· Вывести информацию об актерах, которые были режиссерами хотя бы одного из фильмов.

· Удалить все фильмы, дата выхода которых была более заданного числа лет назад.

3. Расписание занятий. В БД хранится информация о преподавателях
и проводимых ими занятиях.

Для предметов необходимо хранить:

– название;

– время проведения (день недели);

– аудитории, в которых проводятся занятия.

Для преподавателей необходимо хранить:

– ФИО;

– предметы, которые он ведет;

– количество пар в неделю по каждому предмету;

– количество студентов, занимающихся на каждой паре.

· Вывести информацию о преподавателях, работающих в заданный день недели в заданной аудитории.

· Вывести информацию о преподавателях, которые не ведут занятия
в заданный день недели.

· Вывести дни недели, в которых проводится заданное количество занятий.

· Вывести дни недели, в которых занято заданное количество аудиторий.

· Перенести первые занятия заданных дней недели на последнее место.

4. Письма. В БД хранится информация о письмах и отправляющих их людях.

Для людей необходимо хранить:

– ФИО;

– дату рождения.

Для писем необходимо хранить:

– отправителя;

– получателя;

– тему письма;

– текст письма;

– дату отправки.

· Найти пользователя, длина писем которого наименьшая.

· Вывести информацию о пользователях, а также количестве полученных и отправленных ими письмах.

· Вывести информацию о пользователях, которые получили хотя бы одно сообщение с заданной темой.

· Вывести информацию о пользователях, которые не получали сообщения с заданной темой.

· Направить письмо заданного человека с заданной темой всем адресатам.

5.Сувениры. В БД хранится информация о сувенирах и их производителях.

Для сувениров необходимо хранить:

– название;

– реквизиты производителя;

– дату выпуска;

– цену.

Для производителей необходимо хранить:

– название;

– страну.

· Вывести информацию о сувенирах заданного производителя.

· Вывести информацию о сувенирах, произведенных в заданной стране.

· Вывести информацию о производителях, чьи цены на сувениры меньше заданной.

· Вывести информацию о производителях заданного сувенира, произведенного в заданном году.

· Удалить заданного производителя и его сувениры.

6.Заказ. В БД хранится информация о заказах магазина и товарах в них.

Для заказа необходимо хранить:

– номер заказа;

– товары в заказе;

– дату поступления.

Для товаров в заказе необходимо хранить:

– товар;

– количество.

Для товара необходимо хранить:

– название;

– описание;

– цену.

· Вывести полную информацию о заданном заказе.

· Вывести номера заказов, сумма которых не превосходит заданную,
и количество различных товаров равно заданному.

· Вывести номера заказов, содержащих заданный товар.

· Вывести номера заказов, не содержащих заданный товар и поступивших в течение текущего дня.

· Сформировать новый заказ, состоящий из товаров, заказанных в текущий день.

· Удалить все заказы, в которых присутствует заданное количество заданного товара.

7.Продукция. В БД хранится информация о продукции компании.

Для продукции необходимо хранить:

– название;

– группу продукции (телефоны, телевизоры и др.);

– описание;

– дату выпуска;

– значения параметров.

Для групп продукции необходимо хранить:

– название;

– перечень групп параметров (размеры и др.).

Для групп параметров необходимо хранить:

– название;

– перечень параметров.

Для параметров необходимо хранить:

– название;

– единицу измерения.

· Вывести перечень параметров для заданной группы продукции.

· Вывести перечень продукции, не содержащий заданного параметра.

· Вывести информацию о продукции для заданной группы.

· Вывести информацию о продукции и всех ее параметрах со значениями.

· Удалить из базы продукцию, содержащую заданные параметры.

· Переместить группу параметров из одной группы товаров в другую.

8.Погода. В БД хранится информация о погоде в различных регионах.

Для погоды необходимо хранить:

– регион;

– дату;

– температуру;

– осадки.

Для регионов необходимо хранить:

– название;

– площадь;

– тип жителей.

Для типов жителей необходимо хранить:

– название;

– язык общения.

· Вывести сведения о погоде в заданном регионе.

· Вывести даты, когда в заданном регионе шел снег и температура была ниже заданной отрицательной.

· Вывести информацию о погоде за прошедшую неделю в регионах, жители которых общаются на заданном языке.

· Вывести среднюю температуру за прошедшую неделю в регионах
с площадью больше заданной.

9.Магазин часов. В БД хранится информация о часах, продающихся в магазина.

Для часов необходимо хранить:

– марку;

– тип (кварцевые, механические);

– цену;

– количество;

– реквизиты производителя.

Для производителей необходимо хранить:

– название;

– страну.

· Вывести марки заданного типа часов.

· Вывести информацию о механических часах, цена на которые не превышает заданную.

· Вывести марки часов, изготовленных в заданной стране.

· Вывести производителей, общая сумма часов которых в магазине
не превышает заданную.

10.Города. В БД хранится информация о городах и их жителях.

Для городов необходимо хранить:

– название;

– год основания;

– площадь;

– количество населения для каждого типа жителей.

Для типов жителей необходимо хранить:

– город проживания;

– название;

– язык общения.

· Вывести информацию обо всех жителях заданного города, разговаривающих на заданном языке.

· Вывести информацию обо всех городах, в которых проживают жители выбранного типа.

· Вывести информацию о городе с заданным количеством населения
и всех типах жителей, в нем проживающих.

· Вывести информацию о самом древнем типе жителей.

11.Планеты. В БД хранится информация о планетах, их спутниках
и галактиках.

Для планет необходимо хранить:

– название;

– радиус;

– температуру ядра;

– наличие атмосферы;

– наличие жизни;

– спутники.

Для спутников необходимо хранить:

– название;

– радиус;

– расстояние до планеты.

Для галактик необходимо хранить:

– название;

– планеты.

· Вывести информацию обо всех планетах, на которых присутствует жизнь, и их спутниках в заданной галактике.

· Вывести информацию о планетах и их спутниках, имеющих наименьший радиус и наибольшее количество спутников.

· Вывести информацию о планете, галактике, в которой она находится,
и ее спутниках, имеющей максимальное количество спутников, но
с наименьшим общим объемом этих спутников.

· Найти галактику, сумма ядерных температур планет которой наибольшая.

12.Точки. В БД хранится некоторое конечное множество точек с их координатами.

· Вывести точку из множества, наиболее приближенную к заданной.

· Вывести точку из множества, наиболее удаленную от заданной.

· Вывести точки из множества, лежащие на одной прямой с заданной прямой.

13.Треугольники. В БД хранятся треугольники и координаты их точек на плоскости.

· Вывести треугольник, площадь которого наиболее приближена к заданной.

· Вывести треугольники, сумма площадей которых наиболее приближена к заданной.

· Вывести треугольники, которые помещаются в окружность заданного радиуса.

14.Словарь. В БД хранится англо-русский словарь, в ко­тором для одного английского слова может быть указано нес­колько его значений и наоборот. Со стороны клиента вводятся последовательно английские (русские) слова. Для каждого из них вывести на консоль все русские (английские) значения слова.

15.Словари. В двух различных базах данных хранятся два словаря: русско-белорусский и белорусско-русский. Клиент вводит слово и выбирает язык. Вывести перевод этого слова.

16.Стихотворения. В БД хранятся несколько стихотворений с указанием автора и года создания. Для хранения стихотворений использовать объекты типа Blob. Клиент выбирает автора и критерий поиска.

· в каком из стихотворений больше всего восклицательных предложений?

· в каком из стихотворений меньше всего повествовательных предложений?

· есть ли среди стихотворений сонеты и сколько их?

17. Четырехугольники. В БД хранятся координаты вершин выпуклых четырехугольников на плоскости.

· Вывести координаты вершин параллелограммов.

· Вывести координаты вершин трапеций.

18.Треугольники. В БД хранятся координаты вершин треугольников
на плоскости.

· Вывести все равнобедренные треугольники.

· Вывести все равносторонние треугольники.

· Вывести все прямоугольные треугольники.

· Вывести все тупоугольные треугольники с площадью больше заданной.

Вариант B

Для заданий варианта В главы 4 создать базу данных для хранения информации. Определить класс для организации соединения (пула соединений). Создать классы для выполнения соответствующих заданию запросов в БД.

Тестовые задания к главе 20

Вопрос 20.1.

Объекты каких классов позволяют загрузить и зарегистрировать необходимый JDBC-драйвер и получить соединение с базой данных или получить доступ
к БД через пространство имен?

1) java.sql.DriverManager;

2) javax.sql.DataSource;

3) java.sql.Statement;

4) java.sql.ResultSet;

5) java.sql.Connection.

Вопрос 20.2.

Какой интерфейс из пакета java.sql должен реализовывать каждый драйвер JDBC?

1) Driver;

2) DriverManager;

3) Connection;

4) DriverPropertyInfo;

5) ResultSet.

Вопрос 20.3.

С помощью какого метода интерфейса Connection можно получить сведения о базе данных, с которой установлено соединение?

1) getMetaData();

2) getDatabaseInfo();

3) getInfo();

4) getMetaInfo();

5) getDatabaseMetaData().

Вопрос 20.4.

Какой интерфейс пакета java.sql используется, когда запрос к источнику данных является обращением к хранимой процедуре?

1) Statement;

2) PreparedStatement;

3) StoredStatement;

4) CallableStatement;

5) StoredProcedure.

Вопрос 20.5.

Какой метод интерфейса Statement необходимо использовать при выполнении SQL-оператора SELECT, который возвращает объект ResulSet?

1) execute();

2) executeQuery();

3) executeUpdate();

4) executeBatch();

5) executeSelect();

6) executeSQL().

 
Глава 21

CЕССИИ, СОБЫТИЯ и ФИЛЬТРЫ

Сеанс (сессия)

При посещении клиентом Web-ресурса и выполнении вариантов запросов, контекстная информация о клиенте не хранится. В протоколе HTTP нет возможностей для сохранения и изменения информации о предыдущих посещениях клиента. При этом возникают проблемы в распределенных системах c различными уровнями доступа для разных пользователей. Действия, которые может делать администратор системы, не может выполнять гость. В данном случае необходима проверка прав пользователя при переходе с одной страницы на другую. В иных случаях необходима информация о предыдущих запросах клиента. Существует несколько способов хранения текущей информации о клиенте или о нескольких соединениях клиента с сервером.

Сеанс (сессия) – соединение между клиентом и сервером, устанавливаемое на определенное время, за которое клиент может отправить на сервер сколько угодно запросов. Сеанс устанавливается непосредственно между клиентом и Web-сервером. Каждый клиент устанавливает с сервером свой собственный сеанс.

Сеансы используются для обеспечения хранения данных во время нескольких запросов Web-страницы или на обработку информации, введенной в пользовательскую форму в результате нескольких HTTP-соединений (например, клиент совершает несколько покупок в интернет-магазине; студент отвечает на несколько тестов в системе дистанционного обучения). Как правило, при работе с сессией возникают следующие проблемы:

· поддержка распределенной сессии (синхронизация/репликация данных, уникальность идентификаторов и т.д.);

· обеспечение безопасности;

· проблема инвалидации сессии (expiration), предупреждение пользователя об уничтожении сессии и возможность ее продления (watchdog).

Чтобы открыть новый сеанс, используется метод getSession() интерфейса HttpServletRequest. Метод извлекает из переданного в сервлет запроса объект сессии класса HttpSession,соответствующий данному пользователю. Сессия содержит информацию о дате и времени создания последнего обращения
к сессии, которая может быть извлечена с помощью методов
getCreationTime() и getLastAccessedTime().

Если для метода getSession(boolean param) входной параметр равен true, то сервлет-контейнер проверяет наличие активного сеанса, установленного с данным клиентом. В случае успеха метод возвращает дескриптор этого сеанса. В противном случае метод устанавливает новый сеанс:

HttpSession se = request.getSession(true);

после чего начинается сбор информации о клиенте.

Чтобы сохранить значения переменной в текущем сеансе, используется метод setAttribute()класса HttpSession, прочесть – getAttribute(), удалить – removeAttribute(). Список имен всех переменных, сохраненных в текущем сеансе, можно получить, используя метод Enumeration getAttributeNames(), работающий так же, как и соответствующий метод интерфейса HttpServletRequest.

Метод String getId() возвращает уникальный идентификатор, который получает каждый сеанс при создании. Метод isNew() возвращает false для уже существующего сеанса и true– для только что созданного.

Если требуется сохранить для использования одну из переменных сеанса, представляющего собой целое число, то:

se.setAttribute("teacherId", new Integer(71));

После этого любой подключившийся к текущему сеансу сервлет сможет прочесть значение переменной teacherId следующим образом:

Integer testId = (Integer)se.getAttribute("teacherID");

Завершить сеанс можно методом invalidate(). Сеанс уничтожает все связи с объектами, и данные, сохраненные в старом сеансе, будут потеряны для всех приложений.

/* пример # 1 : добавление информации в сессию : SessionServlet.java */

package chapt21;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

 

public class SessionServlet extends HttpServlet {

protected void doGet(

HttpServletRequest req,

HttpServletResponse resp)

throws ServletException {

performTask(req, resp);

}

private void performTask(

HttpServletRequest req,

HttpServletResponse resp)

throws ServletException {