Предотвращение запуска бульдозера с лёгкой загрузкой Hibernate
Я использую транзакции Spring, поэтому транзакция все еще активна, когда происходит преобразование POJO в DTO.
Я хотел бы предотвратить Dozer от запуска ленивой загрузки, так что скрытые запросы sql никогда не происходят: все выборки должны выполняться явно через HQL (чтобы получить лучший контроль над показателями).
Я пробовал это до преобразования DTO:
PlatformTransactionManager tm = (PlatformTransactionManager) SingletonFactoryProvider.getSingletonFactory().getSingleton("transactionManager");
tm.commit(tm.getTransaction(new DefaultTransactionDefinition()));
Я не знаю, что происходит с транзакцией, но сеанс Hibernate не закрывается, и ленивая загрузка по-прежнему происходит.
Я пробовал это:
SessionFactory sf = (SessionFactory) SingletonFactoryProvider.getSingletonFactory().getSingleton("sessionFactory");
sf.getCurrentSession().clear();
sf.getCurrentSession().close();
И это предотвращает ленивую загрузку, но хорошо ли манипулировать сеансом непосредственно на прикладном уровне (который называется "фасад" в моем проекте)? Какие побочные эффекты я должен бояться? (Я уже видел, что тесты с использованием конверсий POJO → DTO больше не могут запускаться с помощью тестовых классов AbstractTransactionnalDatasource Spring, поскольку эти классы пытаются вызвать откат транзакции, которая больше не связана с активным сеансом).
Я также пытался установить распространение NOT_SUPPORTED или REQUIRES_NEW, но он повторно использует текущий сеанс Hibernate и не предотвращает ленивую загрузку.
Ответы
Ответ 1
Единственное общее решение, которое я нашел для управления этим (после изучения пользовательских преобразователей, прослушивателей событий и прокси-решетов), является создание настраиваемого поля. Я обнаружил эту функциональность, спрятанную в API-интерфейсе Dozer (я не верю, что она задокументирована в Руководстве пользователя).
Простой пример таков:
public class MyCustomFieldMapper implements CustomFieldMapper
{
public boolean mapField(Object source, Object destination, Object sourceFieldValue, ClassMap classMap, FieldMap fieldMapping)
{
// Check if field is a Hibernate collection proxy
if (!(sourceFieldValue instanceof AbstractPersistentCollection)) {
// Allow dozer to map as normal
return false;
}
// Check if field is already initialized
if (((PersistentSet) sourceFieldValue).wasInitialized()) {
// Allow dozer to map as normal
return false;
}
// Set destination to null, and tell dozer that the field is mapped
destination = null;
return true;
}
}
Это приведет к возврату любых неинициализированных объектов PersistentSet в значение null. Я делаю это так, что, когда они передаются клиенту, я могу различать NULL (незагруженную) коллекцию и пустую коллекцию. Это позволяет мне определить общее поведение в клиенте либо использовать предварительно загруженный набор, либо сделать другой вызов службы для извлечения набора (при необходимости). Кроме того, если вы решите загружать любые коллекции на уровне сервиса, они будут отображаться как обычно.
Я ввожу пользовательский полевой экран с помощью spring:
<bean id="dozerMapper" class="org.dozer.DozerBeanMapper" lazy-init="false">
<property name="mappingFiles">
...
</property>
<property name="customFieldMapper" ref="dozerCustomFieldMapper" />
</bean>
<bean id="dozerCustomFieldMapper" class="my.project.MyCustomFieldMapper" />
Надеюсь, это поможет любому, кто ищет решение для этого, поскольку я не нашел примеров при поиске в Интернете.
Ответ 2
Вариант популярной версии выше, позволяет поймать PersistentBags, PersistentSets, вы назовите его...
public class LazyLoadSensitiveMapper implements CustomFieldMapper {
public boolean mapField(Object source, Object destination, Object sourceFieldValue, ClassMap classMap, FieldMap fieldMapping) {
//if field is initialized, Dozer will continue mapping
// Check if field is derived from Persistent Collection
if (!(sourceFieldValue instanceof AbstractPersistentCollection)) {
// Allow dozer to map as normal
return false;
}
// Check if field is already initialized
if (((AbstractPersistentCollection) sourceFieldValue).wasInitialized()) {
// Allow dozer to map as normal
return false;
}
return true;
}
}
Ответ 3
Я не получил работу над этим (возможно, разные версии). Однако это прекрасно работает
public class HibernateInitializedFieldMapper implements CustomFieldMapper {
public boolean mapField(Object source, Object destination, Object sourceFieldValue, ClassMap classMap, FieldMap fieldMapping) {
//if field is initialized, Dozer will continue mapping
return !Hibernate.isInitialized(sourceFieldValue));
}
}
Ответ 4
Рассматривали ли вы отключение ленивой загрузки вообще?
На самом деле это не похоже на модели, которые вы хотите использовать:
Я хотел бы предотвратить Dozer от запуска ленивой загрузки, так что скрытые запросы sql никогда не происходят: все выборки должны выполняться явно через HQL (чтобы получить лучший контроль над показателями).
Это говорит о том, что вы никогда не захотите использовать ленивую загрузку.
Dozer и поддерживаемый Hibernate beans, который вы передаете, блаженно не знают друг о друге; все Dozer знает, что он обращается к свойствам в bean, а поддерживаемый Hibernate bean отвечает на вызовы get()
ленивой загрузки так же, как если бы вы обращались к этим свойствам самостоятельно.
Любые трюки, позволяющие Dozer знать прокси-серверы Hibernate в вашем beans или наоборот, IMO, сломают слои вашего приложения.
Если вы не хотите, чтобы какие-либо "скрытые SQL-запросы" запускались в неожиданные моменты времени, просто отключите lazy-load.
Ответ 5
Короткая версия этого mapper будет
return sourceFieldValue instanceof AbstractPersistentCollection &&
!( (AbstractPersistentCollection) sourceFieldValue ).wasInitialized();
Ответ 6
Использование CustomFieldMapper не может быть хорошей идеей, поскольку оно будет вызываться для каждого поля вашего исходного класса, но наша проблема - это только ленивое сопоставление ассоциаций (список дочерних объектов), поэтому мы можем установить нулевое значение в getter объекта entity,
public Set<childObject> getChild() {
if(Hibernate.isInitialized(child){
return childObject;
}else
return null;
}