हाइबरनेट: सभी आलसी संग्रह को खींचने के लिए सबसे अच्छा अभ्यास


92

जो मेरे पास है:

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

क्या समस्या है:

समस्या यह है कि मैं सत्र बंद होने के बाद आलसी संग्रह को नहीं खींच सकता। लेकिन मैं आगे बढ़ने की विधि में सत्र बंद नहीं कर सकता ।

क्या समाधान (मोटे समाधान):

a) सत्र बंद होने से पहले, आलसी कलेक्ट्स को खींचने के लिए हाइबरनेट करें

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

....

b) शायद अधिक अभियोगात्मक तरीका @Fetch(FetchMode.SUBSELECT)एनोटेशन का उपयोग करना है

सवाल:

इसे करने के लिए एक सर्वोत्तम अभ्यास / सामान्य तरीका / अधिक उपयुक्त तरीका क्या है? मतलब मेरी वस्तु को JSON में बदल देता है।

जवाबों:


102

आलसी वस्तुओं को आरंभ करने Hibernate.initialize()के @Transactionalलिए उपयोग करें ।

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

अब लेन-देन के बाहर आप आलसी वस्तुओं को प्राप्त करने में सक्षम हैं।

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

1
यह आकर्षक लग रहा है)। जैसा कि मैं समझता हूं कि अगर मैं सभी संग्रहों को खींचने के लिए केवल एक बार Hibernate.initialize की तुलना में @Fetch (FetchMode.SUBSELECT) का उपयोग करूंगा। क्या मैं सही हू?
VB_

4
और जब आप MyEntity का संग्रह पुनः प्राप्त करते हैं तो आप कैसे प्रबंधित करते हैं?
एलेक्सिस डुफ्रेनॉय

1
यदि आप किसी लेनदेन में संग्रह पर "आकार ()" जैसी किसी भी विधि को कहते हैं, तो यह इसे भी इनिशियलाइज़ कर देगा ताकि आपका इनिशियलाइज़ेशन सबसे अच्छा न हो। इसने कहा, "Hibernate.initialize (...)" शब्दशः बेहतर है तो संग्रह। Size (), इसलिए आपके पास सबसे अच्छी सलाह है।
ट्रिस्टन

7

आप एक ही लेन-देन में हाइबरनेट ऑब्जेक्ट के गेटर्स पर पार कर सकते हैं ताकि यह सुनिश्चित हो सके कि सभी आलसी बच्चे वस्तुओं को निम्नलिखित सामान्य सहायक वर्ग के साथ उत्सुकता से प्राप्त कर रहे हैं :

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

इस उत्तर के लिए धन्यवाद। मुझे पता है कि यह एक समय हो गया है, लेकिन मैं इसे हल करने की कोशिश कर रहा था और जब तक मैं आपका कोड यहां नहीं पढ़ता, तब तक यह धीमा था। मैंने यह भी कहा कि इनिशियलाइज़ऑब्जेक्ट (ऑब्जेक्ट, आरीऑब्जेक्ट्स, इनपैकेजनाम) के दूसरे तरीके की शुरुआत में आईएआरएटेट 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; } लिस्ट को अन्यथा नजरअंदाज कर दिया जाता है।
चिप

क्या होगा अगर SecurityException को o.getClass () में फेंक दिया जाए। getMethods () ;?
Oleksii Kyslytsyn

6

सबसे अच्छा समाधान नहीं है, लेकिन यहाँ मुझे क्या मिला है:

1) एनोटेट पाने वाला आप इस एनोटेशन के साथ आरंभ करना चाहते हैं:

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

}

2) इस विधि का उपयोग करें (एक सामान्य वर्ग में रखा जा सकता है, या आप इसे ऑब्जेक्ट के साथ T बदल सकते हैं) किसी ऑब्जेक्ट पर डेटाबेस से पढ़ने के बाद:

    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 को लोड करने के लिए एक पुनरावृत्ति में session.refresh का उपयोग करता हूं। और हर बार जब मैं अपना कार्यक्रम सिर्फ अपनी इकाई के लिए चलाता हूं तो मुझे LazyInitializationException और सत्र संग्रह के बाद लोड किए गए अन्य संग्रह मिलते हैं। ऐसा कैसे हो सकता है
सबा सफ़ावी

5

Utils.objectToJson (निकाय) रखें; सत्र बंद होने से पहले फोन करें।

या आप इस तरह के कोड के साथ लाने के लिए लाने की कोशिश कर सकते हैं

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 को हटा दिया गया है। Javadoc अब FetchMode.JOIN का उपयोग करने की अनुशंसा करता है।
एलेक्सिस डुफ्रेनॉय

4

हाइबरनेट 4.1.6 के साथ उन आलसी एसोसिएशन समस्याओं को संभालने के लिए एक नई सुविधा शुरू की गई है। जब आप hibernate.enable_lazy_load_no_trans संपत्ति को hibernate.properties में या hibernate.cfg.xml में सक्षम करते हैं, तो आपके पास कोई LazyInitializationException नहीं होगी।

अधिक संदर्भ के लिए: https://stackoverflow.com/a/11913404/286588


3
यह वास्तव में एक प्रतिमान है। अधिक जानकारी के लिए: vladmihalcea.com/…
Ph03n1x

3

कई संग्रह लाने के लिए, आपको निम्न करना होगा:

  1. एक संग्रह में शामिल हों
  2. Hibernate.initializeशेष संग्रह के लिए उपयोग करें ।

तो, आपके मामले में, आपको इस तरह की पहली JPQL क्वेरी की आवश्यकता है:

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

इस तरह, आप 2 SQL प्रश्नों के साथ अपने लक्ष्य को प्राप्त कर सकते हैं और कार्टेशियन उत्पाद से बच सकते हैं।


हाय Hibernate#initialize(entity.getSubSet())व्लादिम , यह काम करता है अगर मैं फोन करता हूँ अगर getSubSet रिटर्न Collections.unmodifyableSet(this.subSet)। मैंने कोशिश की और यह नहीं किया। अंडरलेइंग कलेक्शन 'पर्सेंटसैट' है। कॉलिंग के साथ एक ही कहानी#size()
वादिम किरिलचुक

लेकिन शायद यह मुद्दा यह है कि मैं बाद में कॉल करता हूं और मेरी बराबरी सीधे फ़ील्ड एक्सेस का उपयोग करती है और
गेटर्स

यह काम करता है यदि आप मेरे उत्तर में दिए गए चरणों का पालन करते हैं।
व्लाद मिहालसीए

2

यह शायद कहीं भी सबसे अच्छा अभ्यास नहीं है, लेकिन मैं आमतौर SIZEपर उसी लेनदेन में बच्चों को लोड करने के लिए संग्रह पर कॉल करता हूं , जैसे आपने सुझाव दिया है। यह बाल तत्वों की संरचना में किसी भी परिवर्तन के लिए स्वच्छ, प्रतिरक्षा है, और कम ओवरहेड के साथ एसक्यूएल उपज देता है।


0

Gsonवस्तुओं को जसन में बदलने के लिए पुस्तकालय का उपयोग करने का प्रयास करें

सर्वलेट्स के साथ उदाहरण:

  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 repository का उपयोग करते हैं, तो properties.put ("hibernate.enable_lazy_load_no_trans", सच) सेट करें; jpaPropertymap के लिए


0

आप @NamedEntityGraphअपनी इकाई के लिए एक लोड करने योग्य क्वेरी बनाने के लिए एनोटेशन का उपयोग कर सकते हैं जो यह निर्धारित करता है कि आप अपनी क्वेरी पर कौन सा संग्रह लोड करना चाहते हैं।

इस पसंद का मुख्य लाभ यह है कि हाइबरनेट इकाई और इसके संग्रह को पुनः प्राप्त करने के लिए एक एकल क्वेरी बनाता है और केवल जब आप इस ग्राफ का उपयोग करना चुनते हैं, तो इस तरह:

इकाई विन्यास

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

प्रयोग

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

जेपीए-हाइबरनेट में आलसी संग्रह के बारे में कुछ गलतफहमी है। सबसे पहले आइए स्पष्ट करें कि आलसी संग्रह को पढ़ने की कोशिश करना अपवाद क्यों है और न केवल NULL को परिवर्तित करने या आगे उपयोग के मामलों के लिए लौटाता है?

ऐसा इसलिए है क्योंकि डेटाबेस में विशेष रूप से शामिल किए गए स्तंभों में अशक्त फ़ील्ड का अर्थ है और प्रोग्रामिंग भाषाओं की तरह बस-प्रस्तुत नहीं किया गया है। जब आप आलसी संग्रह को अशक्त मान की व्याख्या करने की कोशिश कर रहे हैं, तो इसका मतलब है (डेटास्टोर-साइड पर) इन संस्थाओं के बीच कोई संबंध नहीं है और यह सच नहीं है। इसलिए फेंकना अपवाद किसी प्रकार का सर्वोत्तम अभ्यास है और आपको इससे निपटना होगा न कि हाइबरनेट।

जैसा कि ऊपर उल्लेख मैं करने के लिए सलाह देते हैं:

  1. इसे संशोधित करने या क्वेरी के लिए स्टेटलेस सत्र का उपयोग करने से पहले वांछित वस्तु को अलग कर लें
  2. आलसी क्षेत्रों को वांछित मानों (शून्य, शून्य, आदि) से जोड़ दें।

जैसा कि अन्य उत्तरों में बताया गया है कि बहुत सारे दृष्टिकोण (उत्सुक लाने, शामिल होने आदि) या पुस्तकालयों और ऐसा करने के तरीके हैं, लेकिन आपको समस्या से निपटने और इसे हल करने से पहले अपने दृष्टिकोण को स्थापित करना होगा।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.