Спящий Режим: Отложенная Загрузка, Наследование И Экземпляр

Рассмотрим в качестве примера следующую ситуацию.

У нас есть класс User с полями, описывающими пользователя.

Существует класс Phone, который является родительским для классов CellPhone и SatellitePhone. Класс User имеет поле, содержащее список телефонов пользователей.

Чтобы снизить нагрузку на базу данных, мы сделали этот список «ленивым».

Он будет загружаться только по требованию.

Все это выглядит примерно так

  
  
   

public class User { .

@OneToMany(fetch = FetchType.LAZY) private List<Phone> phones = new ArrayList<Phone>(); public List<Phone> getPhones() { return phones; } } public class Phone { .

} public class CellPhone extends Phone { .

} public class SatellitePhone extends Phone { .

}

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

В большинстве ситуаций нам не важно, с чем именно мы работаем (реальный объект или его прокси).

При запросе любого поля любого объекта прокси-объект будет автоматически инициализирован и мы получим ожидаемые данные.

Но если нам нужно узнать тип объекта, то все идет наперекосяк.

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

Соответственно, создается список, содержащий прокси-объекты, унаследованные от Phone.

Спящий режим: отложенная загрузка, наследование и экземпляр

Когда наша команда впервые столкнулась с этой проблемой, мы немного изучили этот вопрос и поняли, что придется делать «костыль».

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

Прямо перед этой проверкой мы реализовали еще одну: если объект является прокси-объектом, то он инициализируется.

После чего эта неприятная история была благополучно забыта.

Со временем проект разросся, а бизнес-логика усложнилась.

И вот настал момент, когда подобных костылей стало уже слишком много (мы поняли, что на третьем-четвертом костыле все будет не так).

Причем эта проблема стала возникать не только при запросе ленивого списка других объектов у одного объекта, но и при прямом запросе списка объектов из базы данных.

Очень не хотелось отказываться от ленивой загрузки, потому что.

Наша база данных и так сильно загружена.

Мы решили больше не смешивать архитектурные слои приложения и создать что-то более универсальное.



Спящий режим: отложенная загрузка, наследование и экземпляр

Наша схема применения В этой схеме запросы к базе данных обрабатываются уровнем DAO. Он состоит из 1 абстрактного класса JpaDao, который определяет все основные методы работы с базой данных.

И множество классов — его потомки, каждый из которых в конечном итоге использует методы базового класса.

Итак, как мы преодолели проблему с прямым запросом списка объектов разных типов с общим родителем? Мы создали методы в классе JpaDao для инициализации одного прокси-объекта и инициализации списка прокси-объектов.

При каждом запросе списка объектов из базы этот список подвергается инициализации (Мы сознательно пошли на этот шаг, поскольку если мы запрашиваем список объектов в нашем приложении, то нам практически всегда нужна его полная инициализация).

Пример реализации JpaDao

public abstract class JpaDao<ENTITY extends BaseEntity> { .

private ENTITY unproxy(ENTITY entity) { if (entity != null) { if (entity instanceof HibernateProxy) { Hibernate.initialize(entity); entity = (ENTITY) ((HibernateProxy) entity).

getHibernateLazyInitializer().

getImplementation(); } } return entity; } private List<ENTITY> unproxy(List<ENTITY> entities) { boolean hasProxy = false; for (ENTITY entity : entities) { if (entity instanceof HibernateProxy) { hasProxy = true; break; } } if (hasProxy) { List<ENTITY> unproxiedEntities = new LinkedList<ENTITY>(); for (ENTITY entity : entities) { unproxiedEntities.add(unproxy(entity)); } return unproxiedEntities; } return entities; } .

public List<ENTITY> findAll() { return unproxy(getEntityManager().

createQuery("from " + entityClass.getName(), entityClass).

getResultList()); } .

}

Решение первой проблемы прошло не так гладко.

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

Во всех объектах, которые содержат ленивые списки объектов разных типов с одним и тем же родителем (например, «Пользователь со списком телефонов»), мы переопределяем геттеры для этих списков.

Пока списки не запрашиваются, все нормально.

Объект содержит только список прокси, и ненужные запросы не выполняются.

Когда список запрашивается, он инициализируется.

Пример реализации геттера для списка телефонов пользователя

public class User { .

@OneToMany(fetch = FetchType.LAZY) private List<Phone> phones = new ArrayList<Phone>(); public List<Phone> getPhones() { return ConverterUtil.unproxyList(phones); } } public class ConverterUtil { .

public static <T> T unproxy(T entity) { if (entity == null) { return null; } Hibernate.initialize(entity); if (entity instanceof HibernateProxy) { entity = (T) ((HibernateProxy) entity).

getHibernateLazyInitializer().

getImplementation(); } return entity; } public static <T> List<T> unproxyList(List<T> list) { boolean hasProxy = false; for (T entity : list) { if (entity instanceof HibernateProxy) { hasProxy = true; break; } } if (hasProxy) { LinkedList<T> result = new LinkedList<T>(); for (T entity : list) { if (entity instanceof HibernateProxy) { result.add(ConverterUtil.unproxy(entity)); } else { result.add(entity); } } list.clear(); list.addAll(result); } return list; } }

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

Надеюсь, этот пример поможет кому-то в похожей ситуации.

Если вы знаете более оптимальный/красивый способ решения этой проблемы, буду рад добавить его в статью.

Теги: #java #hibernate #OOP #inheritance #instanceof #слои абстракций #java

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.