Hibernate: praktik terbaik untuk menarik semua koleksi malas

92

Apa yang saya punya:

@Entity
public class MyEntity {
  @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
  @JoinColumn(name = "myentiy_id")
  private List<Address> addreses;

  @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
  @JoinColumn(name = "myentiy_id")
  private List<Person> persons;

  //....
}

public void handle() {

   Session session = createNewSession();
   MyEntity entity = (MyEntity) session.get(MyEntity.class, entityId);
   proceed(session); // FLUSH, COMMIT, CLOSE session!

   Utils.objectToJson(entity); //TROUBLES, because it can't convert to json lazy collections
}

Apa masalah:

Masalahnya adalah saya tidak dapat menarik koleksi malas setelah sesi ditutup. Tetapi saya juga tidak bisa menutup sesi dalam metode lanjutkan .

Solusi apa (solusi kasar):

a) Sebelum sesi ditutup, paksa hibernasi untuk menarik koleksi malas

entity.getAddresses().size();
entity.getPersons().size();

....

b) Mungkin cara yang lebih elegan adalah dengan menggunakan @Fetch(FetchMode.SUBSELECT)anotasi

Pertanyaan:

Apa praktik terbaik / cara umum / cara yang lebih elegan untuk melakukannya? Berarti mengubah objek saya menjadi JSON.

VB_
sumber

Jawaban:

102

Gunakan Hibernate.initialize()dalam @Transactionaluntuk menginisialisasi objek malas.

 start Transaction 
      Hibernate.initialize(entity.getAddresses());
      Hibernate.initialize(entity.getPersons());
 end Transaction 

Sekarang di luar Transaksi Anda bisa mendapatkan objek malas.

entity.getAddresses().size();
entity.getPersons().size();
Prabhakaran Ramaswamy
sumber
1
Itu terlihat menarik). Seperti yang saya pahami jika saya akan menggunakan @Fetch (FetchMode.SUBSELECT), maka saya dapat memanggil Hibernate.initialize hanya sekali untuk menarik semua koleksi. Apakah saya benar?
VB_
4
Dan bagaimana Anda mengatur saat mengambil koleksi MyEntity?
Alexis Dufrenoy
1
Jika Anda memanggil metode apa pun seperti "size ()" pada koleksi dalam transaksi, metode ini juga akan menginisialisasi sehingga contoh Anda setelah inisialisasi bukanlah yang terbaik. Ini mengatakan, "Hibernate.initialize (...)" secara semantik lebih baik daripada collection.size (), jadi Anda memiliki saran terbaik.
Tristan
7

Anda dapat melintasi Getters dari objek Hibernate dalam transaksi yang sama untuk memastikan semua objek anak malas diambil dengan penuh semangat dengan kelas helper generik berikut :

HibernateUtil.initializeObject (myObject, "my.app.model");

package my.app.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;

import org.aspectj.org.eclipse.jdt.core.dom.Modifier;
import org.hibernate.Hibernate;

public class HibernateUtil {

public static byte[] hibernateCollectionPackage = "org.hibernate.collection".getBytes();

public static void initializeObject( Object o, String insidePackageName ) {
    Set<Object> seenObjects = new HashSet<Object>();
    initializeObject( o, seenObjects, insidePackageName.getBytes() );
    seenObjects = null;
}

private static void initializeObject( Object o, Set<Object> seenObjects, byte[] insidePackageName ) {

    seenObjects.add( o );

    Method[] methods = o.getClass().getMethods();
    for ( Method method : methods ) {

        String methodName = method.getName();

        // check Getters exclusively
        if ( methodName.length() < 3 || !"get".equals( methodName.substring( 0, 3 ) ) )
            continue;

        // Getters without parameters
        if ( method.getParameterTypes().length > 0 )
            continue;

        int modifiers = method.getModifiers();

        // Getters that are public
        if ( !Modifier.isPublic( modifiers ) )
            continue;

        // but not static
        if ( Modifier.isStatic( modifiers ) )
            continue;

        try {

            // Check result of the Getter
            Object r = method.invoke( o );

            if ( r == null )
                continue;

            // prevent cycles
            if ( seenObjects.contains( r ) )
                continue;

            // ignore simple types, arrays und anonymous classes
            if ( !isIgnoredType( r.getClass() ) && !r.getClass().isPrimitive() && !r.getClass().isArray() && !r.getClass().isAnonymousClass() ) {

                // ignore classes out of the given package and out of the hibernate collection
                // package
                if ( !isClassInPackage( r.getClass(), insidePackageName ) && !isClassInPackage( r.getClass(), hibernateCollectionPackage ) ) {
                    continue;
                }

                // initialize child object
                Hibernate.initialize( r );

                // traverse over the child object
                initializeObject( r, seenObjects, insidePackageName );
            }

        } catch ( InvocationTargetException e ) {
            e.printStackTrace();
            return;
        } catch ( IllegalArgumentException e ) {
            e.printStackTrace();
            return;
        } catch ( IllegalAccessException e ) {
            e.printStackTrace();
            return;
        }
    }

}

private static final Set<Class<?>> IGNORED_TYPES = getIgnoredTypes();

private static boolean isIgnoredType( Class<?> clazz ) {
    return IGNORED_TYPES.contains( clazz );
}

private static Set<Class<?>> getIgnoredTypes() {
    Set<Class<?>> ret = new HashSet<Class<?>>();
    ret.add( Boolean.class );
    ret.add( Character.class );
    ret.add( Byte.class );
    ret.add( Short.class );
    ret.add( Integer.class );
    ret.add( Long.class );
    ret.add( Float.class );
    ret.add( Double.class );
    ret.add( Void.class );
    ret.add( String.class );
    ret.add( Class.class );
    ret.add( Package.class );
    return ret;
}

private static Boolean isClassInPackage( Class<?> clazz, byte[] insidePackageName ) {

    Package p = clazz.getPackage();
    if ( p == null )
        return null;

    byte[] packageName = p.getName().getBytes();

    int lenP = packageName.length;
    int lenI = insidePackageName.length;

    if ( lenP < lenI )
        return false;

    for ( int i = 0; i < lenI; i++ ) {
        if ( packageName[i] != insidePackageName[i] )
            return false;
    }

    return true;
}
}
Florian Sager
sumber
Terima kasih atas jawaban ini. Saya tahu ini sudah lama tetapi saya mencoba menyelesaikan ini dan itu lambat sampai saya membaca kode Anda di sini. Saya juga menambahkan ifs ke awal metode kedua initializeObject (object, seenObjects, insidePackageName): if (object instanceof List) { for(Object item : (List<Object>) object) { initializeObject(item, seenObjects, insidePackageName); } return; } else if (object instanceof Set) { for(Object item : (Set<Object>) object) { initializeObject(item, seenObjects, insidePackageName); } return; } Iterate list jika tidak diabaikan.
Chip
Bagaimana jika SecurityException dilempar ke o.getClass (). GetMethods () ;?
Oleksii Kyslytsyn
6

Bukan solusi terbaik, tapi inilah yang saya dapatkan:

1) Anotasi pengambil yang ingin Anda inisialisasi dengan anotasi ini:

@Retention(RetentionPolicy.RUNTIME)
public @interface Lazy {

}

2) Gunakan metode ini (dapat dimasukkan ke dalam kelas generik, atau Anda dapat mengubah T dengan kelas Objek) pada objek setelah Anda membacanya dari database:

    public <T> void forceLoadLazyCollections(T entity) {

    Session session = getSession().openSession();
    Transaction tx = null;
    try {

        tx = session.beginTransaction();
        session.refresh(entity);
        if (entity == null) {
            throw new RuntimeException("Entity is null!");
        }
        for (Method m : entityClass.getMethods()) {

            Lazy annotation = m.getAnnotation(Lazy.class);
            if (annotation != null) {
                m.setAccessible(true);
                logger.debug(" method.invoke(obj, arg1, arg2,...); {} field", m.getName());
                try {
                    Hibernate.initialize(m.invoke(entity));
                }
                catch (Exception e) {
                    logger.warn("initialization exception", e);
                }
            }
        }

    }
    finally {
        session.close();
    }
}
Damian
sumber
Saya menggunakan session.refresh dalam sebuah iterasi untuk memuat lazyCollections. dan setiap kali saya menjalankan program saya hanya untuk salah satu entitas saya, saya mendapat LazyInitializationException dan koleksi lain dimuat setelah memanggil session.refresh. Bagaimana ini bisa terjadi
saba safavi
5

Tempatkan Utils.objectToJson (entitas); menelepon sebelum penutupan sesi.

Atau Anda dapat mencoba mengatur mode pengambilan dan bermain dengan kode seperti ini

Session s = ...
DetachedCriteria dc = DetachedCriteria.forClass(MyEntity.class).add(Expression.idEq(id));
dc.setFetchMode("innerTable", FetchMode.EAGER);
Criteria c = dc.getExecutableCriteria(s);
MyEntity a = (MyEntity)c.uniqueResult();
StanislavL
sumber
FetchMode.EAGER tidak digunakan lagi. Javadoc merekomendasikan untuk menggunakan FetchMode.JOIN, sekarang.
Alexis Dufrenoy
4

Dengan Hibernate 4.1.6, fitur baru diperkenalkan untuk menangani masalah asosiasi malas tersebut. Saat Anda mengaktifkan properti hibernate.enable_lazy_load_no_trans di hibernate.properties atau di hibernate.cfg.xml, Anda tidak akan memiliki LazyInitializationException lagi.

Untuk Lebih Lanjut, lihat: https://stackoverflow.com/a/11913404/286588

Tanah pertanian
sumber
3
Ini sebenarnya adalah anti-pola. Untuk info lebih lanjut: vladmihalcea.com/…
Ph03n1x
3

Saat harus mengambil beberapa koleksi, Anda perlu:

  1. JOIN FETCH satu koleksi
  2. Gunakan Hibernate.initializeuntuk koleksi yang tersisa.

Jadi, dalam kasus Anda, Anda memerlukan kueri JPQL pertama seperti ini:

MyEntity entity = session.createQuery("select e from MyEntity e join fetch e.addreses where e.id 
= :id", MyEntity.class)
.setParameter("id", entityId)
.getSingleResult();

Hibernate.initialize(entity.persons);

Dengan cara ini, Anda dapat mencapai tujuan Anda dengan 2 kueri SQL dan menghindari Produk Kartesius.

Vlad Mihalcea
sumber
Hai Vlad, apakah itu berhasil jika saya menelepon Hibernate#initialize(entity.getSubSet())jika getSubSet kembali Collections.unmodifyableSet(this.subSet). Saya mencoba dan ternyata tidak. Koleksi yang mendasari adalah 'PersistentSet'. Cerita yang sama dengan menelepon#size()
Vadim Kirilchuk
Tapi mungkin masalahnya adalah bahwa saya kemudian memanggil berisi dan yang sederajat menggunakan akses lapangan langsung dan bukan pengambil ..
Vadim Kirilchuk
Ini berfungsi jika Anda mengikuti langkah-langkah yang disediakan dalam jawaban saya.
Vlad Mihalcea
2

Mungkin tidak ada yang mendekati praktik terbaik, tetapi saya biasanya memanggil SIZEkoleksi untuk memuat anak-anak dalam transaksi yang sama, seperti yang Anda sarankan. Bersih, kebal terhadap perubahan apa pun dalam struktur elemen turunan, dan menghasilkan SQL dengan overhead rendah.

davek
sumber
0

Coba gunakan Gsonperpustakaan untuk mengonversi objek ke Json

Contoh dengan servlet:

  List<Party> parties = bean.getPartiesByIncidentId(incidentId);
        String json = "";
        try {
            json = new Gson().toJson(parties);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write(json);
Mohamed Nagy
sumber
0

jika Anda menggunakan repositori jpa, setel properties.put ("hibernate.enable_lazy_load_no_trans", true); ke jpaPropertymap

userSait
sumber
0

Anda dapat menggunakan @NamedEntityGraphanotasi ke entitas Anda untuk membuat kueri yang dapat dimuat yang mengatur koleksi mana yang ingin Anda muat pada kueri Anda.

Keuntungan utama dari pilihan ini adalah hibernate membuat satu kueri tunggal untuk mengambil entitas dan koleksinya dan hanya ketika Anda memilih untuk menggunakan grafik ini, seperti ini:

Konfigurasi entitas

@Entity
@NamedEntityGraph(name = "graph.myEntity.addresesAndPersons", 
attributeNodes = {
    @NamedAttributeNode(value = "addreses"),
    @NamedAttributeNode(value = "persons"
})

Pemakaian

public MyEntity findNamedGraph(Object id, String namedGraph) {
        EntityGraph<MyEntity> graph = em.getEntityGraph(namedGraph);

        Map<String, Object> properties = new HashMap<>();
        properties.put("javax.persistence.loadgraph", graph);

        return em.find(MyEntity.class, id, properties);
    }
usus
sumber
0

Ada beberapa kesalahpahaman tentang lazy collection di JPA-Hibernate. Pertama-tama mari kita jelaskan bahwa mengapa mencoba membaca lazy collection melontarkan pengecualian dan tidak hanya mengembalikan NULL untuk mengonversi atau kasus penggunaan lebih lanjut? .

Itu karena bidang Null di Database terutama di kolom yang digabungkan memiliki arti dan bukan hanya status tidak disajikan, seperti bahasa pemrograman. ketika Anda mencoba menafsirkan lazy collection menjadi nilai Null, artinya (di sisi Datastore) tidak ada hubungan antara entitas ini dan itu tidak benar. jadi melempar pengecualian adalah praktik terbaik dan Anda harus mengatasinya, bukan Hibernate.

Jadi seperti yang disebutkan di atas saya merekomendasikan untuk:

  1. Lepaskan objek yang diinginkan sebelum mengubahnya atau menggunakan sesi stateless untuk membuat kueri
  2. Memanipulasi bidang malas ke nilai yang diinginkan (nol, nol, dll.)

juga seperti yang dijelaskan dalam jawaban lain ada banyak pendekatan (eager fetch, bergabung, dll.) atau pustaka dan metode untuk melakukan itu, tetapi Anda harus mengatur pandangan Anda tentang apa yang terjadi sebelum menangani masalah dan menyelesaikannya.

Mohsen Msr
sumber