вторник, 17 февраля 2009 г.

Как нужно использовать Hibernate?

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

Стандартный способ использования Hibernate — паттерн Session per View. Согласно этому паттерну использование Hibernate должно выглядеть следующим образом: открывается сессия, получаются все необходимые данные, производятся вычисления, строится представление, сессия закрывается. После закрытия сессии никакие данные, полученные с момента её открытия, более не используются.

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

До тех пор пока вы используете объекты, полученные из Hibernate, вы должны иметь открытой сессию, в которой были получены эти объекты (или к которой были привязаны), и иметь активную транзакцию! Использовать эти объекты можно только в однопоточной среде! Иначе со стороны Hibernate возможны ошибки.

Обычно, создаётся некоторый перехватчик, срабатывающий при получении запроса. Он открывает сессию и делает её доступной для всех классов, методы которых выполняются в данном потоке обработки (что-то типа ThreadLocal).

Может быть, такого способа работы часто бывает достаточно, но, на мой взгляд, такой костюмчик жмёт.

Т.е. объекты, полученные из Hibernate — не совсем обычные POJO. Хоть об этом и уверяют на официальном сайте проекта. В чём же их отличие мы узнаем далее.

Начиная с Hibernate 3, везде, где только можно, выполняется ленивая загрузка, если явно не указано обратное. При помощи ASM'а Hibernate на лету генерирует байт код прокси-объектов и экранирует ими связи запрашиваемых объектов. За счёт этих прокси части объекта подгружаются по мере необходимости.

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

От ограничений Session per View можно избавиться, если в определённый момент иметь гарантию того, что объект загружен полностью со всеми своими связями. Тогда он станет обычным POJO. Его можно будет использовать после закрытия сессии и использовать многопоточно, если сам объект на это рассчитан.

Получить такую гарантию можно, если отключить для всех связей этого объекта ленивую загрузку. Это довольно утомительно, т.к. нет способа отключить ленивую загрузку сразу для всего — придётся отключать её для каждого класса отдельно.

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


public class LoadDragoon {

private SessionFactory sessionFactory;

private static Logger logger = Logger.getLogger(LoadDragoon.class.getName());

public LoadDragoon(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}

public void forceLoad(Object obj) throws HibernateException {
if (obj == null) {
return;
}

Class entityClass = Hibernate.getClass(obj);

if (!Hibernate.isInitialized(obj)) {
logger.finest("Force initialization of " + entityClass.getName());
}

Hibernate.initialize(obj);
ClassMetadata md = sessionFactory.getClassMetadata(entityClass);

if (md == null) {
return;
}

String[] propertyNames = md.getPropertyNames();

for (String propertyName : propertyNames) {
Type propertyType = md.getPropertyType(propertyName);

if (propertyType.isEntityType()) {
Object assoc = md.getPropertyValue(obj, propertyName,
EntityMode.POJO);
forceLoad(assoc);
} else if (propertyType.isCollectionType()) {
Object container = md.getPropertyValue(obj, propertyName,
EntityMode.POJO);
Hibernate.initialize(container);

if (container instanceof Map) {
Map map = (Map) container;

for (Map.Entry entry : map.entrySet()) {
forceLoad(entry.getKey());
forceLoad(entry.getValue());
}
} else {
Collection collection = (Collection) container;

for (Object element : collection) {
forceLoad(element);
}
}
}
}
}

}

1 комментарий:

Kir комментирует...

ээээ, а hibernateTemplate.initialize(entity) не иницилизирует разве весь граф?