Первый из вариантов можно назвать решением «в лоб». Оно предполагает открытие и закрытие подключения к БД в теле самого сервлета, в методе «doGet» или «doPost». В чем минус такого подхода? При создании нового соединения к БД затрачивается определенное время. Чем больше нагрузка на сервер, тем это время может быть больше (впрочем, не для всех БД). Количество единовременных подключений, в коммерческих серверах БД, определяется лицензией, в большинстве случаев, ограниченной. Кроме того, на поддержку многочисленных подключений может просто не хватить физических ресурсов сервера. Тем не менее, если пользователей вашего приложения можно пересчитать по пальцам, то стоит ли готовить пушку на воробьев?
Второй вариант состоит в том, чтобы организовать одно подключение к БД и давать его во «временное пользование» сервлетам. Расплатой за такой подход будет необходимость четкого планирования времени, затрачиваемого сервлетами на выполнение операций. Ни один из сервлетов не может занимать подключение на длительный срок, так как это парализует работу остальных сервлетов и пользователей. Рассмотрим этапы реализации подобного решения:
- Создание глобального, в рамках веб-приложения, объекта MyConnManager. Открытие в его конструкторе подключения.
- Создание public-методов для предоставления доступа к этому соединению вызывающим сервлетам. Методы, конечно, должны быть синхронизированы (synchronized).
- Регистрация MyConnManager в контексте приложения, для возможности его вызова из JSP.
public class MyConnManager {
private Connection connection;
private boolean connectionFree = true;
public MyConnManager () throws Exception {
Class.forName("com.mysql.jdbc.Driver").newInstance();
this.connection = null;
try {
Properties props = new Properties();
props.setProperty( "user", "user");
props.setProperty( "password", " password ");
props.setProperty( "useUnicode", "true" );
props.setProperty( "characterEncoding", "windows-1251" );
this.connection = DriverManager.getConnection(
"jdbc:mysql://localhost/Public", props);
} catch (Exception e) {System.out.println(e.getMessage());}
}
public synchronized Connection getConnection() {
while (this.connectionFree == false) {
try {
wait();
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
this.connectionFree = false;
notify();
return this.connection;
}
public synchronized void releaseConnection() {
while (this.connectionFree == true) {
try {
wait();
} catch (InterruptedException e)
{System.out.println(e.getMessage());
}
}
this.connectionFree = true;
notify();
}
public void close() {
try {
this.connection.close();
} catch (SQLException e) {
System.out.println(e.getMessage());
}
}
В примере, приведенном выше, мы используем совершенно тривиальный метод
подключения к базе данных, работающей под управлением MySQL сервера. Полученное подключение мы сохраняем во внутреннем private-поле, чтобы исключить возможность самовольного доступа к нему из внешних классов. В том числе и сервлетов. Получение класса Connection, который представляет собой подключение, возможно только используя специально предусмотренные методы getConnection(). Соответственно, возвращение подключения - releaseConnection(). Для не знакомых с принципами многопоточного программирования, поясню. Для того чтобы получить возможность выполнить любые действия с БД, сервлету, или любому другому классу, необходимо вызвать метод getConnection(). При этом: флаг connectionFree устанавливается в False, а классу возвращается объект java.sql.Connection. При попытке получить подключение другим объектом (например, сервлетом), проверяется состояние флага и, если оно не свободно (флаг в состоянии False), то wait() заставляет объект ждать, пока флаг не установится в True. После чего уже второй объект установит флаг, воспользуется подключением и освободит его вызовом releaseConnection(). Ключевое слово synchronized указывает на необходимость предоставлять доступ к методу «строго по одному». То есть, в 1 момент времени только 1 поток будет иметь возможность работать с методом, устанавливать значения переменных и так далее.
Итак, менеджер мы построили. Если предполагается использовать его
только из сервлетов, то никаких дальнейших телодвижений, кроме импорта сервлетами соответствующего пакета, не требуется. Но если вы предполагаете строительство приложения с использованием JSP, то ситуация несколько усложняется. Нам необходимо зарегистрировать объект менеджера, чтобы дать возможность JSP-страницам использовать его. Для этого мы должны всего-навсего создать объект менеджера и сохранить его в контексте веб-приложения. Сделать это можно, например, так:
import javax.servlet.*;
public final class ContextListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent servletContextEvent) {
ServletContext servletContext = servletContextEvent.getServletContext();
try {
MyConnManager connmanager = new MyConnManager ();
servletContext.setAttribute("connmanager ", connmanager);
} catch (Exception e) {
servletContext.log(
"Couldn't create connmanager sttribute: " + e.getMessage());
}
}
public void contextDestroyed(ServletContextEvent ServletContextEvent) {
ServletContext servletContext = servletContextEvent.getServletContext();
MyConnManager servletContext = (MyConnManager)
servletContext.getAttribute("servletContext ");
servletContext.close();
servletContext.removeAttribute("connmanager ");
}
}
Как видите, ничего сложного нет. Реализованный подход, вероятно, будет
использоваться вами наиболее часто при разработке приложений с малым количеством пользователей. Адепты программирования могут возразить мне, что в любом случае надо использовать пул подключений, но у меня есть повод не согласиться с ними. Как вам известно, самый главный лозунг программирования звучит как “Keep it simple”. Поэтому я не вижу смысла усложнять то, что и так отлично работает.
И, наконец, Третий способ, которого так ждали большевики.
Создание пула соединений, несмотря на громкое и значительное название,
вовсе не такая уж сложная задача. Под этим понятием скрывается, всего-навсего, уже знакомый нам менеджер соединений, имеющий в своем распоряжении набор открытых соединений.
На практике, для хранения объектов Connection, чаще всего используются
массивы. В зависимости от подхода автора, можно городить сложные структуры либо сделать нечто более простое.
Давайте наметим поле работы:
- пул должен существовать в единственном экземпляре
- его методы должны быть синхронизированы
- необходимо private-хранилище открытых коннектов, нечто типа массива
Вроде бы, мы перечислили все необходимые условия? Прежде, чем мы
приступим, хочу уточнить, что есть несколько способов поддержания единственности менеджера: либо создавать менеджер в конструкторе главного объекта нашего приложения, такой подход был рассмотрен в предыдущем примере, либо создать в менеджере метод, который будет возвращать самого менеджера. Немного запутанно, не так ли? Но все очень просто. Надо всего лишь создать объект, конструктор которого будет объявлен, как private и вызывать его из открытого статического метода.
Примерно вот так это должно выглядеть. Как вы видите, мы проверяем, не
создан ли уже наш менеджер и, если не создан, создает его, присваивает статической переменной, и возвращает ссылку на него.
public class ConnectionPool {
private static ConnectionPool mc;
private int maxConn=10;
private Connection[] ConnArray = new Connection[maxConn];
private int withdrewed=0;
private ConnectionPool() {
for (int i = 0; i < maxConn; i++){
ConnArray[i] = openOneConnection();
}
}
static public ConnectionPool getConnPool(){
ConnectionPool mcc;
mcc = (mc==null?new ConnectionPool():mc);
mc = mcc;
return mcc;
}
Дальше мы должны озаботиться тем, чтобы менеджеру было что возвращать.
Это делается путем создания массива и заполнения его объектами java.sql.Connection. Покончив с этим, создаем ключевой метод. Традиционно называем его getConnection(). Определен он, например, таким образом:
synchronized public Connection getConnection(){
Connection conn=null;
while (withdrewed==10){
try {
wait();
} catch (InterruptedException e) { }
}
withdrewed++;
for (int i = 0; i < maxConn; i++){
if (ConnArray[i] != null) {
conn = ConnArray[i];
ConnArray[i] = null;
break;
}
}
notify();
return conn;
}
Метод синхронизирован, работает с самым обычным массивом. Переменнаяwithdrewed указывает, сколько подключений занято на данный момент времени. Если она равна 10, значит, свободных подключений на данный момент времени нет, и поток «просят» подождать.
Возвращается подключение в «родные пенаты» методом
releaseConnection(). Все, что он делает, находит пустую(равную null) ячейку, и присваивает ей Connection, после чего уменьшает переменную на withdrewed единицу. Для того, чтобы не сохранять соединение в неопределенном состоянии, мы вызываем метод rollback, который отменяет все действия, ранее сделанные с использование этого соединения, которые не были завершенны вызовом Commit или Rollback. Подробнее о rollback и commit рассказывается в учебниках по SQL.
synchronized public void releaseConnection(Connection conn){
withdrewed--;
conn.rollback();
for (int i = 0; i<maxConn; i++){
if (ConnArray[i] == null){
ConnArray[i] = conn;
break;
}
}
notify();
}
Собственно, вот и все. Можно сказать, что основная работа проведена. Для
полноты картины стоит определить еще один метод, с помощью которого можно было бы закрыть и уничтожить все открытые подключения к БД. Код настолько просто, что комментировать в нем попросту нечего.
synchronized public void closeAll(){
for (int i = 0; i<maxConn; i++){
if (ConnArray[i] != null)
try{
ConnArray[i].rollback();
ConnArray[i].close();
} catch (Exception e){}
}
ConnArray = null;
}
Подводя итоги этой заметки, хочу сказать следующее: я только начинаю знакомиться с Java.Поэтому есть вероятность (большая, да) того, что текст изобилует неточностями. Буду рад выслушать любую конструктивную критику. Для тех, кто знает еще меньше чем я: все три описанных подхода работают и испытаны на кроликах клиентах. Постарайтесь просто не бояться громких названий. У вас все получиться. Удачи!
Спасибо Александру Чистякову и Алексею Филиппову, которые терпеливо сносили мои постоянные "где это искать".
Распространение заметки свободное. Указание автора и первоисточника - обязательно
http://www.shubert.ru/main.php?sect=2&sub=2&doc=2
1 коммент.:
Хорошая статья, слегка напоминает реализацию пула из книги Девида Гери "JSP" . Но на практике подобную технику реализовывать не стоит, ее нужно хорошенько переделать, поскольку она неучитывает ньюансы сервера БД: при обычной перезапуске сервака без перезапуска веб-сервера соединения в пуле остаются, но они уже не рабочие, и будут все время ити ошыбки (для каждого БД сервера разные).
Также получается, если количество доступных соединений с одной базой ограничено вручную.
Отправить комментарий