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


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.