วิธีการแก้ไข org.hibernate.LazyInitializationException - ไม่สามารถเริ่มต้นพร็อกซี - ไม่มีเซสชัน


188

ฉันได้รับข้อยกเว้นต่อไปนี้:

Exception in thread "main" org.hibernate.LazyInitializationException: could not initialize proxy - no Session
    at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:167)
    at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:215)
    at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:190)
    at sei.persistence.wf.entities.Element_$$_jvstc68_47.getNote(Element_$$_jvstc68_47.java)
    at JSON_to_XML.createBpmnRepresantation(JSON_to_XML.java:139)
    at JSON_to_XML.main(JSON_to_XML.java:84)

เมื่อฉันพยายามโทรจากสายหลักดังต่อไปนี้:

Model subProcessModel = getModelByModelGroup(1112);
System.out.println(subProcessModel.getElement().getNote());

ฉันใช้getModelByModelGroup(int modelgroupid)วิธีแรกเช่นนี้

public static Model getModelByModelGroup(int modelGroupId, boolean openTransaction) {

    Session session = SessionFactoryHelper.getSessionFactory().getCurrentSession();     
    Transaction tx = null;

    if (openTransaction) {
        tx = session.getTransaction();
    }

    String responseMessage = "";

    try {
        if (openTransaction) {
            tx.begin();
        }
        Query query = session.createQuery("from Model where modelGroup.id = :modelGroupId");
        query.setParameter("modelGroupId", modelGroupId);

        List<Model> modelList = (List<Model>)query.list(); 
        Model model = null;

        for (Model m : modelList) {
            if (m.getModelType().getId() == 3) {
                model = m;
                break;
            }
        }

        if (model == null) {
            Object[] arrModels = modelList.toArray();
            if (arrModels.length == 0) {
                throw new Exception("Non esiste ");
            }

            model = (Model)arrModels[0];
        }

        if (openTransaction) {
            tx.commit();
        }

        return model;

   } catch(Exception ex) {
       if (openTransaction) {
           tx.rollback();
       }
       ex.printStackTrace();
       if (responseMessage.compareTo("") == 0) {
           responseMessage = "Error" + ex.getMessage();
       }
       return null;
    }
}

และได้รับการยกเว้น จากนั้นเพื่อนแนะนำให้ฉันทดสอบเซสชันและรับเซสชันปัจจุบันเสมอเพื่อหลีกเลี่ยงข้อผิดพลาดนี้ ดังนั้นฉันทำสิ่งนี้:

public static Model getModelByModelGroup(int modelGroupId) {
    Session session = null;
    boolean openSession = session == null;
    Transaction tx = null;
    if (openSession) {
        session = SessionFactoryHelper.getSessionFactory().getCurrentSession(); 
        tx = session.getTransaction();
    }
    String responseMessage = "";

    try {
        if (openSession) {
            tx.begin();
        }
        Query query = session.createQuery("from Model where modelGroup.id = :modelGroupId");
        query.setParameter("modelGroupId", modelGroupId);

        List<Model> modelList = (List<Model>)query.list(); 
        Model model = null;

        for (Model m : modelList) {
            if (m.getModelType().getId() == 3) {
                model = m;
                break;
            }
        }

        if (model == null) {
            Object[] arrModels = modelList.toArray();
            if (arrModels.length == 0) {
                throw new RuntimeException("Non esiste");
            }

            model = (Model)arrModels[0];

            if (openSession) {
                tx.commit();
            }
            return model;
        } catch(RuntimeException ex) {
            if (openSession) {
                tx.rollback();
            }
            ex.printStackTrace();
            if (responseMessage.compareTo("") == 0) {
                responseMessage = "Error" + ex.getMessage();
            }
            return null;        
        }
    }
}

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

คำตอบ:


93

มีอะไรผิดปกติที่นี่คือการกำหนดค่าการจัดการเซสชันของคุณถูกตั้งค่าเป็นปิดเซสชันเมื่อคุณยอมรับธุรกรรม ตรวจสอบว่าคุณมีสิ่งที่ชอบ:

<property name="current_session_context_class">thread</property>

ในการกำหนดค่าของคุณ

เพื่อที่จะเอาชนะปัญหานี้คุณสามารถเปลี่ยนการกำหนดค่าของโรงงานเซสชั่นหรือเปิดเซสชั่นอื่นและเพียงแค่ขอวัตถุขี้เกียจโหลดเหล่านั้น แต่สิ่งที่ฉันจะแนะนำที่นี่คือการเริ่มต้นคอลเลกชันขี้เกียจนี้ใน getModelByModelGroup ตัวเองและโทร:

Hibernate.initialize(subProcessModel.getElement());

เมื่อคุณยังอยู่ในเซสชันที่ใช้งานอยู่

และสิ่งสุดท้ายที่ คำแนะนำที่เป็นมิตร คุณมีสิ่งนี้ในวิธีการของคุณ:

for (Model m : modelList) {
    if (m.getModelType().getId() == 3) {
        model = m;
        break;
    }
}

โปรดติดตั้งโค้ดนี้เพียงกรองโมเดลเหล่านั้นด้วยรหัสประเภทเท่ากับ 3 ในข้อความสั่งแบบสอบถามเพียงไม่กี่บรรทัดด้านบน

อ่านเพิ่มเติม:

การกำหนดค่าจากโรงงานเซสชัน

ปัญหากับเซสชั่นปิด


1
ขอบคุณ! ฉันแก้ไขปัญหาของฉันโดยใช้ openSession () แทนที่จะเป็น getCurrentSession () เป็นหนึ่งในลิงก์ที่คุณให้คำแนะนำกับฉัน แต่ตอนนี้ฉันกลัวว่ามันผิดที่จะทำเช่นนั้น
Blerta Dhimitri

2
ไม่น่าจะดี แต่อ่านเพิ่มเติมเพื่อให้สามารถควบคุมเซสชันและธุรกรรมของคุณได้อย่างเต็มที่ เป็นเรื่องสำคัญที่จะต้องรู้พื้นฐานเพราะเทคโนโลยีระดับสูงทั้งหมดเช่น Spring, Hibernate และทำงานได้มากขึ้นในแนวคิดเดียวกัน
goroncy

179

หากคุณใช้ Spring ทำเครื่องหมายคลาสเป็น@Transactionalดังนั้น Spring จะจัดการเซสชั่น

@Transactional
public class MyClass {
    ...
}

โดยการใช้@Transactionalงานสิ่งสำคัญมากมายเช่นการเผยแพร่ธุรกรรมจะได้รับการจัดการโดยอัตโนมัติ ในกรณีนี้หากวิธีการทำธุรกรรมอื่นเรียกว่าวิธีการจะมีตัวเลือกในการเข้าร่วมการทำธุรกรรมอย่างต่อเนื่องหลีกเลี่ยงข้อยกเว้น "ไม่มีเซสชั่น"

คำเตือนหากคุณใช้งาน@Transactionalโปรดระวังพฤติกรรมที่เกิดขึ้น ดูบทความนี้สำหรับข้อผิดพลาดทั่วไป ตัวอย่างเช่นการอัพเดตเอนทิตีจะยังคงอยู่แม้ว่าคุณจะไม่ได้โทรออกอย่างชัดเจนsave


21
ฉันไม่สามารถพูดเกินความสำคัญของคำตอบนี้ได้ ฉันขอแนะนำให้ลองใช้ตัวเลือกนี้อย่างจริงจังก่อน
sparkyspider

6
นอกจากนี้โปรดทราบว่าคุณต้องเพิ่ม@EnableTransactionManagementการกำหนดค่าของคุณเพื่อเปิดใช้งานการทำธุรกรรม " หากวิธีการทำธุรกรรมอื่นเรียกว่าวิธีการจะมีตัวเลือกในการเข้าร่วมการทำธุรกรรมอย่างต่อเนื่อง " พฤติกรรมนี้แตกต่างกันสำหรับวิธีการทำธุรกรรมที่แตกต่างกันเช่นอินเตอร์เฟสพร็อกซี vs คลาสพร็อกซีหรือคลาสของ AspectJ อ้างถึงเอกสาร
Erik Hofer

1
เราควรเข้าใจหรือไม่ว่าTransactionalคำอธิบายประกอบSpring นั้นไม่เพียง แต่สำหรับการปรับเปลี่ยนธุรกรรม แต่ยังสำหรับการเข้าถึงรายการเท่านั้น?
Stephane

8
ฉันขอแนะนำอย่างจริงจังให้ใช้คำอธิบายประกอบนี้ที่ด้านบนของชั้นเรียนเท่านั้นสำหรับการทดสอบ รหัสจริงควรทำเครื่องหมายแต่ละวิธีเป็นธุรกรรมในชั้นเรียนแยกต่างหาก นอกจากวิธีการทั้งหมดในชั้นเรียนจะต้องเปิดการเชื่อมต่อกับการทำธุรกรรมกับฐานข้อมูล
m1ld

1
@Transactional (readOnly = true) ปลอดภัยหรือไม่แทนที่จะใช้ @Transactional
Hamedz

105

คุณสามารถลองตั้งค่า

<property name="hibernate.enable_lazy_load_no_trans">true</property>

ใน hibernate.cfg.xml หรือ persistence.xml

ปัญหาที่ควรทราบเกี่ยวกับคุณสมบัตินี้ได้อธิบายไว้ที่นี่


8
คุณสามารถอธิบายความหมายของมันได้หรือไม่
Mohit Kanwar

2
ฉันยังอยากรู้ว่ามันทำอะไร มันแก้ไขปัญหาที่ฉันมี แต่ฉันอยากจะเข้าใจว่าทำไม
Hassan

3
สำหรับ persistence.xml:<property name="hibernate.enable_lazy_load_no_trans" value="true"/>
ACV

33
คุณทราบหรือไม่ว่าhibernate.enable_lazy_load_no_transรูปแบบต่อต้านถูกต้องใช่ไหม
Vlad Mihalcea

6
อย่าใช้คุณสมบัตินี้หาก SPRING จัดการธุรกรรมของคุณคุณสมบัตินี้จะนำไปสู่การระเบิดการทำธุรกรรมเพียงแค่ SPRING ง่าย ๆ จะปิดแอปพลิเคชัน
คุณ

54

วิธีที่ดีที่สุดในการจัดการLazyInitializationExceptionคือการใช้JOIN FETCHคำสั่ง:

Query query = session.createQuery(
    "from Model m " +
    "join fetch m.modelType " +
    "where modelGroup.id = :modelGroupId"
);

อย่างไรก็ตามอย่าใช้ Anti-Patterns ต่อไปนี้ตามคำแนะนำของคำตอบ:

บางครั้งการฉาย DTOเป็นทางเลือกที่ดีกว่าการดึงเอนทิตีและด้วยวิธีนี้คุณจะไม่ได้อะไรLazyInitializationExceptionเลย


ฉันจะระบุได้อย่างไรว่าการโทรใดกำลังประสบปัญหา ฉันพบว่ามันยากที่จะระบุสาย มีวิธีใดบ้าง เพื่อจุดประสงค์ในการทดสอบฉันใช้FetchType=EAGERแต่นี่ไม่ใช่วิธีแก้ไขที่ถูกต้องใช่ไหม
Shantaram Tupe

เพียงใช้การบันทึก และนกอินทรีก็ไม่ดีใช่
Vlad Mihalcea

จากนั้นคุณควรใช้ DTO หรือเริ่มต้นการเชื่อมโยงทั้งหมดก่อนออกจาก@Transactionalบริการ
Vlad Mihalcea

2
เราควรสนับสนุนแนวทางปฏิบัติขององค์กรที่ดีที่สุด แต่ไม่ใช่การแก้ไขด่วน
etlds

21

ฉันได้รับข้อผิดพลาดเดียวกันสำหรับความสัมพันธ์แบบหนึ่งถึงหลายรายการสำหรับคำอธิบายประกอบด้านล่าง

@OneToMany(mappedBy="department", cascade = CascadeType.ALL)

เปลี่ยนเป็นด้านล่างหลังจากเพิ่ม fetch = FetchType.EAGER มันใช้งานได้สำหรับฉัน

@OneToMany(mappedBy="department", cascade = CascadeType.ALL, fetch=FetchType.EAGER)

26
ใช่มันอาจแก้ไขได้ แต่ตอนนี้คุณกำลังโหลดทรีของข้อมูลทั้งหมด ซึ่งจะมีผลกระทบด้านลบต่อประสิทธิภาพในกรณีส่วนใหญ่
astro8891

13

หากคุณใช้สปริงข้อมูล jpa สปริงบูตคุณสามารถเพิ่มบรรทัดนี้ใน application.properties

spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

7
นี่ถือเป็นรูปแบบการต่อต้าน vladmihalcea.com/…
Sudip Bhandari

9

ข้อยกเว้นนี้เนื่องจากเมื่อคุณโทรsession.getEntityById()เซสชันจะถูกปิด ดังนั้นคุณต้องแนบเอนทิตีกับเซสชันอีกครั้ง หรือวิธีแก้ปัญหาอย่างง่ายคือกำหนดค่าdefault-lazy="false" ให้กับคุณentity.hbm.xmlหรือหากคุณใช้คำอธิบายประกอบเพียงแค่เพิ่ม@Proxy(lazy=false)ไปยังคลาสเอนทิตีของคุณ


5

ฉันพบปัญหาเดียวกัน ฉันคิดว่าอีกวิธีหนึ่งในการแก้ไขปัญหานี้คือคุณสามารถเปลี่ยนแบบสอบถามเพื่อเข้าร่วมดึงข้อมูลองค์ประกอบของคุณจากโมเดลดังนี้:

Query query = session.createQuery("from Model m join fetch m.element where modelGroup.id = :modelGroupId")

4

ซึ่งหมายความว่าไม่ได้โหลดวัตถุที่คุณพยายามเข้าถึงดังนั้นให้เขียนแบบสอบถามที่ทำให้การดึงข้อมูลเข้าร่วมของวัตถุที่คุณพยายามเข้าถึง

เช่น:

หากคุณพยายามรับ ObjectB จาก ObjectA โดยที่ ObjectB เป็น foreign key ใน ObjectA

คำค้นหา:

SELECT objA FROM ObjectA obj JOIN FETCH obj.objectB objB

3

มีคำตอบที่ดีมากมายที่นี่ซึ่งจัดการข้อผิดพลาดนี้ในขอบเขตกว้าง ๆ ฉันวิ่งเข้าไปในสถานการณ์เฉพาะกับ Spring Security ซึ่งมีความรวดเร็วแม้ว่าอาจจะไม่เหมาะสมก็ตาม

ระหว่างการอนุญาตผู้ใช้ (ทันทีหลังจากเข้าสู่ระบบและผ่านการรับรองความถูกต้อง) ฉันกำลังทดสอบเอนทิตีผู้ใช้สำหรับสิทธิ์เฉพาะในคลาสแบบกำหนดเองที่ขยาย SimpleUrlAuthenticationSuccessHandler

เอนทิตีผู้ใช้ของฉันใช้ UserDetails และมีชุดของบทบาทที่โหลดขี้เกียจซึ่งโยนข้อผิดพลาด "org.hibernate.LazyInitializationException" - ไม่สามารถเริ่มต้นพร็อกซี - ไม่มีเซสชัน "ข้อยกเว้น การเปลี่ยนชุดจาก "fetch = FetchType.LAZY" เป็น "fetch = FetchType.EAGER" แก้ไขสิ่งนี้ให้ฉัน



2

ประสบข้อยกเว้นเดียวกันในกรณีการใช้งานที่แตกต่างกัน

ป้อนคำอธิบายรูปภาพที่นี่

ใช้กรณี:ลองอ่านข้อมูลจาก DB ด้วยการฉาย DTO

การแก้ไข:การใช้งานได้รับวิธีการแทนการโหลด

การใช้งานทั่วไป

public class HibernateTemplate {
public static Object loadObject(Class<?> cls, Serializable s) {
    Object o = null;
    Transaction tx = null;
    try {
        Session session = HibernateUtil.getSessionFactory().openSession();
        tx = session.beginTransaction();
        o = session.load(cls, s); /*change load to get*/
        tx.commit();
        session.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return o;
}

}

ระดับความคงทน

public class Customer {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "Id")
private int customerId;

@Column(name = "Name")
private String customerName;

@Column(name = "City")
private String city;

//constructors , setters and getters

}

ส่วนต่อประสานลูกค้า (CustomerDAO)

public interface CustomerDAO 
     {
   public CustomerTO getCustomerById(int cid);
     }

Entity Transfer Object Class

public class CustomerTO {

private int customerId;

private String customerName;

private String city;

//constructors , setters and getters

}

ระดับโรงงาน

public class DAOFactory {

static CustomerDAO customerDAO;
static {
    customerDAO = new HibernateCustomerDAO();
}

public static CustomerDAO getCustomerDAO() {
    return customerDAO;
}

}

DAO เฉพาะเอนทิตี

public class HibernateCustomerDAO implements CustomerDAO {

@Override
public CustomerTO getCustomerById(int cid) {
    Customer cust = (Customer) HibernateTemplate.loadObject(Customer.class, cid);
    CustomerTO cto = new CustomerTO(cust.getCustomerId(), cust.getCustomerName(), cust.getCity());
    return cto;
}

}

การดึงข้อมูล: คลาสทดสอบ

CustomerDAO cdao = DAOFactory.getCustomerDAO();
CustomerTO c1 = cdao.getCustomerById(2);
System.out.println("CustomerName -> " + c1.getCustomerName() + " ,CustomerCity -> " + c1.getCity());

นำเสนอข้อมูล

ป้อนคำอธิบายรูปภาพที่นี่

ข้อความค้นหาและผลลัพธ์ที่สร้างขึ้นโดยระบบไฮเบอร์เนต

ไฮเบอร์เนต: เลือก customer0_.Id เป็น Id1_0_0_, customer0_.City as City2_0_0_, customer0_.Name เป็น Name3_0_0_ จาก CustomerLab31 customer0_ โดยที่ customer0_.Id =?

CustomerName -> Cody, CustomerCity -> LA


1

หากคุณใช้Grail'sFramework คุณสามารถแก้ไขข้อยกเว้นในการเริ่มต้นสันหลังยาวได้โดยง่ายLazyคีย์เวิร์ดในฟิลด์ที่ระบุใน Domain Class

ตัวอย่างเช่น:

class Book {
    static belongsTo = [author: Author]
    static mapping = {
        author lazy: false
    }
}

ค้นหาข้อมูลเพิ่มเติมได้ที่นี่



1

ซึ่งหมายความว่าคุณกำลังใช้ JPA หรือไฮเบอร์เนตในรหัสของคุณและทำการแก้ไขการดำเนินการบนฐานข้อมูลโดยไม่ต้องทำธุรกรรมทางธุรกิจเชิงตรรกะ วิธีแก้ปัญหาง่ายๆนี้คือทำเครื่องหมายส่วนของรหัส @Transactional



-2

คุณสามารถแก้ไขได้โดยเพิ่ม lazy = false เข้าไปในไฟล์ * .hbm.xml ของคุณหรือคุณสามารถเริ่มต้นวัตถุของคุณใน Hibernate.init (Object) เมื่อคุณได้รับวัตถุจาก db


10
โดยทั่วไปการเพิ่ม lazy = false ไม่ใช่ความคิดที่ดี นั่นคือเหตุผลที่ความเกียจคร้านเป็นความจริงโดยค่าเริ่มต้น
MoienGK

OP บอกไว้ชัดเจนว่าเขาไม่ได้รับอนุญาตให้ทำเช่นนั้น
GingerHead

-2

ทำการเปลี่ยนแปลงต่อไปนี้ในservlet-context.xml

    <beans:property name="hibernateProperties">
        <beans:props>

            <beans:prop key="hibernate.enable_lazy_load_no_trans">true</beans:prop>

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