|
Доступ к БД из сервлета
(Владислав Каменский)
Доступ к БД из сервлета.
Взаимосвязь апплет-сервлет.
Решение проблемы русификации.
Здесь вы найдете исчерпывающую информацию, о том как получить доступ к БД, как с ней
работать, и какие здесь есть "подводные камни". Прежде всего о том, почему это
мы собрались работать с БД именно из сервлета. Дело в том, что интерес представляется
именно в том чтобы, работать с БД, находящейся для пользователя на удаленном сервере, а
не на его локальной машине. Оптимальным, по моему мнению, это написание связки
Апплет-Сервлет. Апплет отвечает за интерфейс взаимодействия пользователя с БД. Сервлет -
обрабатывает запросы, апплета, работает с БД, и посылает ответные данные. Т.е. вся
"внутренняя кухня" возлагается на сервлет. Тем более, что апплету по большому
счету, и не разрешен доступ к БД. Если же вы совсем не хотите заморачиваться, то можете
формировать HTML страницы, вместо того, чтобы отображать данные через апплет. Для
тестирования своих сервлетов я использую Java Web Server 2.0 - он наиболее полно
реализует технологию сервлетов (это и не странно, т.к. и сервер и сервлеты продукция
Sun). Итак приступим.
Вариант 1: Драйвер ODBC
// загружаем драйвер
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
// bdName имя БД вашем драйвере ODBC
String url = "jdbc:odbc:bdName";
// логин
user = "root";
// пароль
passwd="";
// установим соединение
Connection connection =
DriverManager.getConnection(url, user, passwd);
Вариант 2: Драйвер mySQL
localhost:3306/bdName";
// здесь 3306 - стандартный
// порт сервера БД mySQL, а localhost его имя.
String url = "jdbc:mysql:
// этот драйвер не входит
// в стандартную поставку JDK,
// но его можно скачать с java.sun.com
String driverName = "org.gjt.mm.mysql.Driver";
//логин
user = "root";
//пароль
passwd="";
Class.forName(driverName);
Connection connection =
DriverManager.getConnection(url, user, passwd);
Примечание: Далее работа с БД, ведется одинаково,
независимо от вида SQL базы, т.е. загружая различные драйверы, мы можем работать с
различными БД, не изменяя при этом исходного текста (который мы научимся
создавать).
Здесь стоит отметить, что полученный объект Connection, следует сохранить в
HttpSession. Для тех, кто не знает, что это такое объясню, на пальцах. Между браузером и
сервером, при вызове сервлета образуется сеанс связи, за которым следит сервак. Так вот,
этот сеанс связи может запоминать пары типа: Имя-Значение, этим мы и воспользуемся для
того, чтобы при следующем вызове сервлета, не устанавливать соединение заново, а
быстренько получить его из сеанса связи:
public void doPost
(HttpServletRequest req, HttpServletResponse res)
{
// получим доступ к сеансу связи
HttpSession session = req.getSession(true);
// положим туда значение Connection
session.putValue("connect",connection);
...
}
// теперь как достать соединение
public void doPost
(HttpServletRequest req, HttpServletResponse res)
{
HttpSession session = req.getSession(true);
Connection connection =
(Connection)session.getValue("connect");
...
}
Итак сеанс связи установлен, что же дальше, а дальше все очень просто (надеюсь вы немного
знакомы с языком SQL).
// Это объект, используемый
// для выполнения операторов SQL
// и получения результатов их исполнения
Statement statement = connection.createStatement();
// осуществим выборку из БД согласно синтаксису оператора SELECT
String query = "SELECT *FROM man";
// man - это какая-нибудь таблица в вашей БД, так
// мы осуществим выборку всей таблицы, если нужны
// записи удовлетворяющие некоторым условиям
// String query = "SELECT *FROM man WHERE и т.д. домыслите сами
// теперь осуществим выборку из таблицы БД
ResultSet resultSet = statement.executeQuery(query);
// в ResultSet хранятся все данные БД, удовлетворяющие
// запросу query
Теперь осталось разобраться, как достать данные из ResultSet, но перед этим задумаемся
где мы будем хранить их. Для этой цели я предлагаю использовать объект типа Vector,
причем конечно же двумерный(в смысле вектор векторов).
// это очень удобный
// класс, из которого можно почерпнуть такую
// полезную информацию, как количество полей в БД
ResultSetMetaData metadata =resultSet.getMetaData();
rows = new Vector(); // будет двумерным вектором
while (resultSet.next())
{
// сюда будем записывать значения
// каждого ряда данных
Vector newRow = new Vector();
Object obj;
// пробег по записи БД
for (int i = 1; i <= metadata.getColumnCount(); i++)
{
// получим значение ячейки
obj = resultSet.getObject(i);
// формируем запись
newRow.addElement(obj);
}
// прибавляем запись
rows.addElement(newRow);
}
Теперь в векторе rows хранится вся необходимая для нас информация. И мы можем с успехом
ее передать апплету (об этом немного позже), а пока отмечу, что такой способ не вполне
корректен. Т.к. на самом деле для каждого типа SQL существует определенный метод getXXX
(см. документацию по JDBC). И чтобы ваша программа была неуязвима, стоит сделать примерно
так:
ResultSetMetaData metadata =resultSet.getMetaData();
rows = new Vector();
while (resultSet.next())
{
// сюда будем записывать значения
// каждого ряда данных
Vector newRow = new Vector();
Object obj;
// пробег по записи БД
for (int i = 1; i <= metadata.getColumnCount(); i++)
{
int type = metaData.getColumnType(i);
switch(type)
{
case Types.INTEGER:
obj = resultSet.getInt(i);
break;
case Types.REAL:
obj = resultSet.getFloat(i);
break;
case Types.TIMESTAMP:
obj = resultSet.getTimestamp(i);
break;
// и т.д. для каждого типа SQL
}
...
// формируем запись
newRow.addElement(obj);
}
// прибавляем запись
rows.addElement(newRow);
}
Теперь остановимся подробней на "подводных камнях". Дело в том, что, если при
получении из БД текстовых данных, наивно применить метод getString(), то вместо русских
букв, вы получите невесть что(очень похоже на криптографические записки немецких
шпионов). Поступать тут стоит значительно изощреннее, но по большому счету, особых
трудностей здесь нет, если вспомнить о таком замечательном формате как Unicode. Что это
такое думаю вы знаете. Привожу фрагмент кода, котороый позволяет читать русские буквы,
куда этот фрагмент нужно вставить в предыдущий пример, я думаю вы догадаетесь.
// кодировка Windows
String cp = new String("Cp1251");
InputStream txt1 = resultSet.getUnicodeStream(i);
// здесь будем хранить прочитанную
// строку из БД в формате Unicode
byte txt2[];
// размер ячейки
int columnsize = metadata.getColumnDisplaySize(i);
// отведем двойной размер, т.к. в
// Unicode два байта под символ
txt2 = new byte[2*columnsize];
// число прочитанных байтов всегда кратно
// двум. Делим на два, т.к. первый байт всегда ноль
int len = (txt1.read(txt2))/2;
// здесь - будет находится готовая строка
// (удалены лидирующие нули)
byte txt3[];
// и заводим массив, куда перепишем каждый второй байт
txt3 = new byte[len];
for (int j=0;j<(len);j++)
{
// убираем первый, нулевой байт, т.е записываем
// только каждый второй байт
txt3[j]=txt2[j*2+1];
}
// устанавливаем нужную нам кодировку
String str = new String(txt3,cp);
// здесь уже хранятся русские буквы, а не абракадабра.
obj = new String(str);
Примечание: При работе с mySQL вопреки ожиданиям,
оказалось, что убирать первый байт оказалось ненужным, и в массиве байтов
txt2 оказалась нужная нам строка. Но в любом случае действовать нужно
подобным образом, и легче всего - опытным: попробовать записать каждый второй байт, и
если результатом будет строка, записанная через один байт, то просто опустить фрагмент
кода, записывающий каждый второй байт, а пользоваться непосредственно массивом
txt2
Теперь, когда мы (с таким трудом) получили русские буковки, и всякие там очень нужные
нам циферки, задумаемся о том как бы переправить эту всю информацию апплету. Это сделать
очень легко, помятуя о том, что наши данные хранятся в объекте Vector, а он в свою
очередь реализует интерфейс Serializable, что позволит нам без труда переправить данные
апплету.
protected void sendVector(HttpServletResponse res, Vector resp)
{
try
{
ObjectOutputStream pout =
new ObjectOutputStream(res.getOutputStream());
pout.writeObject(resp);
pout.flush();
pout.close();
}
catch (Exception ex)
{
System.out.println(ex);
}
}
И это, как это не странно, все, к тому же русские буквы не исказятся, т.к. передача
осуществиться побайтно. Чтобы все выглядело совсем уж изящно, приведем текст приемной
стороны (апплета).
URL servlet;
servlet = new URL(applet.getCodeBase(),SERVLET_PATH);
// String SERVLET_PATH = "/servlet/RespServlet1";
// установим соединение
// метод POST
URLConnection conn = servlet.openConnection();
// установим параметры
conn.setDoInput(true);
// соединения
conn.setDoOutput(true);
conn.setUseCaches(false);
//установим параметры запроса
conn.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
DataOutputStream out =
new DataOutputStream(conn.getOutputStream());
// если нужно передадим параметры сервлету
out.writeBytes(...);
// и наконец, начнем читать данные
// получим входной поток
InputStream result = conn.getInputStream();
// создадим на его базе более удобный поток
ObjectInputStream in = new ObjectInputStream(result);
// читаем таблицу базы данных
Vector vec = (Vector)in.readObject();
in.close();
Примечание: Надо понимать, что мы будем предавать
данные из апплета сервлету методом POST, поэтому данные должны быть в виде
"имя=значение". Это нужно сделать примерно так:
String toservlet=
URLEncoder.encode("name1")+"="
+URLEncoder.encode("value1")+"&"
+URLEncoder.encode("name2")+"="
+URLEncoder.encode("value2");
out.writeBytes(toservlet);
Итак процесс завершен, апплет получил данные, которые пришли из БД, посредством
сервлета, к тому же мы обошли проблему, связанную с кириллическими символами. Теперь
остается написать интерфейс, который бы красочно отобразил те данные которые мы так с
успехом получили из БД. Для этих целей я советую объект JTable библиотеки Swing. Вещь
запутанная, но если разобраться, то все будет выглядеть очень красиво. Осталось
разобраться с тем как сервлету получить данные, которые задумает послать ему апплет,
например при редактировании БД. Согласно документации по JDBC делать это надо примерно
так:
public void doPost
(HttpServletRequest req, HttpServletResponse res)
{
String mode;
mode = req.getParameter("mode");
if (mode != null)
{
// здесь обрабатываем полученное значение
}
}
Но, если апплет послал данные в виде русских букв, то нужна перекодировка полученного
значения:
public void doPost
(HttpServletRequest req, HttpServletResponse res)
{
String mode;
mode = req.getParameter("mode");
// переведем в нужную кодировку
String rusmode = new String(
mode.getBytes(res.getCharacterEncoding()),"Cp1251");
if (mode != null)
{
// здесь обрабатываем полученное значение
}
}
Если вы не хотите связываться с апплетом (это разумно в том случае, если ваши системные
ресурсы не позволяют использовать библиотеку Swing), то конечно же вы изберете путь
формирования динамических HTML страниц. Поступать здесь следует следующим способом:
public void doPost
(HttpServletRequest req, HttpServletResponse res)
{
res.setContentType("text/html");
ServletOutputStream out = res.getOutputStream();
out.println(""+
"<html>"+
"<head>"+ ....);
// и т.д. формируем
// html страницу
}
Причем, если вы берете данные из БД, то проблемы с русскими буквами может и не
возникнуть, например в связке JavaWebServer и Access никакая перекодировка не
потребовалась, а вот с mySQL этот номер не прошел, но здесь как вы наверное уже
догадались, помогает метод описанный выше, через UnicodeStream. Если же вы хотите видеть
на сформированной html странице фрагмент кириллического текста (взятого не из БД):
public void doPost
(HttpServletRequest req, HttpServletResponse res)
{
res.setContentType("text/html");
ServletOutputStream out = res.getOutputStream();
String rustxt = "Это я хочу видеть на моей странице";
// осуществим перекодировку, обратную той, что
// мы делали доселе
String rustxtencode =
new String(rustxt.getBytes("Cp1251"),
res.getCharacterEncoding());
out.println(""+
"<html>"+
"<head>"+
// заголовок будет из русских
// букв. и т.д. со всеми
// русскими строками
"<title>"+rustxtencode+
"</title>"+
"</head>"+ ...
}
Ну вот и все, вроде бы обо всем, о чем хотел рассказать, я поведал. Если у кого возникнут
вопросы по поводу моей статьи, пишите на мыло, постараюсь ответить.
Центральный Научно-Исследовательский Институт
Робототехники и Кибернетики (ЦНИИ РТК)
Санкт-Петербург.
Дата последнего обновления: 4 апреля 2000 г.
|