Не позволяйте Dozer запускать ленивую загрузку Hibernate

Я использую транзакции Spring, поэтому транзакция все еще активна, когда происходит преобразование POJO в DTO.

Я бы хотел, чтобы Dozer не запускал ленивую загрузку, чтобы никогда не возникали скрытые запросы sql: все выборки должны выполняться явно через HQL (чтобы получить лучший контроль над производительностью).

  1. Это хорошая практика (я нигде не могу найти документацию)?

  2. Как это сделать безопасно?

Я пробовал это перед преобразованием 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 и не предотвращает ленивую загрузку.


person Tristan    schedule 05.04.2011    source источник


Ответы (6)


Единственное универсальное решение, которое я нашел для управления этим (после изучения пользовательских преобразователей, прослушивателей событий и прокси-преобразователей), — это реализация настраиваемого сопоставления полей. Я обнаружил, что эта функция спрятана в Dozer API (я не верю, что она задокументирована в Руководстве пользователя).

Простой пример выглядит следующим образом;

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 (((AbstractPersistentCollection) 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" />

Я надеюсь, что это поможет любому, кто ищет решение для этого, так как я не смог найти никаких примеров при поиске в Интернете.

person JamieB    schedule 11.05.2011
comment
Спасибо, это здорово, я даже могу подтвердить, что это не задокументировано нигде, кроме как здесь: google .fr/search?q=CustomFieldMapper+PersistentSet - person Tristan; 12.05.2011
comment
Кроме того, в последней версии Dozer (5.3.0) есть другой способ сделать это (sourceforge.net/tracker/) - person Tristan; 12.05.2011

Разновидность популярной версии, приведенной выше, обязательно захватывает как PersistentBag, так и 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;
}

}

person Alex Rose    schedule 11.01.2013

У меня не получилось все вышеперечисленное (вероятно, разные версии). Однако это отлично работает

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));
    }
}
person user959107    schedule 23.09.2011

Вы не думали вообще отключить ленивую загрузку?

На самом деле это не похоже на шаблоны, которые вы заявляете, что хотели бы использовать:

Я бы хотел, чтобы Dozer не запускал ленивую загрузку, чтобы никогда не возникали скрытые запросы sql: все выборки должны выполняться явно через HQL (чтобы получить лучший контроль над производительностью).

Это говорит о том, что вы никогда не захотите использовать ленивую загрузку.

Dozer и bean-компоненты с поддержкой Hibernate, которые вы ему передаете, в блаженном неведении друг о друге; все, что знает Dozer, это то, что он обращается к свойствам в bean-компоненте, а bean-компонент с поддержкой Hibernate отвечает на вызовы get() лениво загруженной коллекции так же, как если бы вы обращались к этим свойствам самостоятельно.

Любые трюки, позволяющие Dozer узнать о прокси-серверах Hibernate в ваших bean-компонентах или наоборот, IMO, разрушат слои вашего приложения.

Если вы не хотите, чтобы какие-либо «скрытые SQL-запросы» запускались в неожиданное время, просто отключите отложенную загрузку.

person matt b    schedule 05.04.2011
comment
Конечно, я хотел бы отключить ленивую загрузку, если это возможно, но как это сделать? Я имею в виду, что default-lazy=false означает, что все мои ассоциации будут жадно выбраны, не так ли? - person Tristan; 05.04.2011
comment
Да, или вы можете указать lazy="false" в классе или свойстве, и да, это приведет к нетерпеливому выбору. Вы должны иметь либо жадную выборку, либо ленивую загрузку; вы не можете сопоставить свойство/коллекцию с помощью Hibernate и загружать его с помощью Hibernate только время от времени. - person matt b; 05.04.2011
comment
Хорошо, я бы хотел, чтобы Hibernate никогда не загружал мои коллекции автоматически при вызове геттера. Hibernate загружает мои коллекции только тогда, когда я указываю это с явным соединением в запросе HQL (например, из заказов на выборку присоединения к человеку). Можете ли вы подтвердить, что такое поведение невозможно с Hibernate? (и есть ли какая-то причина...? или другие популярные фреймворки, которые соответствовали бы этому поведению?) - person Tristan; 05.04.2011
comment
Философия Hibernate заключается в том, чтобы вернуть вам ваш объект, когда вы его запросите, включая все коллекции (возможно, лениво загруженные), ассоциации и т. д. То, что вы описываете, немного ближе к уровню SQL, где вы можете иметь некоторый контроль над тем, что получается, а что нет. Возможно, вам лучше поискать другие библиотеки доступа к данным, такие как iBatis SqlMaps, которые позволяют отображать результаты запросов (собственной спецификации) к объектам - person matt b; 05.04.2011
comment
Извините, Мэтт, был лучший ответ (см. Там). - person Tristan; 12.05.2011

Короткая версия этого сопоставителя будет

return sourceFieldValue instanceof AbstractPersistentCollection && 
!( (AbstractPersistentCollection) sourceFieldValue ).wasInitialized();
person asm0dey    schedule 22.03.2013
comment
Я не уверен, как рефакторинг кода, чтобы сделать его менее читаемым, действительно повышает ценность этого ответа. - person JamieB; 29.05.2014

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

public Set<childObject> getChild() {
if(Hibernate.isInitialized(child){
    return childObject;
}else
 return null;
}
person Prateek Singh    schedule 03.11.2014
comment
Это эффективно отключает ленивую загрузку для всех применимых коллекций, а не только для сопоставления VO. Если бы у вас была какая-либо внутренняя логика приложения, требующая ленивой загрузки этой коллекции, она просто получила бы значение null. Этот вопрос касается именно отключения ленивой загрузки для сопоставления VO, а не вообще. - person JamieB; 20.06.2017