วิธีแปลงพร็อกซีไฮเบอร์เนตเป็นวัตถุเอนทิตีจริง


161

ในระหว่างการไฮเบอร์เนตSessionฉันกำลังโหลดวัตถุบางอย่างและบางส่วนถูกโหลดเป็นพร็อกซีเนื่องจากการโหลดแบบสันหลังยาว ไม่เป็นไรและฉันไม่ต้องการปิดการโหลดแบบขี้เกียจ

แต่ต่อมาฉันต้องส่งวัตถุบางอย่าง (จริง ๆ แล้ววัตถุหนึ่งชิ้น) ไปยังไคลเอนต์ GWT ผ่าน RPC และมันเกิดขึ้นว่าวัตถุรูปธรรมนี้เป็นตัวแทน ดังนั้นฉันต้องเปลี่ยนมันเป็นวัตถุจริง ฉันไม่พบวิธีเช่น "materialize" ใน Hibernate

ฉันจะเปลี่ยนบางสิ่งบางอย่างจากพร็อกซีไปยัง reals รู้คลาสและ ID ของพวกเขาได้อย่างไร

ในขณะนี้ทางออกเดียวที่ฉันเห็นคือการขับไล่วัตถุนั้นออกจากแคชของไฮเบอร์เนตและโหลดใหม่ แต่มันก็แย่มากด้วยเหตุผลหลายประการ

คำตอบ:


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วิธีการในการ implementors บังคับให้ทำอะไรเป็นพิเศษในช่วงอนุกรม
Bozho

1
มีวิธีพกพา (JPA) ในการทำเช่นนี้หรือไม่?
Kawu

ทำไมถึงไฮเบอร์เนตเริ่มต้นการขี้เกียจเริ่มต้นการยกเว้นเมื่อฉันเรียกมัน ฉันแค่ใช้ like: Object o = session.get (MyClass.class, id); วัตถุอื่น ๆ = o.getSomeOtherClass (); initializeAndUnproxy (อื่น ๆ );
ศุกร์ที่

6
คุณสามารถทำสิ่งเดียวกันได้โดยไม่ต้องคลาส util ของคุณเอง -(T)Hibernate.unproxy(entity)
panser

47

ดังที่ฉันได้อธิบายไว้ในบทความนี้ตั้งแต่ Hibernate ORM 5.2.10คุณสามารถทำได้เช่นนี้

Object unproxiedEntity = Hibernate.unproxy(proxy);

ก่อน Hibernate 5.2.10 วิธีที่ง่ายที่สุดในการทำเช่นนั้นคือการใช้วิธีunproxy ที่นำเสนอโดยการPersistenceContextใช้งานภายในของไฮเบอร์เนต:

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

การเรียกสิ่งนี้ในเอนทิตีหลักจัดการกับฟิลด์คอลเล็กชันหรือไม่? เช่นถ้าคุณมีDepartmentรายชื่อStudentคุณยังต้องunproxy(department.getStudents()) - หรือเพียงพอunproxy(department)หรือไม่
trafalmadorian

1
เริ่มต้นพร็อกซีที่กำหนดเท่านั้น ไม่เกี่ยวข้องกับการเชื่อมโยงเนื่องจากอาจทำให้โหลดข้อมูลได้มากมายหากคุณเกิด unproxy ที่เป็นเอนทิตีรูท
Vlad Mihalcea

อย่างไรก็ตามจะPersistentContext#unproxy(proxy)ส่งข้อยกเว้นหากไม่ได้กำหนดค่าเริ่มต้นพร็อกซีในขณะที่Hibernate.unproxy(proxy)และLazyInitializer#getImplementation(proxy)เริ่มต้นพร็อกซีหากจำเป็น เพิ่งได้รับการยกเว้นเนื่องจากความแตกต่างนี้ ;-)
bgraves

13

ลองใช้ดู Hibernate.getClass(obj)


15
สิ่งนี้จะส่งคืนคลาสแทนที่จะเป็นวัตถุตกต่ำ
Stefan Haberl

อันที่จริงวิธีนี้เป็นวิธีที่ดีเมื่อเราพยายามหา Class of 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 มันจะไม่เป็นไรถ้าคุณจะให้ความเห็นเกี่ยวกับเรื่องนี้ ในกรณีของฉันมันดูรหัสของฉันเป็นวิธีที่ง่ายที่สุด แต่ไม่เต็ม = (.
Sergey Bondarev

ขอบคุณ เราจะรับการปรับใช้สำหรับ "CollectionsUtils.containTotallyEqual (managedObjects, value)" ได้ที่ไหน?
Ilan.K

บูลีนสแตติกสาธารณะประกอบด้วยคอลเลกชัน (คอลเลกชัน <?> ค่าวัตถุ) {ถ้า (isEmpty (คอลเลกชัน)) {คืนเท็จ; } สำหรับ (Object Object: collection) {ถ้า (object == value) {return true; }} คืนค่าเท็จ }
Sergey Bondarev

มันเป็นเพียงวิธีการใช้งานที่สร้างขึ้นด้วยตัวเอง
Sergey Bondarev

10

วิธีที่ฉันแนะนำด้วย JPA 2:

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

2
คำตอบของคุณแตกต่างจากของฉันอย่างไร
Vlad Mihalcea

ฉันได้ลองใช้วิธีแก้ไขปัญหานี้ ... ไม่ได้ผลเสมอไปถ้าคุณไม่ใส่อะไรแบบนี้ไว้ก่อนคำสั่ง unwrap: HibernateProxy hibernateProxy = (HibernateProxy) เป็นไปได้ProxyObject; ถ้า (hibernateProxy.getHibernateLazyInitializer (). isUninitialized ()) {hibernateProxy.getHibernateLazyInitializer (). เตรียมใช้งาน (); }
user3227576

2

ด้วย Spring Data JPA และ Hibernate ฉันใช้ subinterfaces JpaRepositoryเพื่อค้นหาวัตถุที่อยู่ในลำดับชั้นประเภทที่แมปโดยใช้กลยุทธ์ "เข้าร่วม" ขออภัยคิวรีกำลังส่งคืนพร็อกซีของชนิดฐานแทนอินสแตนซ์ของชนิดคอนกรีตที่คาดไว้ สิ่งนี้ทำให้ฉันไม่สามารถแสดงผลลัพธ์เป็นประเภทที่ถูกต้องได้ เช่นเดียวกับคุณฉันมาที่นี่เพื่อมองหาวิธีที่มีประสิทธิภาพในการรับสิทธิ์เข้าร่วมของฉัน

วลาดมีความคิดที่ถูกต้องสำหรับการไม่แสดงผลลัพธ์เหล่านี้ Yannis ให้รายละเอียดเพิ่มเติมเล็กน้อย เพิ่มคำตอบของพวกเขานี่คือส่วนที่เหลือของสิ่งที่คุณอาจมองหา:

รหัสต่อไปนี้ให้วิธีง่าย ๆ ในการ unproxy เอนทิตีที่ proxied ของคุณ:

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 ทั้งหมด

ข้อกำหนดหนึ่งเดียว - จำเป็นต้องแก้ไขคลาสแม่ (ที่อยู่) และเพิ่มวิธีการช่วยเหลือแบบง่าย

แนวคิดทั่วไป: เพิ่มเมธอดผู้ช่วยเหลือไปยังคลาสพาเรนต์ซึ่งส่งคืนตัวเอง เมื่อเมธอดถูกเรียกใช้บนพร็อกซีมันจะโอนสายไปยังอินสแตนซ์จริงและส่งคืนอินสแตนซ์จริงนี้

การใช้งานมีความซับซ้อนมากขึ้นเล็กน้อยเนื่องจากจำศีลว่าคลาสพร็อกซีส่งคืนตัวเองและยังคงส่งคืนพร็อกซีแทนอินสแตนซ์จริง วิธีแก้ไขคือห่ออินสแตนซ์ที่ส่งคืนเป็นคลาส wrapper อย่างง่ายซึ่งมีชนิดคลาสแตกต่างจากอินสแตนซ์จริง

ในรหัส:

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

class AddressWrapper {
    private Address wrappedAddress;
...
}

ในการส่ง Address พร็อกซีไปยังคลาสย่อยจริงให้ใช้ดังต่อไปนี้:

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

โค้ดตัวอย่างของคุณดูเหมือนไม่ชัดเจน (หรือฉันอาจต้องการกาแฟเพิ่ม) EntityWrapper มาจากไหน? ที่ควรเป็น AddressWrapper และฉันคาดเดา AddressWrapped ควรพูด AddressWrapper? คุณช่วยอธิบายเรื่องนี้ได้ไหม?
กัส

@ กัสคุณพูดถูก ฉันแก้ไขตัวอย่าง ขอบคุณ :)
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();
}

หวังว่านี่จะช่วยใครซักคน!

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.