Cách chuyển đổi proxy Hibernate thành đối tượng thực thể thực


161

Trong thời gian ngủ đông Session, tôi đang tải một số đối tượng và một số trong số chúng được tải dưới dạng proxy do lười tải. Tất cả đều ổn và tôi không muốn tắt tải.

Nhưng sau này tôi cần gửi một số đối tượng (thực tế là một đối tượng) đến máy khách GWT thông qua RPC. Và nó xảy ra rằng đối tượng cụ thể này là một proxy. Vì vậy, tôi cần phải biến nó thành một đối tượng thực sự. Tôi không thể tìm thấy một phương pháp như "cụ thể hóa" trong Hibernate.

Làm thế nào tôi có thể biến một số đối tượng từ proxy thành thực tế biết lớp và ID của chúng?

Hiện tại, giải pháp duy nhất tôi thấy là đuổi đối tượng đó khỏi bộ nhớ cache của Hibernate và tải lại, nhưng nó thực sự tồi tệ vì nhiều lý do.

Câu trả lời:


232

Đây là một phương pháp tôi đang sử dụng.

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
Tôi muốn làm điều tương tự, vì vậy tôi đã viết ví dụ ủy quyền cho ObjectOutputStream và sau đó đọc lại từ ObjectInputStream tương ứng, và điều đó dường như thực hiện thủ thuật. Tôi không chắc liệu đó có phải là một cách tiếp cận hiệu quả hay không, nhưng vẫn tự hỏi tại sao nó hoạt động ... mọi bình luận về nó sẽ được đánh giá rất cao. Cảm ơn!
shrini1000

@ shrini1000 nó hoạt động vì khi tuần tự hóa khởi tạo bộ sưu tập (nếu phiên chưa đóng). Cũng HibernateProxyđịnh nghĩa một writeReplacephương thức để buộc người thực hiện phải làm một cái gì đó đặc biệt trong quá trình tuần tự hóa.
Bozho

1
Có một cách di động (JPA) để làm điều này?
Kawu

Tại sao, Hibernate.initialize ném lazyInitializeException khi tôi gọi nó? Tôi chỉ sử dụng như: Object o = session.get (MyClass. Class, id); Đối tượng khác = o.getSomeOtherClass (); khởi tạoAndUnproxy (khác);
fredcrs

6
bạn có thể làm tương tự mà không cần lớp tiện dụng của riêng bạn -(T)Hibernate.unproxy(entity)
panser

46

Như tôi đã giải thích trong bài viết này , kể từ Hibernate ORM 5.2.10 , bạn có thể làm điều đó như thế này:

Object unproxiedEntity = Hibernate.unproxy(proxy);

Trước khi ngủ đông 5.2.10 . cách đơn giản nhất để làm điều đó là sử dụng phương thức unproxy được cung cấp bởi PersistenceContexttriển khai nội bộ Hibernate :

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

Có gọi điều này trên một thực thể cha mẹ xử lý các trường bộ sưu tập ?? ví dụ: nếu bạn có DepartmentDanh sách Student, bạn vẫn cần unproxy(department.getStudents()) - hoặc nó chỉ đủ để làm unproxy(department)?
trafalmadorian

1
Chỉ Proxy đã cho được khởi tạo. Nó không xếp tầng cho các hiệp hội, vì điều đó có khả năng tải hàng tấn dữ liệu nếu bạn tình cờ hủy một thực thể gốc.
Vlad Mihalcea

Tuy nhiên, PersistentContext#unproxy(proxy)ném một ngoại lệ nếu proxy không được khởi tạo trong khi Hibernate.unproxy(proxy)LazyInitializer#getImplementation(proxy)khởi tạo proxy nếu cần thiết. Chỉ cần bắt một ngoại lệ do sự khác biệt này. ;-)
tăng

13

Thử sử dụng Hibernate.getClass(obj)


15
Điều này trả về lớp chứ không phải chính đối tượng đã bị hủy hoại
Stefan Haberl

Trên thực tế, giải pháp này rất tuyệt vời khi chúng tôi đang cố gắng tìm Class of obj để so sánh.
João Rebelo

13

Tôi đã viết mã sau để xóa đối tượng khỏi proxy (nếu chúng chưa được khởi tạo)

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

}

Tôi sử dụng chức năng này trên kết quả của các dịch vụ RPC của mình (thông qua các khía cạnh) và nó xóa sạch đệ quy tất cả các đối tượng kết quả từ proxy (nếu chúng không được khởi tạo).


cảm ơn vì đã chia sẻ mã này mặc dù nó không bao gồm tất cả các trường hợp sử dụng nhưng nó thực sự hữu ích ...
Prateek Singh

Chính xác. Nó nên được cập nhật theo các trường hợp mới. Bạn có thể thử những thứ được đề nghị bởi các chàng trai của GWT. Xem tại đây: gwtproject.org/articles/USE_gwt_with_hibernate.html (xem phần Chiến lược tích hợp). Nói chung, họ khuyên nên sử dụng DTO hoặc Dozer hoặc Gilead. Sẽ ổn thôi nếu bạn cung cấp ý kiến ​​của bạn về điều này. Trong trường hợp của tôi, có vẻ như mã của tôi là giải pháp đơn giản nhất, nhưng không đầy đủ = (.
Sergey Bondarev

cảm ơn. chúng ta có thể nhận triển khai cho "CollectionsUtils.containsTotallyEqual (handleObjects, value)" ở đâu?
Ilan.K

công khai boolean staticTotallyEqual (Bộ sưu tập <?> bộ sưu tập, giá trị đối tượng) {if (isEmpty (bộ sưu tập)) {return false; } for (Object object: Collection) {if (object == value) {return true; }} trả về sai; }
Serge Bondarev

Đó chỉ là phương pháp tiện ích do chính tôi tạo ra
Serge Bondarev

10

Cách tôi khuyên dùng với JPA 2:

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

2
Câu trả lời của bạn khác với tôi như thế nào?
Vlad Mihalcea

Tôi đã thử giải pháp này ... không phải lúc nào cũng hoạt động nếu bạn không đặt một cái gì đó như thế này trước lệnh unsrap: HibernateProxy hibernateProxy = (HibernateProxy) couldProxyObject; if (hibernateProxy.getHibernateLazyInitializer (). isUninitialized ()) {hibernateProxy.getHibernateLazyInitializer (). initize (); }
user3227576

2

Với Spring Data JPA và Hibernate, tôi đã sử dụng các JpaRepositorygiao diện con để tìm kiếm các đối tượng thuộc một hệ thống phân cấp loại được ánh xạ bằng chiến lược "tham gia". Thật không may, các truy vấn đã trả về các proxy của loại cơ sở thay vì các thể hiện của các loại cụ thể dự kiến. Điều này ngăn tôi đúc kết quả cho các loại chính xác. Giống như bạn, tôi đến đây để tìm kiếm một cách hiệu quả để khiến những người tham gia của tôi không bị ảnh hưởng.

Vlad có ý tưởng đúng đắn cho việc không cung cấp các kết quả này; Yannis cung cấp thêm một chút chi tiết. Thêm vào câu trả lời của họ, đây là phần còn lại của những gì bạn có thể đang tìm kiếm:

Đoạn mã sau cung cấp một cách dễ dàng để hủy bỏ các thực thể được ủy quyền của bạn:

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

}

Bạn có thể chuyển các thực thể không được ủy quyền hoặc các thực thể được ủy quyền cho unproxyphương thức. Nếu họ đã không được cung cấp, họ sẽ chỉ được trả lại. Nếu không, họ sẽ không được cung cấp và trả lại.

Hi vọng điêu nay co ich!


1

Cách giải quyết khác là gọi

Hibernate.initialize(extractedObject.getSubojbectToUnproxy());

Ngay trước khi kết thúc phiên.


1

Tôi đã tìm thấy một giải pháp để hủy bỏ một lớp bằng cách sử dụng API Java và JPA tiêu chuẩn. Đã thử nghiệm với chế độ ngủ đông, nhưng không yêu cầu ngủ đông như một sự phụ thuộc và nên hoạt động với tất cả các nhà cung cấp JPA.

Onle một yêu cầu - cần thiết để sửa đổi lớp cha (Địa chỉ) và thêm một phương thức trợ giúp đơn giản.

Ý tưởng chung: thêm phương thức của trình trợ giúp vào lớp cha tự trả về. khi phương thức được gọi trên proxy, nó sẽ chuyển tiếp cuộc gọi đến thể hiện thực và trả về thể hiện thực này.

Việc thực hiện phức tạp hơn một chút, vì ngủ đông nhận ra rằng lớp proxy trả về chính nó và vẫn trả về proxy thay vì thực tế. Giải pháp thay thế là bọc cá thể trả về vào một lớp bao bọc đơn giản, có loại lớp khác với thể hiện thực.

Trong mã:

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

class AddressWrapper {
    private Address wrappedAddress;
...
}

Để truyền proxy Địa chỉ cho lớp con thực, sử dụng như sau:

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

Mã ví dụ của bạn có vẻ hơi không rõ ràng (hoặc có thể tôi chỉ cần thêm cà phê). EntityWrapper đến từ đâu? Đó có phải là Địa chỉ? Và tôi đoán Địa chỉ được yêu cầu nên nói Địa chỉ? Bạn có thể làm rõ điều này?
Gus

@Gus, bạn nói đúng. Tôi đã sửa lại ví dụ. Cảm ơn :)
OndroMih

1

Bắt đầu từ Hiebrnate 5.2.10, bạn có thể sử dụng phương thức Hibernate.proxy để chuyển đổi proxy sang thực thể thực của mình:

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

0

Cảm ơn bạn cho các giải pháp được đề xuất! Thật không may, không ai trong số họ làm việc cho trường hợp của tôi: nhận danh sách các đối tượng CLOB từ cơ sở dữ liệu Oracle thông qua JPA - Hibernate, sử dụng truy vấn gốc.

Tất cả các cách tiếp cận được đề xuất đã cho tôi một ClassCastException hoặc đối tượng java Proxy vừa trả về (bên trong chứa Clob mong muốn).

Vì vậy, giải pháp của tôi là như sau (dựa trên một số cách tiếp cận ở trên):

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

Hy vọng điều này sẽ giúp được ai đó!

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.