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.
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 :
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; } }
sumber
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.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(); } }
sumber
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();
sumber
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
sumber
Saat harus mengambil beberapa koleksi, Anda perlu:
Hibernate.initialize
untuk 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.
sumber
Hibernate#initialize(entity.getSubSet())
jika getSubSet kembaliCollections.unmodifyableSet(this.subSet)
. Saya mencoba dan ternyata tidak. Koleksi yang mendasari adalah 'PersistentSet'. Cerita yang sama dengan menelepon#size()
Mungkin tidak ada yang mendekati praktik terbaik, tetapi saya biasanya memanggil
SIZE
koleksi 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.sumber
Coba gunakan
Gson
perpustakaan untuk mengonversi objek ke JsonContoh 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);
sumber
jika Anda menggunakan repositori jpa, setel properties.put ("hibernate.enable_lazy_load_no_trans", true); ke jpaPropertymap
sumber
Anda dapat menggunakan
@NamedEntityGraph
anotasi 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); }
sumber
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:
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.
sumber