Як перетворити сплячий проксі в реальний об'єкт сутності


161

Під час сплячого режиму Sessionя завантажую деякі об'єкти, а деякі з них завантажуються як проксі, завдяки ледачому навантаженню. Все гаразд, і я не хочу відключати ледачу завантаження.

Але пізніше мені потрібно надіслати деякі об’єкти (фактично один об’єкт) клієнту GWT через RPC. І трапляється, що цей конкретний об’єкт є проксі. Тому мені потрібно перетворити його на реальний об’єкт. Я не можу знайти такий спосіб, як «матеріалізуватися» в режимі сплячки.

Як я можу перетворити деякі об’єкти з проксі-серверів на реальні, знаючи їх клас та ідентифікатор?

Наразі єдине рішення, яке я бачу, - це виселити цей об’єкт із кешу Hibernate та перезавантажити його, але це дійсно погано з багатьох причин.

Відповіді:


232

Ось метод, який я використовую.

public static <T> T initializeAndUnproxy(T entity) {
    if (entity == null) {
        throw new 
           NullPointerException("Entity passed for initialization is null");
    }

    Hibernate.initialize(entity);
    if (entity instanceof HibernateProxy) {
        entity = (T) ((HibernateProxy) entity).getHibernateLazyInitializer()
                .getImplementation();
    }
    return entity;
}

1
Я хотів зробити те ж саме, тому я написав проксі-екземпляр до ObjectOutputStream, а потім прочитав його з відповідної ObjectInputStream, і це, здавалося, зробило трюк. Я не впевнений, чи це ефективний підхід, але все ще цікаво, чому він працював ... будь-які коментарі до цього будуть дуже вдячні. Дякую!
— shrini1000

@ shrini1000 він працював, тому що при серіалізації ініціалізується колекція (якщо сеанс ще не закритий). Також HibernateProxyвизначається writeReplaceметод змусити виконавців зробити щось особливе під час серіалізації.
— Божо

1
Чи є портативний (JPA) спосіб це зробити?
— Каву

Чому, Hibernate.initialize кидаючи lazyInitializeException, коли я його називаю? Я просто використовую: Object o = session.get (MyClass.class, id); Об'єкт other = o.getSomeOtherClass (); ініціалізуватиAndUnproxy (інші);
— fredcrs

6
ви можете зробити те ж саме без власного класу util -(T)Hibernate.unproxy(entity)
— panser

47

Як я пояснив у цій статті , оскільки сплячий ORM 5.2.10 , ви можете це зробити так:

Object unproxiedEntity = Hibernate.unproxy(proxy);

До сплячки 5.2.10 . найпростіший спосіб зробити це - використовувати метод unproxy, запропонований внутрішньою системою Hibernate PersistenceContext:

Object unproxiedEntity = ((SessionImplementor) session)
                         .getPersistenceContext()
                         .unproxy(proxy);

Чи обробляє це виклик на батьківській сутності, обробляючи поля колекції ?? наприклад, якщо у вас є " DepartmentСписок" Student, вам все-таки потрібно unproxy(department.getStudents()) - чи достатньо просто unproxy(department)?
— trafalmadorian

1
Ініціалізується лише даний проксі. Це не надає асоціацій, оскільки це може потенційно завантажувати тонни даних, якщо трапиться непроксифікувати кореневу сутність.
— Влад

Однак PersistentContext#unproxy(proxy)кидає виняток , якщо проксі - сервер не инициализирован в той час як Hibernate.unproxy(proxy)і LazyInitializer#getImplementation(proxy)форматувати проксі - сервер , якщо це необхідно. Щойно спіймали виняток через цю різницю. ;-)
— bgraves

13

Спробуйте використовувати Hibernate.getClass(obj)


15
Це повертає клас, а не сам депроксифікований об’єкт
— Стефан Хаберл

Насправді це рішення є чудовим, коли ми намагаємось знайти Клас obj, наприклад, для порівнянь.
— Жоао Ребело

13

Я написав наступний код, який очищає об'єкт від проксі-серверів (якщо вони ще не ініціалізовані)

public class PersistenceUtils {

    private static void cleanFromProxies(Object value, List<Object> handledObjects) {
        if ((value != null) && (!isProxy(value)) && !containsTotallyEqual(handledObjects, value)) {
            handledObjects.add(value);
            if (value instanceof Iterable) {
                for (Object item : (Iterable<?>) value) {
                    cleanFromProxies(item, handledObjects);
                }
            } else if (value.getClass().isArray()) {
                for (Object item : (Object[]) value) {
                    cleanFromProxies(item, handledObjects);
                }
            }
            BeanInfo beanInfo = null;
            try {
                beanInfo = Introspector.getBeanInfo(value.getClass());
            } catch (IntrospectionException e) {
                // LOGGER.warn(e.getMessage(), e);
            }
            if (beanInfo != null) {
                for (PropertyDescriptor property : beanInfo.getPropertyDescriptors()) {
                    try {
                        if ((property.getWriteMethod() != null) && (property.getReadMethod() != null)) {
                            Object fieldValue = property.getReadMethod().invoke(value);
                            if (isProxy(fieldValue)) {
                                fieldValue = unproxyObject(fieldValue);
                                property.getWriteMethod().invoke(value, fieldValue);
                            }
                            cleanFromProxies(fieldValue, handledObjects);
                        }
                    } catch (Exception e) {
                        // LOGGER.warn(e.getMessage(), e);
                    }
                }
            }
        }
    }

    public static <T> T cleanFromProxies(T value) {
        T result = unproxyObject(value);
        cleanFromProxies(result, new ArrayList<Object>());
        return result;
    }

    private static boolean containsTotallyEqual(Collection<?> collection, Object value) {
        if (CollectionUtils.isEmpty(collection)) {
            return false;
        }
        for (Object object : collection) {
            if (object == value) {
                return true;
            }
        }
        return false;
    }

    public static boolean isProxy(Object value) {
        if (value == null) {
            return false;
        }
        if ((value instanceof HibernateProxy) || (value instanceof PersistentCollection)) {
            return true;
        }
        return false;
    }

    private static Object unproxyHibernateProxy(HibernateProxy hibernateProxy) {
        Object result = hibernateProxy.writeReplace();
        if (!(result instanceof SerializableProxy)) {
            return result;
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    private static <T> T unproxyObject(T object) {
        if (isProxy(object)) {
            if (object instanceof PersistentCollection) {
                PersistentCollection persistentCollection = (PersistentCollection) object;
                return (T) unproxyPersistentCollection(persistentCollection);
            } else if (object instanceof HibernateProxy) {
                HibernateProxy hibernateProxy = (HibernateProxy) object;
                return (T) unproxyHibernateProxy(hibernateProxy);
            } else {
                return null;
            }
        }
        return object;
    }

    private static Object unproxyPersistentCollection(PersistentCollection persistentCollection) {
        if (persistentCollection instanceof PersistentSet) {
            return unproxyPersistentSet((Map<?, ?>) persistentCollection.getStoredSnapshot());
        }
        return persistentCollection.getStoredSnapshot();
    }

    private static <T> Set<T> unproxyPersistentSet(Map<T, ?> persistenceSet) {
        return new LinkedHashSet<T>(persistenceSet.keySet());
    }

}

Я використовую цю функцію в результаті моїх послуг RPC (через аспекти), і вона очищає рекурсивно всі об'єкти результату від проксі-серверів (якщо вони не ініціалізовані).


дякую за те, що поділився цим кодом, хоча він не охопив усіх випадків використання випадків, але він справді
— корисний

Правильно. Його слід оновлювати відповідно до нових випадків. Ви можете спробувати речі, рекомендовані хлопцями GWT. Дивіться тут: gwtproject.org/articles/using_gwt_with_hibernate.html (див. Частину стратегій інтеграції). Як правило, вони рекомендують використовувати DTO або Dozer або Gilead. Буде добре, якщо ви дасте свою думку з цього приводу. У моєму випадку, схоже, мій код є найпростішим рішенням, але не повним = (.
— Сергій Бондарев,

Дякую. де ми можемо отримати реалізацію для "CollectionsUtils.containsTotallyEqual (handledObjects, value)"?
— Ілан.К

public static boolean міститьTotallyEqual (колекція <?> колекція, значення об'єкта) {if (isEmpty (collection)) {return false; } for (Object object: collection) {if (object == value) {return true; }} повернути помилкове; }
— Сергій Бондарев

Це просто корисний метод, створений мною
— Сергій Бондарев,

10

Я рекомендую JPA 2:

Object unproxied  = entityManager.unwrap(SessionImplementor.class).getPersistenceContext().unproxy(proxy);

2
Чим ваша відповідь відрізняється від моєї?
— Влад Михальча

Я намагався це рішення ... працює не завжди, якщо ви не ставите щось подібне перед командою unrap: HibernateProxy hibernateProxy = (HibernateProxy) possibleProxyObject; if (hibernateProxy.getHibernateLazyInitializer (). isUninitialized ()) {hibernateProxy.getHibernateLazyInitializer (). ініціалізувати (); }
— користувач3227576

2

Завдяки Spring Data JPA та Hibernate я використовував підінтерфейси JpaRepositoryдля пошуку об’єктів, що належать до ієрархії типів, яка була відображена за допомогою стратегії "приєднатися". На жаль, запити повертали проксі-сервери базового типу замість екземплярів очікуваних типів конкретних. Це заважало мені підводити результати до правильних типів. Як і ви, я прийшов сюди, шукаючи ефективного способу позбавити своїх людей від непротезованого стану.

Влад має правильну ідею для зняття цих результатів; Яніс надає трохи більше деталей. Додавши до своїх відповідей, ось решта того, що ви можете шукати:

Наведений нижче код пропонує простий спосіб зняти проксі з вашого проксі-сервісу:

import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionImplementor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaContext;
import org.springframework.stereotype.Component;

@Component
public final class JpaHibernateUtil {

    private static JpaContext jpaContext;

    @Autowired
    JpaHibernateUtil(JpaContext jpaContext) {
        JpaHibernateUtil.jpaContext = jpaContext;
    }

    public static <Type> Type unproxy(Type proxied, Class<Type> type) {
        PersistenceContext persistenceContext =
            jpaContext
            .getEntityManagerByManagedType(type)
            .unwrap(SessionImplementor.class)
            .getPersistenceContext();
        Type unproxied = (Type) persistenceContext.unproxyAndReassociate(proxied);
        return unproxied;
    }

}

Ви можете передавати методу або непроксірованних entites, або proxied об'єктів unproxy. Якщо вони вже не забруднені, їх просто повернуть. В іншому випадку вони будуть заблоковані та повернуті.

Сподіваюся, це допомагає!


1

Ще одне вирішення питання - зателефонувати

Hibernate.initialize(extractedObject.getSubojbectToUnproxy());

Незадовго до закриття сесії.


1

Я знайшов рішення для депроксирування класу за допомогою стандартних API Java та JPA. Тестується на сплячому режимі, але не потребує сплячого режиму як залежності, і він повинен працювати з усіма постачальниками JPA.

Одне вимога - його необхідно змінити батьківський клас (Адреса) та додати простий метод помічника.

Загальна ідея: додати допоміжний метод до батьківського класу, який повертається сам. коли метод викликається на проксі, він пересилатиме виклик у реальний екземпляр і повертає цей реальний екземпляр.

Реалізація трохи складніша, оскільки сплячий визнає, що клас proxied повертається сам і все ще повертає проксі замість реального примірника. Обхід полягає в тому, щоб перетворити екземпляр, що повернувся, у простий клас обгортки, який має інший тип класу, ніж реальний екземпляр.

У коді:

class Address {
   public AddressWrapper getWrappedSelf() {
       return new AddressWrapper(this);
   }
...
}

class AddressWrapper {
    private Address wrappedAddress;
...
}

Щоб привласнити проксі-сервер до реального підкласу, використовуйте наступне:

Address address = dao.getSomeAddress(...);
Address deproxiedAddress = address.getWrappedSelf().getWrappedAddress();
if (deproxiedAddress instanceof WorkAddress) {
WorkAddress workAddress = (WorkAddress)deproxiedAddress;
}

Ваш приклад код здається трохи незрозумілим (а може, мені просто потрібно більше кави). Звідки береться EntityWrapper? це повинен бути AddressWrapper? І я здогадуюсь, що AddressWrapped повинен сказати AddressWrapper? Ви можете уточнити це?
— Гас

@Gus, ти маєш рацію. Я виправив приклад. Дякую :)
— OndroMih

1

Починаючи з Hiebrnate 5.2.10, ви можете використовувати метод Hibernate.proxy для перетворення проксі-сервера в реальну сутність:

MyEntity myEntity = (MyEntity) Hibernate.unproxy( proxyMyEntity );

0

Дякую за запропоновані рішення! На жаль, жоден з них не працював у моєму випадку: отримання списку об’єктів CLOB з бази даних Oracle через JPA - Hibernate, використовуючи нативний запит.

Усі запропоновані підходи дали мені або ClassCastException, або щойно повернув об'єкт java Proxy (який глибоко всередині містив потрібний Clob).

Тож моє рішення полягає в наступному (на основі декількох вище підходів):

Query sqlQuery = manager.createNativeQuery(queryStr);
List resultList = sqlQuery.getResultList();
for ( Object resultProxy : resultList ) {
    String unproxiedClob = unproxyClob(resultProxy);
    if ( unproxiedClob != null ) {
       resultCollection.add(unproxiedClob);
    }
}

private String unproxyClob(Object proxy) {
    try {
        BeanInfo beanInfo = Introspector.getBeanInfo(proxy.getClass());
        for (PropertyDescriptor property : beanInfo.getPropertyDescriptors()) {
            Method readMethod = property.getReadMethod();
            if ( readMethod.getName().contains("getWrappedClob") ) {
                Object result = readMethod.invoke(proxy);
                return clobToString((Clob) result);
            }
        }
    }
    catch (InvocationTargetException | IntrospectionException | IllegalAccessException | SQLException | IOException e) {
        LOG.error("Unable to unproxy CLOB value.", e);
    }
    return null;
}

private String clobToString(Clob data) throws SQLException, IOException {
    StringBuilder sb = new StringBuilder();
    Reader reader = data.getCharacterStream();
    BufferedReader br = new BufferedReader(reader);

    String line;
    while( null != (line = br.readLine()) ) {
        sb.append(line);
    }
    br.close();

    return sb.toString();
}

Сподіваюся, це допоможе комусь!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.