Оглавление | Назад | Вперёд | Индекс

Глава 8
Соединение с Базой Данных

В этой главе обсуждается использование службы LiveWire Database Service для соединения Вашего приложения с реляционными базами данных DB2, Informix, ODBC, Oracle или Sybase. Показано, как выбрать наилучшую методологию для Вашего приложения.

В главе имеются следующие разделы:

Взаимодействие с Базами Данных


Ваше JavaScript-приложение, запущенное на сервере Netscape Enterprise Server, может использовать LiveWire Database Service для доступа к БД серверов Informix, Oracle, Sybase и DB2 и серверов, использующих стандарт Open Database Connectivity (ODBC). Ваше приложение, запущенное на сервере Netscape FastTrack Server, может получать доступ только к БД на серверах, использующих ODBC.

В последующих обсуждениях предполагается, что Вы уже знакомы реляционными БД и Structured Query Language (SQL).

Прежде чем создать приложение JavaScript с использованием LiveWire, база или базы данных, к которым Вы планируете подключаться, должны уже существовать на сервере БД. Также Вы должны знать их структуру. Если Вы создаёте совершенно новое приложение, включающее БД, необходимо создать БД и заполнить её данными (как минимум в форме прототипа) до создания приложения.

До того как Вы попытаетесь использовать LiveWire, убедитесь, что Ваша рабочая среда сконфигурирована соответственно. О том, как конфигурировать, см. Главу 10, "Конфигурирование Вашей Базы Данных." Вы можете также использовать приложение-образец videoapp, описанное в Главе 13, "Приложения-Образцы Videoapp и Oldvideo," для изучения некоторых возможностей LiveWire.

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

  1. Использовать объект database или создать объект DbPool для установки пула соединений БД. Это обычно выполняется на начальной странице приложения, если только оно не требует установления специального соединения.
  2. Подключить пул к БД. Это также обычно выполняется на начальной странице приложения.
  3. Затребовать соединение из пула. Это выполняется неявно при использовании объекта database или явно - при использовании метода connection объекта DbPool.
  4. Если Вы изменяете информацию в БД, начинается транзакция. Транзакции БД обсуждаются в разделе "Обслуживание Транзакций ".
  5. Создать курсор или вызывать хранимую процедуру для работы с информацией из БД. Здесь может происходить вывод результатов выполнения запроса или обновление содержимого БД. Закройте открытый курсор, результирующий набор или хранимую процедуру после окончания их использования. Курсоры обсуждаются в разделе "Обслуживание Результатов Выполнения Запроса с Помощью Курсоров"; хранимые процедуры обсуждаются в разделе "Вызов Хранимых Процедур".
  6. Подтвердить транзакцию или выполнить откат.
  7. Освободить соединение с БД (если используете объекты Connection).

В этой главе обсуждаются первые три пункта действий. В Главе 9, "Работа с БД", обсуждаются остальные шаги.

Соединение. Подходы.


Есть два основных способа соединения с БД с помощью сервиса LiveWire Database Service. Это объекты DbPool и Connection, либо объект database.

Соединение с Помощью Объектов DbPool и Connection


При этом подходе Вы создаёте пул соединений для работы с реляционной БД. Вы создаёте экземпляр класса DbPool,а затем получаете доступ к объектам Connection через этот объект DbPool. Объекты DbPool и Connection распределяют между собой работу по соединению с БД и обслуживанию набора соединений и доступ к БД через соединение.

Это весьма гибкий подход. Ваше приложение может иметь несколько пулов соединений, каждый со своей собственной конфигурацией БД и пользователя. Каждый пул может иметь несколько соединений при такой конфигурации. Это даёт одновременный доступ к нескольким БД или к одной БД из нескольких бюджетов. Вы можете также ассоциировать пул непосредственно с приложением, а не с отдельным клиентским запросом, и иметь таким образом транзакции, захватывающие несколько клиентских запросов. Это ассоциирование выполняется путём присвоения пула свойству объекта project и удаления этого присвоения после окончания работы с пулом.

Соединение с Объектом database


При этом подходе Вы используете предопределённый объект database для соединения с БД при наличии единственной конфигурации соединения БД и пользователя. Объект database выполняет все действия по работе с БД. Можно представить этот объект как database единый пул соединений с БД.

Этот подход несколько проще, так как используется только один объект database, а не несколько объектов DbPool и Connection. Однако при этом теряется гибкость первого подхода. Если Вы используете только объект database и хотите соединиться с разными БД или разными бюджетами, Вы обязаны отключиться от одной конфигурации, для того чтобы подключиться к другой. Также, при использовании объекта database, одна транзакция не может захватить несколько клиентских запросов, а соединения с несколькими БД-источниками не могут быть установлены одновременно.

Как описано в последующих разделах, Вы должны ответить на два основных вопроса, когда решаете, как устанавливать соединения с БД:

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

Таблица 8.1  Рассмотрение создания пулов БД
Сколько конфигураций БД? Где соединение с пулом? Где отключение пула? Какой объект(ы) содержит пул? Должен ли Ваш код хранить пул и соединение? Как Ваш код хранит пул и соединение в объекте project?

1, используется всеми клиентами

Начальная страница приложения

Нигде

database

Нет

--

1, используется всеми клиентами

Начальная страница приложения

Нигде

1 DbPool object

Да

DbPool: Именованное свойство;

Connection: 1 массив

Фиксированный набор, используется всеми клиентами

Начальная страница приложения

Нигде

N DbPool-объектов

Да

DbPool: Именованное свойство;

Connection: N массивов

Отдельный пул для каждого клиента

Клиентская страница запроса

Зависит от 1

Многие объекты DbPool

Только если соединение захватывает клиентские запросы

DbPool: 1 массив;

Connection: 1 массив

1

Если отдельное соединение не захватывает клиентские запросы, Вы можете соединять и отсоединять пул на каждой странице, в которой нужнó соединение. В этом случае пул не сохраняется в промежутке времени между запросами. Если отдельное соединение захватывает клиентские запросы, соединяйте на первой клиентской странице, которой необходимо соединение, и отсоединяйте на последней такой странице. Это может привести к появлению незанятых соединений/idle, и Ваше приложение должно будет обработать такую ситуацию.

Пулы Соединений с БД


Если Вы хотите использовать объект database, Вам не нужно создавать его. Это предопределённый объект, предоставляемый машиной выполнения JavaScript. Если Вам нужны дополнительные возможности класса DbPool, Вы создаёте экземпляр класса DbPool и соединяете этот объект с конкретной БД, которая создаёт пул соединений.

Вы можете создать общий DbPool-объект и специфицировать позднее информацию соединения (используя метод connect), или можете специфицировать информацию соединения при создании пула. Общий DbPool-объект не имеет никаких доступных соединений в момент его создания. Исходя из этого, Вам может понадобиться установить соединение при создании этого объекта. Если Вы используете объект database, Вы всегда обязаны устанавливать соединение путём вызова метода database.connect.

connect (dbtype, serverName, userName, password,
   databaseName, maxConnections, commitFlag);

При создании соединения Вы можете специфицировать следующую информацию, либо при создании DbPool-объекта, либо при вызове метода connect объекта DbPool или database:

Например, следующий оператор создаёт новый пул БД из 5 соединений с БД Oracle. В этом пуле неподтверждённые транзакции откатываются:

pool = new DbPool ("ORACLE", "myserver1", "ENG", "pwd1", "", 5);

Приложение-образец dbadmin позволяет Вам экспериментировать с соединениями с различными БД как разным пользователям.

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

Однако для некоторых приложений каждый клиент должен выполнять своё собственное соединение. Как сказано в разделе "Совместное Использование Массива Пулов Соединений," клиенты могут совместно использовать объекты. Если это так, убедитесь, что блокировки используются для управления совместным использованием данных, как указано в разделе "Безопасное Использование Объектов с Помощью Блокировки".

В следующей таблице показаны методы объектов DbPool и database для обслуживания пула соединений. (Объект database использует другие методы, рассмотренные ранее, для работы с соединением с БД.)

Таблица 8.2 Методы объектов DbPool и database для обслуживания пулов соединений

connect

Соединяет пул с определённой конфигурацией БД и пользователя.

connected

Проверяет, соединён ли пул и все его соединения с базой данных.

connection

(Только DbPool) Запрашивает доступный Connection-объект из пула.

disconnect

Отсоединяет все соединения пула от БД.

majorErrorCode

Главный код ошибки, возвращаемый сервером БД или ODBC.

majorErrorMessage

Главное сообщение об ошибке, возвращаемое сервером БД или ODBC.

minorErrorCode

Вторичный код ошибки, возвращаемый библиотекой продавца.

minorErrorMessage

Вторичное сообщение об ошибке, возвращаемое библиотекой продавца.

Однопоточные и Многопоточные Базы Данных


LiveWire поддерживает многопоточный доступ к БД. То есть она поддерживает наличие более чем одного потока доступа к одной БД в единицу времени. Отсюда ясно, для чего нужен пул соединений с более чем одним соединением. Однако библиотеки БД некоторых производителей не являются многопоточными. Для таких БД не имеет значения, сколько соединений имеется в Вашем пуле, так как только одно соединение может устанавливаться с БД в единицу времени.

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

Таблица 8.3  Многопоточность БД-клиентов на разных платформах
Sybase Informix Oracle DB2 ODBC 1

NT

Есть

Есть

Есть

Есть

Есть

Sun Solaris

Есть

Есть

Есть

Нет

Нет

HP-UX

Есть

Есть

Нет

Нет

Нет

IBM AIX

Есть

Есть

Нет

Есть

Нет

SGI IRIX

Нет

Нет

Нет

Не поддерживается

Нет

Digital Unix

Есть

Есть

Нет

Не поддерживается

Не поддерживается

1

Все многопоточные тесты для ODBC были сделаны на MS SQL Server. Если Вы используете другой драйвер ODBC, узнайте у производителя, является ли драйвер многопоточным.

Советы


Эти указания являются критичными для однопоточного доступа. Однако Вы должны думать об этом даже тогда, когда используете БД с многопоточным доступом.
Однопоточная библиотека БД может иметь существенные ограничения производительности. Поскольку только один поток имеет доступ к БД в единицу времени, все прочие потоки обязаны ждать, когда первый поток освободит соединение с БД, прежде чем один из них сможет получить доступ к БД. Если доступа ожидают одновременно несколько потоков, каждому придётся ожидать довольно долго.
При разработке доступа к БД Вы должны предусмотреть следующее:

Ограничения на использование транзакций для клиентских библиотек БД, которые не являются многопоточными:

Обслуживание Пулов Соединений


В любой данный момент времени соединённый объект DbPool или database и все соединения пула ассоциированы с определённой конфигурацией базы данных. То есть всё, что находится в пуле, соединено с определённым сервером БД как отдельный пользователь с отдельным паролем и с определённой БД.

Если Ваше приложение всегда использует одну конфигурацию, то можно использовать единственный объект DbPool или использовать объект database и соединяться однократно. В этом случае Вы должны выполнить соединение на начальной странице Вашего приложения.

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

Если Вы используете объект database и несколько соединений, то выбора у Вас нет. Вы обязаны соединяться, отсоединяться и повторно соединяться с объектом database каждый раз, когда Вам нужно изменить что-либо в конфигурации. Вы делаете это под управлением клиентских запросов. В этой ситуации убедитесь, что используются блокировки, как указано в разделе "Совместное Использование Объектов с Блокировкой," чтобы получать исключительный доступ к объекту database. Иначе другой клиентский запрос может отключить объект до того, как текущий клиентский запрос закончит с ним работу. Хотя Вы и можете использовать объект database таким образом, лучше будет всё-таки использовать объекты DbPool.

Если Вы используете объекты DbPool и несколько конфигураций, Вы также должны соединяться, отсоединяться и повторно соединяться с объектом DbPool. Однако с объектами DbPool у Вас появится больше возможностей. Вы можете создавать столько пулов, сколько нужно, и ставить их под контроль объекта project. (См. в Главе 6, "Обслуживание Сессий" информацию об объекте project.) Использование нескольких пулов более эффективно и обычно надёжнее, чем многократное использование единственного пула (как с объектом database , так и с единственным объектом DbPool).

При определении того, как обслуживать пулы, Вы обязаны учитывать два фактора: ко скольки конфигурациям будут иметь доступ пулы и нужно ли будет одному соединению захватывать несколько клиентских запросов. Если у Вас небольшое количество возможных конфигураций, Вы можете создать отдельные пулы для каждой. В разделе "Совместное Использование Фиксированного Набора Пулов Соединений" обсуждается этот подход.

Если у Вас имеется очень большое или заранее не известное количество конфигураций (например, если все пользователи БД получают свои индивидуальные ID), нужно предусмотреть два варианта. Если одного соединения достаточно для выполнения одного клиентского запроса, Вы можете создавать отдельные пулы на клиентской странице.

Однако иногда соединение должно захватывать несколько клиентских запросов (например, если одна транзакция в БД захватывает несколько клиентских запросов). Возможно также, что Вы просто не хотите повторно соединяться с БД на каждой странице приложения. Если это так, Вы можете создать массив пулов, который используется совместно. В разделе "Совместное Использование Массива Пулов Соединений" обсуждается этот подход.

Независимо от используемого подхода, если Вам больше не нужно отдельное соединение в пуле, зачистите ресурсы, используемые этим соединением, чтобы оно стало доступным для других пользователей. Чтобы выполнить это, закройте все открытые курсоры, хранимые процедуры и результирующие наборы. Верните соединение обратно в пул. (Вы не должны освобождать соединение, если используете объект database.)

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

Если Вы создаёте отдельные пулы БД для каждого пользователя, убедитесь, что пул отсоединён, до того как закончить с ним работу. О курсорах см "Манипуляции с Результатами Выполнения Запросов с Помощью Курсоров". О хранимых процедурах и результирующих наборах см. "Вызов Хранимых Процедур".

Совместное Использование Фиксированного Набора Пулов Соединений


Часто в приложении небольшой набор пулов соединений используется всеми пользователями данного приложения. Например, Вашему приложению нужно соединяться с тремя различными БД или с одной БД, которая использует 4 пользовательских ID, соответствующих 4 разным департаментам. Если у вас имеется небольшой набор возможных конфигураций соединения, Вы можете создать отдельный пул для каждой конфигурации. Для этого используйте объекты DbPool.

Тогда необходимо, чтобы пул работал в течение всего периода существования приложения, а не просто в течение периода существования клиента или отдельного клиентского запроса. Вы можете реализовать это, создав каждый пул БД как свойство объекта project. Например, начальная страница приложения может содержать эти операторы:

project.engpool = new DbPool ("ORACLE", "myserver1", "ENG",
   "pwd1", "", 5, true);
project.salespool = new DbPool ("INFORMIX", "myserver2", "SALES",
   "pwd2", "salsmktg", 2);
project.supppool = new DbPool ("SYBASE","myserver3","SUPPORT",
   "pwd3", "suppdb", 3, false);

Эти операторы создают три пула для различных групп пользователей приложения.
Пул project.eng содержит 5 соединений Oracle и подтверждает любую неподтверждённую транзакцию при высвобождении соединения обратно в пул.
Пул project.sales имеет два соединения Informix и откатывает любую неподтверждённую транзакцию при окончании соединения.
Пул project.supp имеет три соединения Sybase и откатывает любую неподтверждённую транзакцию при окончании соединения.

Вы должны создавать такой пул как часть начальной страницы приложения. Эта страница выполняется только при старте приложения. На страницах, доступных пользователям, Вы не создаёте пул и не изменяете соединение. Вместо этого эти страницы определяют, к какой группе принадлежит текущий пользователь, и используют уже установленное соединение из соответствующего пула. Например, в следующем коде определяется, какую БД использовать (на основе значения свойства userGroup объекта request), в БД ищется некоторая информация, которая выводится пользователю, а затем соединение освобождается:

if (request.userGroup == "SALES") {
   salesconn = project.salespool.connection("A sales connection");
   salesconn.SQLTable ("select * from dept");
   salesconn.release();
}

Вы можете также создать пул и изменить соединение на странице, доступной пользователю. В этом случае Вы должны быть осторожны, так как несколько пользователей, осуществляющих одновременный доступ к этой странице, не должны мешать друг другу. Например, только один пользователь должен иметь возможность создавать пул, используемый всеми остальными пользователями. О надёжности совместного использования информации см. раздел "Надёжное Совместное Использование Объектов с Помощью Блокировки".

Совместное Использование Массива Пулов Соединений


В разделе "Совместное Использование Фиксированного Набора Пулов Соединений" описано, как Вы можете применить свойства объекта project для совместного использования фиксированного набора пулов соединений. Этот подход используется, если Вам в процессе разработки уже известно количество необходимых пулов соединений и Вам нужно только небольшое количество соединений.

Часто нельзя предугадать заранее количество необходимых пулов соединений. В других случаях это возможно, но это количество недопустимо велико. Например, предположим, что для каждого потребителя, имеющего доступ к Вашему приложению, приложение проверяет пользовательский профиль на предмет определения того, какую информацию из БД вывести. Вы можете дать каждому потребителю уникальный пользовательский идентификатор ID для БД. Такое приложение требует, чтобы каждый пользователь имел свой набор параметров соединений (соответствующий различным пользовательским ID в БД) и, соответственно, разные пулы соединений.

Вы можете создать объект DbPool и соединять и отсоединять его на каждой странице приложения. Это будет работать, только если одно соединение не должно захватывать несколько клиентских запросов. Иначе эта ситуация должна обрабатываться по-разному.

Для данного приложения, вместо создания фиксированного набора пулов соединений на начальной странице приложения или пула на каждой клиентской странице, Вы создаёте одно свойство объекта project, которое будет содержать массив пулов соединений. Доступ к элементам этого массива осуществляется по ключу на базе определённого пользователя.
Во время инициализации Вы создаёте массив, но не помещаете в него элементы (поскольку никто ещё не пытался использовать приложение), как показано здесь:

project.sharedPools = new Object();

Когда пользователь впервые стартует приложение, оно получает идентифицирующий пользователя ключ. На основе этого ключа приложение создаёт объект пула DbPool и сохраняет его в массиве пулов. Имея данный пул соединений, оно может либо соединяться на каждой странице, либо устанавливать соединение так, как описано в разделе "Обслуживание Соединения по Запросам." Следующий код создаёт пул либо получает уже созданный, проверяет его соединение и работает затем с БД:

// Генерируется уникальный индекс для обращения к данному клиенту, если это
// ещё не было сделано на другой странице. О функции ssjs_generateClientID см.
// "Уникальное Обращение к Объекту client". if client.id == null {
   client.id = ssjs_generateClientID();
}
// Если пула для данного клиента ещё нет, он создаётся
// и производится его соединение с БД. project.lock();
if (project.sharedPools[client.id] == null) {
   project.sharedPools[client.id] = new DbPool ("ORACLE",
      "myserver", user, password, "", 5, false);
}
project.unlock();
// Для удобства устанавливается переменная для этого пула.
var clientPool = project.sharedPools[client.id];
// Теперь у Вас есть пул: посмотрим, соединён ли он. Если нет, попытаемся соединить его.
// Если это не удаётся, перенаправляем на специальную страницу,
// чтобы проинформировать пользователя.
project.lock();
if (!clientPool.connected()) {
   clientPool.connect("ORACLE", "myserver", user, password,
      "", 5, false);
   if (!clientPool.connected()) {
      delete project.sharedPools[client.id];
      project.unlock();
      redirect("noconnection.html");
   }
}
project.unlock();
// Если Вы дошли до этого места, Вы успешно соединились и
// можете работать с БД.
clientConn = clientPool.connection();
clientConn.SQLTable("select * from customers");
// ... другие операции с БД ...
// Всегда освобождайте соединение, если оно Вам больше не нужно.
clientConn.release();
}

Когда пользователь в следующий раз войдёт в приложение (например, с другой страницы приложения), он использует тот же самый код и получит сохранённый пул соединений и (возможно, сохранённый,) объект Connection из объекта project.

Если Вы используете ssjs_generateClientID и сохраняете ID в объекте client, Вам может понадобиться защита от вторжения через доступ к этому ID и, следовательно, к закрытой информации.

ПРИМЕЧАНИЕ:

Объект sharedConns, использованный в этом примере кода, не является предопределённым объектом JavaScript. Он просто создан в этом примере и может иметь другое имя по Вашему выбору.

Индивидуальные Соединения с Базой Данных


Как только Вы создали пул соединений, клиентская страница может получить доступ к индивидуальному соединению из пула. Если Вы используете объект database, соединение в данном объекте является неявным; то есть Вы используете методы объекта database для доступа к соединению. Если, однако, Вы используете объекты DbPool, соединение инкапсулируется в объекте Connection, который Вы получаете через вызов метода объекта DbPool. Например, в следующем пуле:

project.eng = new DbPool ("ORACLE", "myserver", "ENG", "pwd1", "", 5);

Вы можете получить соединение из пула с помощью такого вызова метода:

myconn = project.eng.connection ("My Connection", 60);

Оба параметра метода являются необязательными. Первый это имя соединения (используется при отладке); второй это целое число, обозначающее таймаут в секундах. В этом примере, если пул имеет доступное соединение или если оно становится доступным в течение 60 секунд, это соединение присваивается переменной myconn. Если соединение не становится доступным в течение указанного периода, этот метод возвращается без соединения. Дополнительно об ожидании получения соединения из пула см. раздел "Ожидание Соединения". О том, что делать, если соединение не получено, см. "Запрашивание Незанятого Соединения".

Если Вы закончили использование соединения, возвратите его в пул путём вызова метода release объекта Connection. (Если Вы используете объект database, Вам не нужно самостоятельно освобождать соединение). Прежде чем вызвать метод release, закройте все открытые курсоры, хранимые процедуры и результирующие наборы. Если Вы вызываете метод release, система ожидает, когда всё закроется, и возвращает затем соединение в пул базы данных. После этого соединение доступно следующему пользователю. Об использовании курсоров см. "Манипуляции с Результатами Запросов с Помощью Курсоров". О хранимых процедурах и результирующих наборах см. "Вызов Хранимых Процедур".

После получения соединения (через объект database или объект Connection), Вы можете работать с БД. В таблице резюмированы методы объектов database и connection для работы с единственным соединением. Объект database имеет и другие методы для обслуживания пула соединений, рассмотренные в разделе "Обслуживание Пулов Соединений."

Таблица 8.4  Методы Объектов database и Connection для Работы с Единственным Соединением
Метод Описание

cursor

Создаёт курсор БД для специфицированного оператора SQL SELECT.

SQLTable

Отображает результаты выполнения запроса. Создаёт таблицу HTML для результата выполнения оператора SQL SELECT.

execute

Выполняет специфицированный оператор SQL. Используется для операторов SQL, отличных от запросов/queries.

connected

Возвращает true, если пул БД (и, следовательно, данное соединение) соединён с БД.

release

(Только для Connection) Освобождает соединение обратно в пул.

beginTransaction

Начинает транзакцию SQL.

commitTransaction

Подтверждает текущую транзакцию SQL.

rollbackTransaction

Выполняет откат текущей транзакции SQL.

storedProc

Создаёт объект хранимой процедуры и запускает специфицированную хранимую процедуру БД.

majorErrorCode

Важнейший код ошибки, возвращаемый сервером БД или ODBC.

majorErrorMessage

Сообщение о важнейшей ошибке, возвращаемое сервером БД или ODBC.

minorErrorCode

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

minorErrorMessage

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

Обслуживание Соединения по Нескольким Запросам


В некоторых случаях может понадобиться, чтобы единственное соединение захватывало несколько клиентских запросов. То есть Вы сможете использовать одно соединение на нескольких страницах HTML.

Обычно вы используете свойства объекта client для информации, захватывающего клиентские запросы. Однако значение свойства объекта client не может быть объектом. Исходя из этого, Вы не можете сохранять пул соединений БД в объекте client. Вместо этого Вы используете пул соединений, хранимый в объекте project, обслуживая их так, как описано в данном разделе. Если вы используете этот подход, Вам может понадобиться кодирование пользовательской информации, по соображениям безопасности.

ПРЕДУПРЕЖДЕНИЕ!

Будьте особенно осторожны при использовании такого подхода, поскольку сохранение соединения таким способом делает его недоступным для других пользователей. Если все соединения окажутся недоступны, новые запросы будут ожидать явного освобождения соединения или таймаута соединения. Это особенно проблематично для однопоточных библиотек БД. (Об установлении соединений так, что они будут запрашиваться, если не заняты в течение продолжительного времени, см. "Запрашивание Незанятого Соединения").

 В следующем примере соединение и транзакция захватывают несколько клиентских запросов. Код сохраняет соединение как свойство объекта sharedConns, который сам является свойством объекта project. Объект sharedConns не является предопределённым объектом JavaScript. Он просто создан в данном примере и может иметь другое имя, по Вашему выбору.

Поскольку один пул используется всеми клиентами, Вы должны создавать объект sharedConns и создавать и соединять сам пул на начальной странице приложения примерно таким кодом:

project.sharedConns = new Object();
project.sharedConns.conns = new Object();
project.sharedConns.pool = new DbPool ("SYBASE", "sybaseserver",
   "user", "password", "sybdb", 10, false);

Затем на первой клиентской странице, получающей доступ к пулу, следуйте такой стратегии:

// Генерируется уникальный индекс для обращения к данному клиенту, если он ещё
// не сгенерирован на другой странице.
if client.id == null {
   client.id = ssjs_generateClientID();
}
// Для удобства устанавливается переменная для данного пула.
var clientPool = project.sharedConns.pool;
// Проверяется, соединён ли пул. Если нет, перенаправляется
// на специальную страницу для информирования пользователя.
project.lock();
if (!clientPool.connected()) {
   delete project.sharedConns.pool;
   project.unlock();
   redirect("noconnection.html");
}
project.unlock();
// Соединение получается из пула и сохраняется в объекте project.
project.sharedConns.conns[client.id] = clientPool.connection();
var clientConn = project.sharedConns.conns[client.id];
clientConn.beginTransaction();
cursor = clientConn.cursor("select * from customers", true");
// ... другие операции с БД ...
cursor.close();
}

Заметьте, что эта страница не выполняет откат или подтверждение транзакции. Соединение остаётся открытым, и транзакция продолжается. (Транзакции рассматриваются в разделе "Обслуживание Транзакций").
Вторая HTML-страница запрашивает соединение, базируясь на значении client.id, и продолжает работать с БД так:

// Запрашивается соединение.
var clientConn = project.sharedConns.conns[client.id];
// ... Выполняются ещё какие-нибудь операции с БД ...
// Здесь, если операции с БД успешно прошли, okay устанавливается в 1.
// Если была ошибка при работе с БД, okay устанавливается в 0. В конце
// подтверждается или откатывается транзакция на основе этого значения.
if (okay)
   clientConn.commitTransaction();
else
   clientConn.rollbackTransaction();
// Соединение возвращается в пул.
clientConn.release();
// Избавляемся от значения свойства объекта. Оно Вам больше не нужно.
delete project.sharedConns.conns[client.id];

В этом примере объект sharedConns сохраняет единственный объект DbPool и соединения для данного пула, которые используются в данный момент. Ситуация может быть значительно сложнее. Если у Вас имеется фиксированный набор пулов БД, Вы можете определить отдельный объект для хранения соединений каждого пула.
Если у вас имеется массив пулов и каждому пулу необходимы соединения, захватывающие несколько запросов, Вам необходимо создать массив объектов, каждый из которых сохраняет пул и массив его соединений. Как ответвление, вместо немедленного перенаправления в том случае, если пул не соединён, клиентская страница может сделать новую попытку установить соединение.

Если Вы используете ssjs_generateClientID и храните ID в объекте client, Вам понадобится защита от вторжения и получения доступа к ID и, следовательно, к закрытой информации.

Ожидание Соединения


В пуле, созданном объектом DbPool, соединений имеется фиксированное количество соединений. Если в момент попытки доступа все соединения заняты, Ваше приложение ожидает освобождения соединения в течение специфицированного периода таймаута. Вы можете управлять периодом ожидания.

Предположим, Вы определили следующий пул из 3 соединений:

pool = new DbPool ("ORACLE", "myserv", "user", "password", "", 3);

Предположим далее, что три клиента одновременно получают доступ к приложению и каждый использует одно из трёх соединений. Четвёртый клиент теперь запрашивает соединение через следующий вызов:

myconnection = pool.connection();

Этот клиент обязан ждать, пока один из трёх клиентов не освободит соединение. В данном случае, поскольку вызов connection не специфицирует таймаут, клиент ждёт освобождения соединения неопределённо долго, а затем возвращает это соединение.

Вы можете специфицировать различные периоды таймаута, задавая аргументы метода connection. Второй аргумент метода connection это период таймаута в секундах. Если Вы специфицируете таймаут 0, система ждёт бесконечно долго. Например, следующий код ожидает соединения только 30 секунд перед таймаутом:

myconnection = pool.connection ("Name of Connection", 30);

Если в течение специфицированного периода соединение не освобождается, метод возвращает null, и в сообщение об ошибке устанавливается сообщение о наименьшей ошибке. Вы можете получить это сообщение, вызвав метод minorErrorMessage объекта pool. Если Вы вызываете таймаут из connection, Вам может понадобиться освободить соединение, отключив одно из уже установленных. Дополнительно см. "Запрашивание Свободного Соединения".

Запрашивание Свободного Соединения


Если Ваше приложение запрашивает соединение из объекта DbPool, оно может не получить его. Доступные опции в этот момент зависят от архитектуры Вашего приложения.

Если каждое соединение существует только в период существования отдельного клиентского запроса, недоступность соединений невозможна из-за того, что пользователь оставил приложение в бездействии на значительный период времени. Это может произойти только из-за того, что весь код страницы JavaScript не закончил выполняться. В этом случае Вы не должны пытаться разорвать используемое соединение, чтобы воспользоваться им. Если Вы разорвёте соединение в этот момент, Вы рискуете оставить этот поток выполнения в несоответствующем состоянии. Вместо этого Вы должны удостовериться, что Ваше приложение освобождает каждое соединение по мере завершения его использования. Если Вы не хотите ожидать соединения, Вы должны предоставить пользователю возможность выбора другого варианта.

Если, наоборот, соединение захватывает несколько клиентских запросов, Вы можете захотеть запросить свободные соединения. В этой ситуации соединение может освободиться, поскольку пользователь не завершил транзакцию. Например, предположим, что пользователь отправляет данные на первой странице приложения и эти данные начинают многостраничную транзакцию БД. Вместо отправки данных для продолжения транзакции на следующей странице, пользователь переходит на другой сайт и никогда не вернётся к данному приложению. По умолчанию соединение остаётся открытым и не может использоваться другими клиентами.

Вы можете вручную запросить соединение, зачистив его и освободив в пул БД. Чтобы сделать это, напишите функции типа нижеследующих:

Ваше приложение может использовать эти функции так:

  1. Когда Вы получаете новое соединений, вызывайте Bucket для создания объекта bucket.
  2. На любой странице, получающей доступ к соединению, вызывайте MarkBucket для обновления штампа времени.
  3. Если приложение делает паузу (таймаут), пытаясь получить соединение их пула, вызывайте RetrieveConnection для поиска незанятых соединений, закройте все открытые курсоры, подтвердите или откатите работающие транзакции и верните незанятые соединения обратно в пул.
  4. Если соединение было возвращено в пул, попытайтесь получить соединение из пула.

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

Создание Bucket

Функция bucket содержит соединение и штамп времени. Этот образец конструктора функции принимает соединение в качестве единственного параметра:

// Конструктор для Bucket
function Bucket(c)
{
   this.connection = c;
   this.lastModified = new Date();
}

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

myBucket.openCursor =
   myBucket.connection.cursor("select * from customer", true);
Пометка Объекта Bucket

Функция MarkBucket принимает объект Bucket в качестве параметра и устанавливает в поле lastModified текущее время.

function MarkBucket(bucket)
{
   bucket.lastModified = new Date();
}

Вызывайте MarkBucket на каждой странице приложения, которая использует соединение, содержащееся в bucket. Это восстанавливает в lastModified значение текущей даты и предотвращает появление незанятых соединений.

Запрашивание Старых Соединений

RetrieveConnections сканирует массив объектов Bucket, ищет buckets соединения, штамп времени которых установлен ранее некоторого определённого времени. Если соединение найдено, функция вызывает CleanBucket (описан далее) для возвращения соединения в пул БД.

// Запрашиваются соединения, не занятые в течение специфицированного количества минут.
function RetrieveConnections(BucketArray, timeout)
{
   var i;
   var count = 0;
   var now;
   now = new Date();    // Этот цикл выполняется для каждого bucket в массиве.
   for (i in BucketArray) {
 // Вычисляется разница во времени между текущей/now и последней/last
 // модифицированной датой. Эта разница выражается в миллисекундах.
// Если она больше значения timeout, вызывается функция зачистки.
       if ((now - i.lastModified)/60000) > timeout) {
CleanBucket(i);
// Избавляется от bucket, поскольку он больше не используется.
  delete i;
count = count + 1;
      }
   }  return count;
}
Зачистка Bucket

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

function CleanBucket(bucket)
{
   bucket.openCursor.close();
   bucket.connection.rollbackTransaction();
   bucket.connection.release();
}

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

Направление Их Всех в Пул

Следующий пример кода использует уже определённые функции для запрашивания соединений, к которым не обращались в течение 10 минут. Сначала создаётся совместно используемый массив соединений и пул БД с 5 соединениями:

if ( project.sharedConns == null ) {
   project.sharedConns = new Object();
   project.sharedConns.pool = new DbPool ("ORACLE", "mydb",
      "user", "password", "", 5, false);
   if ( project.sharedConns.pool.connected() ) {
      project.sharedConns.connections = new Object();
   }
else {
      delete project.sharedConns;
   }
}

Теперь используем следующий код для попытки получения соединения. После зачистки пула генерируется клиентский ID, который затем используется как индекс в массиве соединений. Далее пытаемся получить соединение. Если возникает таймаут, вызываем RetrieveConnections для возвращения старого соединения в пул.
Если RetrieveConnections возвращает соединение в пул, пытаемся получить соединение вновь. Если всё ещё не можем получить соединение, выполняется перенаправление на другую страницу с информацией, что свободных соединений нет. Если запрашивается соединение, сохраняем его в новом bucket соединения и сохраняем этот bucket соединения в совместно используемом массиве соединений.

if ( project.sharedConns != null ) {
   var pool = project.sharedConns.pool;
   // Этот код запускается, только если пул уже соединён.
   // Если нет, возможно, Вам нужен код для соединения.
   if ( pool.connected() == true ) {
      // Генерируется клиентский ID.
      client.id = ssjs_generateClientID();
      // Попытка получить соединение.
      var connection = pool.connection("my connection", 30);
      // Если соединение null, тогда ничего не будет доступно       // в течение специфицированного лимита времени. Пытаемся запросить старые соединения.
      if (connection == null) {
         // Запрашиваются соединения, не используемые в течение последних 10 минут.
var count = RetrieveConnections(project.sharedConns.connections, 10);
         // Если count не равен 0, делаем какое-нибудь соединение доступным.
if (count != 0){
connection = pool.connection("my connection", 30);
    // Если connection всё ещё null, отказываемся.
      if (connection == null)
        redirect("nofreeconnections.html");
      }
         else {
            // Отказываемся.
         redirect("nofreeconnections.html");
       }}
      // Если Вы не дошли досюда, Вы получили соединение и можете продолжить работу.
      // Поместите это connection в новый bucket, стартуйте транзакцию,
      // получайте курсор, сохраняйте его в bucket и продолжайте работу.
      project.sharedConns.connections[client.id] =         new Bucket(connection);
 connection.beginTransaction();
project.sharedConns.connections[client.id].cursor =
connection.cursor("select * from customer", true);
      // Помечаем bucket соединения как использованный.
      MarkBucket(project.sharedConns.connections[client.id]);
   // Операторы Базы Данных.
   ...
}

На следующей странице многостраничной транзакции выполняются операции БД по этому соединению. После последней операции БД по соединению помечается bucket соединения:

var Bucket = project.sharedConns.connections[client.id]; if ( Bucket == null) {
   // Повторное соединение.
}
else {    // Взаимодействие с БД.
...
   // Последняя операция БД на странице.
   row = Bucket.cursor.next();
   row.customerid = 666;
 Bucket.openCursor.insertRow("customer");
   // Помечается bucket соединения как использованный на данной странице.
   MarkBucket(Bucket);
}
Оглавление | Назад | Вперёд | Индекс

Дата последнего обновления: 29 сентября 1999 г.

© Copyright ╘ 1999 Sun Microsystems, Inc. Некоторая часть Copyright ╘ 1999 Netscape Communications Corp. Все Права Зарезервированы.