Hazırda bekletme: tüm tembel koleksiyonları çekmek için en iyi uygulama


92

Neyim var:

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

Ne sorun:

Sorun şu ki, oturum kapatıldıktan sonra tembel koleksiyon alamıyorum. Ama devam etme yönteminde de bir oturumu kapatamıyorum .

Ne çözüm (kaba çözüm):

a) Oturum kapanmadan önce, tembel koleksiyonları çekmek için hazırda bekletmeye zorlayın

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

....

b) Belki daha ellegant bir yol, @Fetch(FetchMode.SUBSELECT)açıklama kullanmaktır

Soru:

En iyi uygulama / ortak yol / bunu yapmanın daha ellegant yolu nedir? Nesnemi JSON'a dönüştürmek anlamına gelir.

Yanıtlar:


102

Tembel nesneleri başlatmak için Hibernate.initialize()içinde kullanın @Transactional.

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

Artık İşlemin dışında tembel nesneler elde edebilirsiniz.

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

1
Çekici görünüyor). @Fetch (FetchMode.SUBSELECT) kullanıp kullanmayacağımı anladığım kadarıyla, tüm koleksiyonları çekmek için Hibernate.initialize'ı yalnızca bir kez çağırabilirim. Haklı mıyım
VB_

4
Ve bir MyEntity koleksiyonunu aldığınızda nasıl idare edersiniz?
Alexis Dufrenoy

1
Bir işlemdeki bir koleksiyonda "size ()" gibi herhangi bir yöntemi çağırırsanız, aynı zamanda onu başlatacaktır, böylece sizin başlatmanızdan sonraki örneğiniz en iyisi olmaz. Buna göre, "Hibernate.initialize (...)" anlamsal olarak collection.size () 'den daha iyidir, bu yüzden en iyi tavsiyeye sahipsiniz.
Tristan

7

Tüm tembel alt nesnelerin aşağıdaki genel yardımcı sınıfla hevesle getirildiğinden emin olmak için aynı işlemde Hazırda Bekletme nesnesinin Getters'ı üzerinden geçiş yapabilirsiniz :

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

Bu cevap için teşekkür ederim. Bir süredir biliyorum ama bunu çözmeye çalışıyordum ve kodunuzu burada okuyana kadar yavaş ilerliyordu. Ayrıca, ikinci yöntemin başlangıcına ifs ekledim 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; } Listeleri yineleme aksi takdirde yok sayılır.
Chip

Ya SecurityException o.getClass (). GetMethods ();
Oleksii Kyslytsyn

6

En iyi çözüm değil, ama bende şu var:

1) Bu ek açıklama ile başlatmak istediğiniz alıcıya açıklama ekleyin:

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

}

2) Bir nesneyi veritabanından okuduktan sonra bu yöntemi kullanın (genel bir sınıfa yerleştirilebilir veya T'yi Object sınıfıyla değiştirebilirsiniz):

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

LazyCollections'ı yüklemek için bir yinelemede session.refresh kullanıyorum. ve programımı yalnızca varlığımdan biri için çalıştırdığımda, session.refresh'i çağırdıktan sonra LazyInitializationException ve diğer koleksiyonları yükledim. Bu nasıl olabilir?
Saba safavi

5

Utils.objectToJson (varlık) öğesini yerleştirin; oturum kapanmadan önce arayın.

Veya getirme modunu ayarlamayı ve bunun gibi bir kodla oynamayı deneyebilirsiniz

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

FetchMode.EAGER kullanımdan kaldırıldı. Javadoc, şimdi FetchMode.JOIN'i kullanmanızı önerir.
Alexis Dufrenoy

4

Hibernate 4.1.6 ile, bu tembel ilişkilendirme sorunlarını çözmek için yeni bir özellik sunulmuştur. Hibernate.properties veya hibernate.cfg.xml'de hibernate.enable_lazy_load_no_trans özelliğini etkinleştirdiğinizde, artık LazyInitializationException olmayacak.

Daha fazla bilgi için: https://stackoverflow.com/a/11913404/286588


3
Bu aslında bir anti-modeldir. Daha fazla bilgi için: vladmihalcea.com/…
Ph03n1x

3

Birden çok koleksiyonu getirmeniz gerektiğinde şunları yapmanız gerekir:

  1. FETCH bir koleksiyona KATILIN
  2. Hibernate.initializeKalan koleksiyonlar için kullanın .

Yani, sizin durumunuzda, bunun gibi bir ilk JPQL sorgusuna ihtiyacınız var:

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

Bu şekilde 2 SQL sorgusu ile hedefinize ulaşabilir ve Kartezyen Üründen kaçınabilirsiniz.


Merhaba Vlad, Hibernate#initialize(entity.getSubSet())getSubSet dönerse ararsam çalışır Collections.unmodifyableSet(this.subSet). Denedim ama olmadı. Underlaying koleksiyonu 'PersistentSet'tir. #size()
Aramayla

Ama belki de sorun şu ki, daha sonra aramam, ve eşitlerimin alıcıları değil, doğrudan alan erişimini kullanmasıdır ..
Vadim Kirilchuk

Cevabımda verilen adımları izlerseniz işe yarar.
Vlad Mihalcea

2

Muhtemelen en iyi uygulamaya yaklaşan herhangi bir yer yoktur, ancak genellikle SIZEönerdiğiniz gibi çocukları aynı işleme yüklemek için koleksiyona çağırıyorum . Temizdir, alt öğelerin yapısındaki herhangi bir değişikliğe karşı bağışıktır ve düşük ek yük ile SQL sağlar.


0

Kullanmayı dene GsonNesneleri Json'a dönüştürmek kitaplığı

Sunucu uygulamalı örnek:

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

0

jpa deposunu kullanıyorsanız, properties.put ("hibernate.enable_lazy_load_no_trans", true) 'yi ayarlayın; jpaPropertymap için


0

@NamedEntityGraphSorgunuza hangi koleksiyonları yüklemek istediğinizi ayarlayan yüklenebilir bir sorgu oluşturmak için varlığınıza ek açıklamayı kullanabilirsiniz .

Bu seçimin ana avantajı, hazırda bekletmenin varlığı ve koleksiyonlarını almak için tek bir sorgu yapması ve yalnızca bu grafiği kullanmayı seçtiğinizde şudur:

Varlık yapılandırması

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

Kullanım

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

0

JPA-Hibernate'de tembel koleksiyonlar hakkında bir tür yanlış anlaşılma var. Her şeyden önce , tembel bir koleksiyonu okumaya çalışmanın neden istisnalar attığını ve yalnızca dönüştürme veya daha fazla kullanım durumları için NULL döndürmediğini açıklığa kavuşturalım ?.

Bunun nedeni, Veritabanlarındaki, özellikle birleştirilmiş sütunlardaki Null alanların, programlama dilleri gibi, basitçe sunulmayan bir duruma değil, bir anlama sahip olmasıdır. tembel bir koleksiyonu Null değerine yorumlamaya çalıştığınızda bu, (Datastore tarafında) bu varlıklar arasında hiçbir ilişki olmadığı ve bu doğru olmadığı anlamına gelir. bu nedenle istisna atmak bir tür en iyi uygulamadır ve bununla Hazırda Bekletme değil, bununla başa çıkmanız gerekir.

Yukarıda belirtildiği gibi şunları tavsiye ederim:

  1. Değiştirmeden veya sorgulama için durum bilgisiz oturum kullanmadan önce istenen nesneyi ayırın
  2. Tembel alanları istenen değerlere (sıfır, boş, vb.) Göre değiştirin

Ayrıca diğer yanıtlarda da açıklandığı gibi, bunu yapmak için birçok yaklaşım (istekli getirme, katılma vb.) veya kitaplıklar ve yöntemler vardır, ancak sorunu çözmeden ve çözmeden önce neler olduğuna dair görüşünüzü oluşturmanız gerekir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.