Hibernate 프록시를 실제 엔티티 객체로 변환하는 방법


161

최대 절전 모드 Session에서 일부 객체를로드하고 있으며 일부는 지연로드로 인해 프록시로로드됩니다. 괜찮습니다. 게으른 로딩을 끄고 싶지 않습니다.

그러나 나중에 RPC를 통해 일부 객체 (실제로 하나의 객체)를 GWT 클라이언트에 보내야합니다. 그리고이 구체적인 객체는 프록시입니다. 그래서 나는 그것을 실제 물체로 바꿔야합니다. Hibernate에서 "materialize"와 같은 방법을 찾을 수 없습니다.

프록시에서 클래스 및 ID를 알고있는 객체로 일부 객체를 전환하려면 어떻게해야합니까?

현재 내가 볼 수있는 유일한 해결책은 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방법 도 정의합니다 .
Bozho

1
이것을 수행하는 휴대용 (JPA) 방법이 있습니까?
Kawu

왜 Hibernate.initialize가 호출 할 때 lazyInitializeException을 던지는가? 다음과 같이 사용하고 있습니다. Object o = session.get (MyClass.class, id); 다른 객체 = o.getSomeOtherClass (); initializeAndUnproxy (other);
fredcrs

6
util 클래스 없이도 같은 작업을 수행 할 수 있습니다 –(T)Hibernate.unproxy(entity)
panser

47

이 기사 에서 설명했듯이 Hibernate ORM 5.2.10 부터 다음과 같이 할 수 있습니다.

Object unproxiedEntity = Hibernate.unproxy(proxy);

최대 절전 모드 전에 5.2.10 . 가장 간단한 방법 은 Hibernate 내부 구현에서 제공 하는 unproxy 메소드 를 사용하는 PersistenceContext것입니다.

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

부모 엔터티에서 이것을 호출하면 컬렉션 필드를 처리합니까 ?? 예를 들어 Departmentwith with with List 가있는 경우 Student에도 unproxy(department.getStudents()) 그래야 unproxy(department)합니까?
trafalmadorian

1
주어진 프록시 만 초기화됩니다. 루트 엔터티를 프록시 해제하는 경우 많은 데이터를로드 할 수 있으므로 연결에 계단식으로 연결되지 않습니다.
Vlad Mihalcea

그러나 PersistentContext#unproxy(proxy)프록시 동안 초기화되지 않은 경우 예외가 발생 Hibernate.unproxy(proxy)하고 LazyInitializer#getImplementation(proxy), 필요한 경우 프록시를 초기화한다. 이 차이로 인해 예외가 발생했습니다. ;-)
자갈

13

사용하려고 Hibernate.getClass(obj)


15
이것은 해독되지 않은 객체 자체보다는 클래스를 반환합니다.
Stefan Haberl

실제로이 솔루션은 instanceof 비교를 위해 obj 클래스를 찾으려고 할 때 좋습니다.
João Rebelo

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 서비스의 결과 (측면을 통해)에 사용하고 프록시에서 모든 결과 객체를 재귀 적으로 정리합니다 (초기화되지 않은 경우).


이 코드를 공유해 주셔서 감사합니다. 모든 사용 사례를 다루지는 않았지만 실제로는 도움이됩니다 ...
Prateek Singh

옳은. 새로운 경우에 따라 업데이트해야합니다. GWT 사람들이 권장하는 것을 시도 할 수 있습니다. 여기를보십시오 : gwtproject.org/articles/using_gwt_with_hibernate.html (통합 전략 파트 참조). 일반적으로 DTO, Dozer 또는 Gilead를 사용하는 것이 좋습니다. 이것에 대한 의견을 제시하면 좋을 것입니다. . 내 경우에는 내 코드는 간단한 솔루션,하지만 완전하지 = (외모
세르게이 Bondarev

감사. "CollectionsUtils.containsTotallyEqual (handledObjects, value)"에 대한 구현을 어디서 얻을 수 있습니까?
Ilan.K

공개 정적 부울 containsTotallyEqual (Collection <?> collection, Object value) {if (isEmpty (collection)) {return false; } for (객체 객체 : 수집) {if (object == value) {return true; }} false를 리턴합니다. }
Sergey Bondarev

그것은 자신에 의해 단지 유틸리티 메소드입니다
세르게이 Bondarev

10

JPA 2에서 권장하는 방법 :

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

2
답이 내 것과 어떻게 다릅니 까?
Vlad Mihalcea

이 솔루션을 시도했습니다 ... unwrap 명령 전에 다음과 같은 것을 넣지 않으면 항상 작동하지 않습니다. HibernateProxy hibernateProxy = (HibernateProxy) possibleProxyObject; if (hibernateProxy.getHibernateLazyInitializer (). isUninitialized ()) {hibernateProxy.getHibernateLazyInitializer (). initialize (); }
user3227576

2

Spring Data JPA와 Hibernate에서는 하위 인터페이스를 사용했습니다. JpaRepository 사용하여 "join"전략을 사용하여 매핑 된 유형 계층 구조에 속하는 객체를 검색했습니다. 불행히도 쿼리는 예상되는 구체적인 유형의 인스턴스 대신 기본 유형의 프록시를 반환했습니다. 이로 인해 결과를 올바른 유형으로 캐스팅하지 못했습니다. 당신처럼, 나는 내 유물을 프록시하지 않는 효과적인 방법을 찾기 위해 여기에 왔습니다.

Vlad는 이러한 결과를 프록시 해제 할 수있는 올바른 아이디어를 가지고 있습니다. Yannis가 조금 더 자세히 설명합니다. 그들의 대답에 덧붙여, 당신이 찾고있는 나머지 부분은 다음과 같습니다.

다음 코드는 프록시 엔티티를 프록시 해제하는 쉬운 방법을 제공합니다.

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;
    }

}

프록시되지 않은 엔티티 또는 프록시 된 엔티티를 unproxy 메소드에 . 이미 프록시되지 않은 경우 간단히 반환됩니다. 그렇지 않으면 프록시되지 않고 반환됩니다.

도움이 되었기를 바랍니다!


1

다른 해결 방법은 전화하는 것입니다

Hibernate.initialize(extractedObject.getSubojbectToUnproxy());

세션을 닫기 직전에


1

표준 Java 및 JPA API를 사용하여 클래스를 프록시 해제하는 솔루션을 찾았습니다. 최대 절전 모드로 테스트되었지만 종속성으로 최대 절전 모드가 필요하지 않으며 모든 JPA 공급자와 함께 작동해야합니다.

하나의 요구 사항-부모 클래스 (주소)를 수정하고 간단한 도우미 메소드를 추가해야합니다.

일반적인 아이디어 : 자신을 반환하는 부모 클래스에 도우미 메서드를 추가하십시오. 메소드가 프록시에서 호출되면 호출을 실제 인스턴스로 전달하고이 실제 인스턴스를 리턴합니다.

최대 절전 모드는 프록시 클래스가 자체 인스턴스를 반환하고 여전히 실제 인스턴스 대신 프록시를 반환한다는 것을 인식하므로 구현은 조금 더 복잡합니다. 해결 방법은 반환 된 인스턴스를 실제 래퍼 클래스와 다른 클래스 유형을 갖는 단순 랩퍼 클래스로 랩핑하는 것입니다.

코드에서 :

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


0

제안 된 솔루션에 감사드립니다! 불행히도 필자의 경우에는 네이티브 쿼리를 사용하여 JPA-Hibernate를 통해 Oracle 데이터베이스에서 CLOB 객체 목록을 수신하지 못했습니다.

제안 된 모든 접근 방식은 ClassCastException을 제공하거나 방금 Java 프록시 객체를 반환했습니다 (내부에는 원하는 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();
}

이것이 누군가를 도울 수 있기를 바랍니다!

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.