Разбираемся с разрывом соединения с базой данных и настраиваем соединение с базой данных через пул.
Конфигурация
Liferay Portal 6.1
Apache Tomcat 7
Oracle 11g
Portlet - портлет, обеспечиваюший отображение данных из БД
Взаимодействие между приложением (Liferay Portal и Portlet) и базой данных настроено через пул соединений.
Настройки пула взяты из инструкций по установки портала:
Во время разработки никаких трудностей и ошибок в доступе к данным обнаружено не было.
Но после запуска нашей связки в боевом режиме временами в логе стали появляться сообщения следующего характера:
ERROR: getRequestTypeList(102)... rTypeList = []
08:11:03,628 ERROR [JDBCExceptionReporter:75] ORA-02396: exceeded maximum idle time, please connect again
08:11:03,630 ERROR [JDBCTransaction:109] Could not toggle autocommit java.sql.SQLRecoverableException: Closed Connection
08:11:03,630 ERROR [JDBCTransaction:109] JDBC rollback failed java.sql.SQLException: ORA-01012: not logged on
08:11:03,631 ERROR [JDBCExceptionReporter:75] Already closed.
08:11:03,632 ERROR [JDBCEлxceptionReporter:75] Already closed.
08:11:03,632 ERROR [TransactionInterceptor:121] Application exception overridden by rollback exception org.springframework.transaction.TransactionSystemException: Could not roll back Hibernate transaction; nested exception is org.hibernate.TransactionException: JDBC rollback failed
...
Caused by: java.sql.SQLException: ORA-01012: not logged on
Визуально для конечного пользователя сайта это выглядело следующим образом:
При серфинге по страницам портала, кроме нашего портлета, все страницы отображались без ошибок
При обращении к нашему портлету выводилось сообщение об отсутствии данных, но при перезагрузке страницы данные отображались корректно.
Теория
Одной из самых дорогих операций при работе с БД является установка соединения с БД. Пул соединений предназначен для минимазации затрат на установку подключения. Он реализует это путем хранения набора заранее установленных соединений. При инициализации устанавливаются соединения и помещаются в пуд в состоянии ожидания. При обращении приложения ему отдается соединение. После закрытия соединения приложением, соединение возвращется обратно в пул в состояние ожидания.
Рассуждения
А что будет в случае если соединение, хранящееся в пуле, будет закрыто на уровне БД? Узнает ли пул об этом? И не получит ли приложение закрытое соединение под видом рабочего?
Действительно. В наших настройках пула по-умолчанию пул не производит контроль состояния своих соединений. И получив такое "плохое" соединение, приложение столкнется с исключением типа "ORA-02396: exceeded maximum idle time, please connect again" при попытке выполнения запроса.
Отсюда следует, что для избежания подобных конфузов необходимо либо:
заставить приложение самостоятельно проводить проверку соединений и запрашивать новое в случае провала. Как это реализовано в LIferay Portal в отличие от самописного портлета!
заставить пул мониторить и поддерживать работоспособное состояние соединений и выдавать стопроцентно рабочие соединения. Данный вариант предпочтительнее, так как не требует доп. программирования и исключает ситуацию с потерей времени на установку соединения в момент запроса приложением.
Решение
Настраиваем пул соединений в контексте портлета и в контексте LIferay Portal: META-INF/context.xml и ROOT.xml
factory="org.apache.tomcat.jdbc.pool.DataSourceFactory" - указание на реализацию пула
validationQuery="select 1 from dual" - запрос который будет запускаться для проверки работоспособности соединения
testWhileIdle="true" - включаем проверку состояния, интервал устанавливается параметром timeBetweenEvictionRunsMillis и равен 5 секундам по умолчанию http://people.apache.org/~fhanik/jdbc-pool/jdbc-pool.html - описание всех возможных параметров
Теперь каждые 5 секунд пул будет посылать на исполнение запрос "select 1 from dual". Тем самым соединение будет поддерживаться в рабочем состоянии, а в случае потери работоспособности будет удалено из пула.
P.S. Да, забыл упомянуть, что по умолчанию в СУБД Oracle соединения не умирают за сроком давности. Но в нашем случае было установлена настройка
ALTER SYSTEM SET RESOURCE_LIMIT=TRUE SCOPE=BOTH;
ALTER PROFILE "DEFAULT" LIMIT IDLE_TIME 15;
Проверить установлены ли данные настройки можно следующими запросами:
SHOW PARAMETER RESOURCE_LIMIT;
SELECT PROFILE, RESOURCE_NAME, LIMIT FROM DBA_PROFILES
WHERE PROFILE='default' AND RESOURCE_NAME ='idle_time';