Cara mengonversi proksi Hibernate ke objek entitas nyata

161

Selama Hibernate Session, saya memuat beberapa objek dan beberapa di antaranya dimuat sebagai proxy karena pemuatan malas. Tidak apa-apa dan saya tidak ingin mematikan pemuatan malas.

Tetapi kemudian saya perlu mengirim beberapa objek (sebenarnya satu objek) ke klien GWT melalui RPC. Dan kebetulan objek konkret ini adalah proxy. Jadi saya perlu mengubahnya menjadi objek nyata. Saya tidak dapat menemukan metode seperti "terwujud" di Hibernate.

Bagaimana saya bisa mengubah beberapa objek dari proxy menjadi real dengan mengetahui kelas dan ID mereka?

Saat ini satu-satunya solusi yang saya lihat adalah mengusir objek dari cache Hibernate dan memuatnya kembali, tetapi itu benar-benar buruk karena banyak alasan.

Andrey Minogin
sumber

Jawaban:

232

Inilah metode yang saya gunakan.

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;
}
Bozho
sumber
1
Saya ingin melakukan hal yang sama, jadi saya menulis contoh proksi ke ObjectOutputStream dan kemudian membacanya kembali dari ObjectInputStream yang sesuai, dan itu tampaknya melakukan trik. Saya tidak yakin apakah ini pendekatan yang efisien, tetapi masih bertanya-tanya mengapa itu berhasil ... komentar di dalamnya akan sangat dihargai. Terima kasih!
shrini1000
@ shrini1000 berhasil karena ketika serialisasi menginisialisasi koleksi (jika sesi belum ditutup). Juga HibernateProxymendefinisikan writeReplacemetode untuk memaksa implementor melakukan sesuatu yang istimewa selama serialisasi.
Bozho
1
Apakah ada cara portabel (JPA) untuk melakukan ini?
Kawu
mengapa, Hibernate.initialize melempar lazyInitializeException ketika saya menyebutnya? Saya hanya menggunakan seperti: Objek o = session.get (MyClass.class, id); Objek lainnya = o.getSomeOtherClass (); initializeAndUnproxy (lainnya);
fredcrs
6
Anda dapat melakukan hal yang sama tanpa kelas util Anda sendiri -(T)Hibernate.unproxy(entity)
panser
47

Seperti yang saya jelaskan dalam artikel ini , sejak Hibernate ORM 5.2.10 , Anda dapat melakukannya seperti ini:

Object unproxiedEntity = Hibernate.unproxy(proxy);

Sebelum Hibernasi 5.2.10 . cara paling sederhana untuk melakukannya adalah dengan menggunakan metode unproxy yang ditawarkan oleh PersistenceContextimplementasi internal Hibernate :

Object unproxiedEntity = ((SessionImplementor) session)
                         .getPersistenceContext()
                         .unproxy(proxy);
Vlad Mihalcea
sumber
Apakah memanggil ini pada entitas induk menangani bidang koleksi ?? misalnya, jika Anda memiliki DepartmentdenganStudent , apakah Anda masih perlu unproxy(department.getStudents()) - atau cukup unproxy(department)?
trafalmadorian
1
Hanya Proxy yang diberikan diinisialisasi. Itu tidak mengalir ke asosiasi, karena itu berpotensi memuat banyak data jika Anda tidak memproxy entitas root.
Vlad Mihalcea
Namun PersistentContext#unproxy(proxy)melempar pengecualian jika proxy tidak diinisialisasi sementara Hibernate.unproxy(proxy)dan LazyInitializer#getImplementation(proxy)menginisialisasi proxy jika perlu. Hanya menangkap pengecualian karena perbedaan ini. ;-)
bgraves
13

Coba gunakan Hibernate.getClass(obj)

Sanek Shu
sumber
15
Ini mengembalikan kelas daripada objek yang dideproksikan sendiri
Stefan Haberl
Sebenarnya solusi ini sangat bagus ketika kita mencoba mencari Kelas objek untuk perbandingan misalnya.
João Rebelo
13

Saya telah menulis kode berikut yang membersihkan objek dari proxy (jika belum diinisialisasi)

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

}

Saya menggunakan fungsi ini atas hasil layanan RPC saya (melalui aspek) dan membersihkan secara rekursif semua objek hasil dari proxy (jika tidak diinisialisasi).

Sergey Bondarev
sumber
terima kasih telah membagikan kode ini meskipun belum mencakup semua kasus penggunaan tetapi sangat membantu ...
Prateek Singh
Benar. Ini harus diperbarui sesuai dengan kasus baru. Anda dapat mencoba hal-hal yang direkomendasikan oleh GWT guys. Lihat di sini: gwtproject.org/articles/using_gwt_with_hibernate.html (lihat bagian Strategi Integrasi). Secara umum mereka merekomendasikan untuk menggunakan DTO atau Dozer atau Gilead. Ini akan baik-baik saja jika Anda akan memberikan pendapat Anda tentang ini. Dalam kasus saya kelihatannya kode saya adalah solusi paling sederhana, tetapi tidak full = (.
Sergey Bondarev
Terima kasih. di mana kita bisa mendapatkan implementasi untuk "CollectionsUtils.containsTotallyEqual (ditanganiObjects, value)"?
Ilan.K
boolean statis publik berisiTotallyEqual (koleksi <?>, Nilai objek) {if (isEmpty (collection)) {return false; } untuk (Objek objek: koleksi) {if (objek == nilai) {return true; }} return false; }
Sergey Bondarev
Ini hanya metode utilitas yang dibuat oleh saya sendiri
Sergey Bondarev
10

Cara saya merekomendasikan dengan JPA 2:

Object unproxied  = entityManager.unwrap(SessionImplementor.class).getPersistenceContext().unproxy(proxy);
Yannis JULIENNE
sumber
2
Bagaimana jawaban Anda berbeda dari jawaban saya?
Vlad Mihalcea
Saya sudah mencoba solusi ini ... tidak bekerja selalu jika Anda tidak meletakkan sesuatu seperti ini sebelum membuka perintah: HibernateProxy hibernateProxy = (HibernateProxy) possibleProxyObject; if (hibernateProxy.getHibernateLazyInitializer (). isUninitialized ()) {hibernateProxy.getHibernateLazyInitializer (). initialize (); }
user3227576
2

Dengan Spring Data JPA dan Hibernate, saya menggunakan subinterfaces JpaRepositoryuntuk mencari objek milik hierarki tipe yang dipetakan menggunakan strategi "join". Sayangnya, kueri mengembalikan proxy dari tipe dasar dan bukan contoh dari tipe beton yang diharapkan. Ini mencegah saya dari casting hasil ke tipe yang benar. Seperti Anda, saya datang ke sini mencari cara yang efektif untuk mendapatkan hak saya tidak diproksikan.

Vlad memiliki ide yang tepat untuk tidak menguji hasil ini; Yannis memberikan sedikit lebih banyak detail. Menambah jawaban mereka, inilah yang mungkin Anda cari:

Kode berikut memberikan cara mudah untuk membatalkan proksi entitas proksi Anda:

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

}

Anda dapat memberikan Entitas yang tidak diproksi atau entitas yang diproksi ke unproxy metode. Jika mereka sudah tidak terbukti, mereka hanya akan dikembalikan. Jika tidak, mereka akan diproteksi dan dikembalikan.

Semoga ini membantu!

Sharky
sumber
1

Solusi lain adalah menelepon

Hibernate.initialize(extractedObject.getSubojbectToUnproxy());

Tepat sebelum menutup sesi.

0x6B6F77616C74
sumber
1

Saya menemukan solusi untuk mendeproxy kelas menggunakan Java standar dan API JPA. Diuji dengan hibernate, tetapi tidak memerlukan hibernate sebagai dependensi dan harus bekerja dengan semua penyedia JPA.

Hanya satu persyaratan - perlu untuk memodifikasi kelas induk (Alamat) dan menambahkan metode pembantu sederhana.

Gagasan umum: tambahkan metode helper ke kelas induk yang mengembalikan sendiri. ketika metode dipanggil pada proxy, itu akan meneruskan panggilan ke instance nyata dan mengembalikan instance nyata ini.

Implementasi sedikit lebih kompleks, karena hibernate mengakui bahwa kelas proksi mengembalikan dirinya sendiri dan masih mengembalikan proksi alih-alih instance nyata. Solusinya adalah untuk membungkus instance yang dikembalikan ke dalam kelas wrapper sederhana, yang memiliki tipe kelas yang berbeda dari instance sebenarnya.

Dalam kode:

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

class AddressWrapper {
    private Address wrappedAddress;
...
}

Untuk memberikan alamat proxy ke subkelas nyata, gunakan yang berikut ini:

Address address = dao.getSomeAddress(...);
Address deproxiedAddress = address.getWrappedSelf().getWrappedAddress();
if (deproxiedAddress instanceof WorkAddress) {
WorkAddress workAddress = (WorkAddress)deproxiedAddress;
}
OndroMih
sumber
Contoh kode Anda agak tidak jelas (atau mungkin saya hanya perlu kopi lagi). Di mana EntityWrapper berasal? haruskah itu AddressWrapper? Dan saya menduga AddressWrapped seharusnya mengatakan AddressWrapper? Bisakah Anda mengklarifikasi ini?
Gus
@ Tuhan, kamu benar. Saya mengoreksi contohnya. Terima kasih :)
OndroMih
1

Mulai dari Hiebrnate 5.2.10 Anda dapat menggunakan metode Hibernate.proxy untuk mengonversi proxy ke entitas Anda yang sebenarnya:

MyEntity myEntity = (MyEntity) Hibernate.unproxy( proxyMyEntity );
O. Badr
sumber
0

Terima kasih atas solusi yang disarankan! Sayangnya, tidak ada satupun yang berfungsi untuk kasus saya: menerima daftar objek CLOB dari database Oracle melalui JPA - Hibernate, menggunakan kueri asli.

Semua pendekatan yang diusulkan memberi saya baik ClassCastException atau hanya mengembalikan objek java Proxy (yang jauh di dalamnya berisi Clob yang diinginkan).

Jadi solusi saya adalah sebagai berikut (berdasarkan beberapa pendekatan di atas):

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

Semoga ini bisa membantu seseorang!

Dmitry
sumber