ไฮเบอร์เนต: แนวทางปฏิบัติที่ดีที่สุดในการดึงคอลเลกชันขี้เกียจทั้งหมด


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
}

มีปัญหาอะไร:

ปัญหาคือฉันไม่สามารถดึงคอลเลกชันขี้เกียจได้หลังจากปิดเซสชันแล้ว แต่ฉันยังไม่สามารถปิดเซสชันในวิธีดำเนินการได้

วิธีแก้ปัญหาคืออะไร (วิธีแก้ปัญหาหยาบ):

ก) ก่อนปิดเซสชันบังคับให้ไฮเบอร์เนตดึงคอลเลกชันขี้เกียจ

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
มันดูน่าสนใจ) อย่างที่ฉันเข้าใจว่าฉันจะใช้ @Fetch (FetchMode.SUBSELECT) หรือไม่กว่าที่ฉันจะโทรหา Hibernate.initialize เพียงครั้งเดียวเพื่อดึงคอลเล็กชันทั้งหมด ฉันถูกไหม?
VB_

4
และคุณจะจัดการอย่างไรเมื่อดึงคอลเล็กชัน MyEntity
Alexis Dufrenoy

1
หากคุณเรียกเมธอดใด ๆ เช่น "size ()" บนคอลเลกชันในธุรกรรมก็จะเริ่มต้นด้วยดังนั้นตัวอย่างของคุณหลังจากการเริ่มต้นของคุณจึงไม่ใช่วิธีที่ดีที่สุด สิ่งนี้กล่าวว่า "Hibernate.initialize (... )" นั้นดีกว่าในเชิงความหมายจากนั้นก็คือ collection.size () ดังนั้นคุณมีคำแนะนำที่ดีที่สุด
Tristan

7

คุณสามารถสำรวจผ่านวัตถุ Getters of the Hibernate ในธุรกรรมเดียวกันเพื่อให้มั่นใจว่าวัตถุลูกที่ขี้เกียจทั้งหมดจะถูกดึงมาอย่างกระตือรือร้นด้วยคลาสตัวช่วยทั่วไปต่อไปนี้:

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

ขอบคุณสำหรับคำตอบนี้ ฉันรู้ว่ามันผ่านมาสักพักแล้ว แต่ฉันพยายามแก้ปัญหานี้และมันก็ช้าไปจนกระทั่งฉันอ่านโค้ดของคุณที่นี่ ฉันยังเพิ่ม ifs ที่จุดเริ่มต้นของวิธีที่สอง 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; } ซ้ำรายการที่ถูกละเว้น
ชิป

จะเกิดอะไรขึ้นถ้า SecurityException ถูกโยนที่ o.getClass (). getMethods ();?
Oleksii Kyslytsyn

6

ไม่ใช่ทางออกที่ดีที่สุด แต่นี่คือสิ่งที่ฉันได้รับ:

1) คำอธิบายประกอบที่คุณต้องการเริ่มต้นด้วยคำอธิบายประกอบนี้:

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

}

2) ใช้วิธีนี้ (สามารถใส่ในคลาสทั่วไปหรือคุณสามารถเปลี่ยน T ด้วยคลาส Object) บนวัตถุหลังจากที่คุณอ่านจากฐานข้อมูล:

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

ฉันใช้ session.refresh ในการทำซ้ำเพื่อโหลด lazyCollections และทุกครั้งที่ฉันเรียกใช้โปรแกรมสำหรับเอนทิตีหนึ่งของฉันฉันได้รับ LazyInitializationException และคอลเลกชันอื่น ๆ โหลดหลังจากเรียกใช้ session.refresh สิ่งนี้เกิดขึ้นได้อย่างไร
saba safavi

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 ตอนนี้
Alexis Dufrenoy

4

ด้วย Hibernate 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);

ด้วยวิธีนี้คุณจะบรรลุเป้าหมายได้ด้วยการสืบค้น SQL 2 รายการและหลีกเลี่ยงผลิตภัณฑ์คาร์ทีเซียน


สวัสดีวลาดมันได้ผลไหมถ้าฉันโทรหาHibernate#initialize(entity.getSubSet())ถ้า getSubSet กลับ Collections.unmodifyableSet(this.subSet)มา ฉันพยายามแล้วก็ไม่ได้ Underlaying collection คือ 'PersistentSet' เรื่องเดียวกันกับการโทร#size()
Vadim Kirilchuk

แต่บางทีปัญหาก็คือฉันเรียกว่ามีในภายหลังและเท่ากับของฉันใช้การเข้าถึงฟิลด์โดยตรงและไม่ใช่ตัวรับ ..
Vadim Kirilchuk

ได้ผลถ้าคุณทำตามขั้นตอนที่ให้ไว้ในคำตอบของฉัน
Vlad Mihalcea

2

อาจไม่ใช่ที่ใดก็ตามที่เข้าใกล้แนวทางปฏิบัติที่ดีที่สุด แต่ฉันมักจะเรียกSIZEคอลเลกชันเพื่อโหลดเด็กในธุรกรรมเดียวกันอย่างที่คุณแนะนำ มันสะอาดไม่มีภูมิคุ้มกันต่อการเปลี่ยนแปลงใด ๆ ในโครงสร้างขององค์ประกอบลูกและให้ SQL ที่มีค่าโสหุ้ยต่ำ


0

ลองใช้Gsonไลบรารีเพื่อแปลงวัตถุเป็น Json

ตัวอย่างกับ servlets:

  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 ให้ตั้งค่า properties.put ("hibernate.enable_lazy_load_no_trans", true); เป็น 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

มีความเข้าใจผิดบางอย่างเกี่ยวกับคอลเลกชันขี้เกียจใน JPA-Hibernate ก่อนอื่นเรามาดูกันดีกว่าว่า เหตุใดการพยายามอ่านคอลเลกชันที่ขี้เกียจจึงทำให้เกิดข้อยกเว้นและไม่เพียงส่งคืนค่า NULL สำหรับการแปลงหรือกรณีการใช้งานเพิ่มเติม .

นั่นเป็นเพราะเขตข้อมูล Null ในฐานข้อมูลโดยเฉพาะอย่างยิ่งในคอลัมน์ที่เข้าร่วมมีความหมายและไม่ใช่แค่สถานะที่ไม่ได้นำเสนอเช่นภาษาโปรแกรม เมื่อคุณพยายามตีความคอลเลกชัน lazy เป็นค่า Null หมายความว่า (บนฝั่ง Datastore) ไม่มีความสัมพันธ์ระหว่างเอนทิตีเหล่านี้และไม่เป็นความจริง ดังนั้นข้อยกเว้นในการขว้างปาจึงเป็นแนวทางปฏิบัติที่ดีที่สุดและคุณต้องจัดการกับสิ่งนั้นไม่ใช่ไฮเบอร์เนต

ดังที่ได้กล่าวไว้ข้างต้นฉันขอแนะนำให้:

  1. ถอดออบเจ็กต์ที่ต้องการออกก่อนที่จะแก้ไขหรือใช้เซสชันแบบไม่ระบุสถานะสำหรับการสืบค้น
  2. จัดการช่องขี้เกียจให้เป็นค่าที่ต้องการ (ศูนย์ค่าว่าง ฯลฯ )

ตามที่อธิบายไว้ในคำตอบอื่น ๆ มีวิธีการมากมาย (การดึงข้อมูลการเข้าร่วม ฯลฯ ) หรือไลบรารีและวิธีการทำเช่นนั้น แต่คุณต้องตั้งค่ามุมมองของคุณว่าเกิดอะไรขึ้นก่อนที่จะจัดการกับปัญหาและแก้ไข

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.