วิธีการแก้ปัญหา“ ล้มเหลวในการเริ่มต้นการรวบรวมบทบาท” อย่างเฉื่อยชายกเว้นไฮเบอร์เนต


363

ฉันมีปัญหานี้:

org.hibernate.LazyInitializationException: ล้มเหลวในการเริ่มต้นการรวบรวมบทบาทอย่างเกียจคร้าน: mvc3.model.Topic.comments ไม่มีเซสชันหรือเซสชันถูกปิด

นี่คือรูปแบบ:

@Entity
@Table(name = "T_TOPIC")
public class Topic {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int id;

    @ManyToOne
    @JoinColumn(name="USER_ID")
    private User author;

    @Enumerated(EnumType.STRING)    
    private Tag topicTag;

    private String name;
    private String text;

    @OneToMany(mappedBy = "topic", cascade = CascadeType.ALL)
    private Collection<Comment> comments = new LinkedHashSet<Comment>();

    ...

    public Collection<Comment> getComments() {
           return comments;
    }

}

คอนโทรลเลอร์ซึ่งเรียกโมเดลมีลักษณะดังต่อไปนี้:

@Controller
@RequestMapping(value = "/topic")
public class TopicController {

    @Autowired
    private TopicService service;

    private static final Logger logger = LoggerFactory.getLogger(TopicController.class);


    @RequestMapping(value = "/details/{topicId}", method = RequestMethod.GET)
    public ModelAndView details(@PathVariable(value="topicId") int id)
    {

            Topic topicById = service.findTopicByID(id);
            Collection<Comment> commentList = topicById.getComments();

            Hashtable modelData = new Hashtable();
            modelData.put("topic", topicById);
            modelData.put("commentList", commentList);

            return new ModelAndView("/topic/details", modelData);

     }

}

หน้า jsp มีลักษณะดังต่อไปนี้:

<%@page import="com.epam.mvc3.helpers.Utils"%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
      <title>View Topic</title>
</head>
<body>

<ul>
<c:forEach items="${commentList}" var="item">
<jsp:useBean id="item" type="mvc3.model.Comment"/>
<li>${item.getText()}</li>

</c:forEach>
</ul>
</body>
</html>

มีการยกเว้นข้อยกเว้นเมื่อดู jsp อยู่ในแนวเดียวกับc: forEach loop

คำตอบ:


214

หากคุณรู้ว่าคุณต้องการเห็นCommentทุกครั้งที่คุณเรียกค้นข้อมูลTopicจากนั้นเปลี่ยนการแมปฟิลด์ของคุณcommentsเป็น:

@OneToMany(fetch = FetchType.EAGER, mappedBy = "topic", cascade = CascadeType.ALL)
private Collection<Comment> comments = new LinkedHashSet<Comment>();

คอลเลคชั่นจะถูกโหลดขี้เกียจโดยค่าเริ่มต้นดูที่นี่หากคุณต้องการทราบข้อมูลเพิ่มเติม


35
ขออภัยฉันต้องการใช้ lazy-load ดังนั้นฉันได้เปลี่ยน 'LinkedHashSet' พิมพ์ t 'PersistentList' ข้อยกเว้นยังคงเกิดขึ้น
Eugene

242
สิ่งนี้สามารถใช้เป็นวิธีแก้ปัญหา แต่ไม่ใช่วิธีแก้ไขปัญหาที่แท้จริง เกิดอะไรขึ้นถ้าเราต้องดึงอย่างเกียจคร้าน?
Dkyc

14
แต่ในกรณีถ้าเราต้องการขี้เกียจการแก้ปัญหานี้จะไม่ทำงานและในกรณีส่วนใหญ่เราต้องการขี้เกียจเท่านั้น
Thashre Thashre

103
นี่คือประเภทของคำตอบที่ปรากฏขึ้นทุกหนทุกแห่งบนสแต็กล้น สั้นตรงประเด็นแก้ปัญหาและการทำผิดพลาด สำหรับผู้อ่านในอนาคตให้ทำตัวเองชอบและเรียนรู้สิ่งที่ขี้เกียจและกระตือรือร้นดึงและเข้าใจผลที่ตามมา
Ced

13
@darrengorman เมื่อฉันเริ่มต้น JPA ฉันโพสต์คำถามรอบ ๆ บรรทัดของ OP ฉันได้รับการตอบกลับเช่นเดียวกับที่คุณให้ เร็ว ๆ นี้เมื่อฉันวิ่งทดสอบด้วยแถวหลายแสนแถวเดาว่าเกิดอะไรขึ้น ฉันคิดว่ามันทำให้เข้าใจผิดเพราะมันให้คำตอบที่ง่ายเกินไปสำหรับปัญหาที่ผู้เริ่มต้นส่วนใหญ่จะเผชิญและในไม่ช้าพวกเขาก็จะมีฐานข้อมูลทั้งหมดโหลดอยู่ในหน่วยความจำหากพวกเขาไม่ระวัง (และพวกเขาจะไม่ ระวังให้ดี) :)
Ced

182

จากประสบการณ์ของฉันฉันมีวิธีการดังต่อไปนี้เพื่อแก้ไข LazyInitializationException ที่มีชื่อเสียง:

(1) ใช้ Hibernate เริ่มต้น

Hibernate.initialize(topics.getComments());

(2) ใช้ JOIN FETCH

คุณสามารถใช้ไวยากรณ์ JOIN FETCH ใน JPQL ของคุณเพื่อดึงคอลเล็กชันลูกออกได้อย่างชัดเจน นี่คือวิธีดึงข้อมูลจาก EAGER

(3) ใช้ OpenSessionInViewFilter

LazyInitializationException มักเกิดขึ้นในมุมมองเลเยอร์ หากคุณใช้ Spring Framework คุณสามารถใช้ OpenSessionInViewFilter อย่างไรก็ตามฉันไม่แนะนำให้คุณทำเช่นนั้น มันอาจนำไปสู่ปัญหาประสิทธิภาพการทำงานหากไม่ได้ใช้อย่างถูกต้อง


5
(1) ทำงานให้ฉันอย่างสมบูรณ์แบบ กรณีของฉัน: Hibernate.initialize (registry.getVehicle (). getOwner (). getPerson (). getAddress ());
Leonel Sanches da Silva

6
ดูเหมือนว่า Hibernate.initialize ใช้ไม่ได้กับ EntityManager
marionmaiden

8
นี่ควรเป็นคำตอบที่ถูกต้อง ตัวอย่างเช่นในโครงการของฉันในที่ทำงานเราไม่ควรใช้การดึงข้อมูลของ EAGER อย่างชัดเจน มันทำให้เกิดปัญหาในระบบนี้โดยเฉพาะ
Steve Waters

ดูเหมือนว่าน่าสนใจ แต่ไม่มีเอกสารที่จะใช้ในกรณีอื่น ... คุณช่วยจัดหาลิงค์หรือคำอธิบายเพิ่มเติมเกี่ยวกับวิธีใช้โซลูชันนี้ได้ไหม?
Pipo

58

ฉันรู้ว่ามันเป็นคำถามเก่า แต่ฉันต้องการความช่วยเหลือ คุณสามารถใส่คำอธิบายประกอบการทำธุรกรรมในวิธีการบริการที่คุณต้องการในกรณีนี้ findTopicByID (id) ควรมี

@Transactional(propagation=Propagation.REQUIRED, readOnly=true, noRollbackFor=Exception.class)

ดูข้อมูลเพิ่มเติมเกี่ยวกับคำอธิบายประกอบนี้ได้ที่นี่

เกี่ยวกับโซลูชันอื่น ๆ :

fetch = FetchType.EAGER 

ไม่ใช่วิธีปฏิบัติที่ดีควรใช้เมื่อจำเป็นเท่านั้น

Hibernate.initialize(topics.getComments());

initializer ไฮเบอร์เนตจะผูกคลาสของคุณเข้ากับเทคโนโลยีไฮเบอร์เนต หากคุณตั้งเป้าหมายที่จะยืดหยุ่นไม่ใช่วิธีที่ดีที่จะไป

หวังว่ามันจะช่วย


3
คำอธิบายประกอบ @Transactional ทำงานให้ฉัน แต่โปรดทราบว่า Propagation.REQUIRED เป็นค่าเริ่มต้นอย่างน้อยใน Spring Boot 1.4.2 (Spring 4.3)
ben3000

4
ใช่มันเป็น แต่ฉันคิดว่ามันจะได้รับการชื่นชมที่จะทำให้มันชัดเจนว่าคุณสามารถเปลี่ยนการแพร่กระจายของจริง
sarbuLopex

ไม่ใช่@Transactionalฤดูใบไม้ผลิเท่านั้นใช่ไหม
Campa

@Campa ใช่มันเป็น หากคุณต้องการจัดการด้วยตนเองคุณควรวางตรรกะทางธุรกิจของคุณไว้ในธุรกรรมที่ดึงมาจากตัวจัดการเอนทิตี
sarbuLopex

54

ต้นกำเนิดของปัญหาของคุณ:

โดยค่าเริ่มต้น hibernate lazily จะโหลดคอลเลกชัน (ความสัมพันธ์) ซึ่งหมายความว่าเมื่อใดที่คุณใช้collectionในโค้ดของคุณ (ที่นี่commentsในTopicชั้นเรียน) hibernate จะได้รับจากฐานข้อมูลตอนนี้ปัญหาคือว่าคุณได้รับคอลเล็กชันในคอนโทรลเลอร์ของคุณ ถูกปิด) นี่คือบรรทัดของรหัสที่ทำให้เกิดข้อยกเว้น (ที่คุณกำลังโหลดcommentsคอลเลกชัน):

    Collection<Comment> commentList = topicById.getComments();

คุณได้รับ "ข้อคิดเห็น" คอลเลกชัน (topic.getComments ()) ในตัวควบคุมของคุณ (ที่JPA sessionสิ้นสุด) และที่ทำให้เกิดข้อยกเว้น นอกจากนี้หากคุณมีcommentsคอลเลกชันในไฟล์ jsp ของคุณเช่นนี้ (แทนที่จะเก็บไว้ในคอนโทรลเลอร์ของคุณ):

<c:forEach items="topic.comments" var="item">
//some code
</c:forEach>

คุณจะยังคงมีข้อยกเว้นเดียวกันด้วยเหตุผลเดียวกัน

การแก้ปัญหา:

เนื่องจากคุณสามารถมีเพียงสองคอลเลกชันที่มีFetchType.Eager(คอลเลกชันที่ดึงมาอย่างกระตือรือร้น) ในคลาส Entity และเนื่องจากการโหลดแบบสันหลังยาวนั้นมีประสิทธิภาพมากกว่าการโหลดแบบโลดโผนฉันคิดว่าวิธีแก้ปัญหาของคุณดีกว่าการเปลี่ยนเป็นFetchTypeความกระตือรือร้น:

หากคุณต้องการให้มีการเริ่มต้นการรวบรวมขี้เกียจและทำให้งานนี้เป็นการดีกว่าที่จะเพิ่มโค้ดนี้ในweb.xml:

<filter>
    <filter-name>SpringOpenEntityManagerInViewFilter</filter-name>
    <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>SpringOpenEntityManagerInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

สิ่งที่รหัสนี้ทำคือมันจะเพิ่มความยาวของคุณJPA sessionหรือตามที่เอกสารระบุว่าจะใช้"to allow for lazy loading in web views despite the original transactions already being completed."ดังนั้นวิธีนี้เซสชัน JPA จะเปิดอีกเล็กน้อยและเนื่องจากคุณสามารถโหลดคอลเลกชันอย่างเกียจคร้านในไฟล์ jsp และคลาสตัวควบคุมของคุณ .


7
เหตุใดเซสชัน JPS จึงถูกปิด วิธีที่จะทำให้มันไม่ถูกปิด? วิธีการทำคอลเลกชันขี้เกียจ?
Dims

1
อะไรคือขีด จำกัด ของสองคอลเลกชัน FetchType.Eager ต่อเอนทิตี
chrisinmtown

ใน Spring Boot คุณสามารถเพิ่ม 'spring.jpa.open-in-view = true' เป็น 'application.properties'
Askar

28

เหตุผลคือเมื่อคุณใช้ lazy load เซสชันถูกปิด

มีสองวิธีแก้ไข

  1. อย่าใช้ความขี้เกียจ

    ตั้งค่าเป็นlazy=falseXML หรือตั้งค่า@OneToMany(fetch = FetchType.EAGER)หมายเหตุประกอบ

  2. ใช้ขี้เกียจโหลด

    ตั้งค่าเป็นlazy=trueXML หรือตั้งค่า@OneToMany(fetch = FetchType.LAZY)หมายเหตุประกอบ

    และเพิ่มOpenSessionInViewFilter filterในของคุณweb.xml

รายละเอียดของฉันดูโพสต์


1
... และยังแก้ปัญหาทั้งสองไม่ดี แนะนำให้ใช้ EAGER สามารถสร้างปัญหาใหญ่ การใช้ OpenSessionInViewFilter เป็นรูปแบบการต่อต้าน
Rafael

27
@Controller
@RequestMapping(value = "/topic")
@Transactional

ฉันแก้ปัญหานี้ด้วยการเพิ่ม@Transactionalฉันคิดว่านี่จะทำให้เซสชันเปิดได้


ทำไมสิ่งนี้ถึงได้รับการลงคะแนนลบ การเพิ่มธุรกรรมให้กับการดำเนินการจะขยายเซสชัน
Tudor Grigoriu

1
มันเป็นวิธีปฏิบัติที่ไม่ดีในการโฆษณา @ ธุรกรรมกับตัวควบคุม
Rafael

@Rafael ทำไมมันเป็นการปฏิบัติที่ไม่ดี?
Amr Ellafy

@AmrEllafy -> นี่คือคำอธิบายที่ดี: stackoverflow.com/a/18498834/1261162
Rafael

22

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

การแก้ปัญหาที่เป็นไปได้:

  1. ทำทุกตรรกะนี้ในเลเยอร์บริการ (ด้วย @Transactional) ไม่ใช่ในคอนโทรลเลอร์ ควรมีสถานที่ที่เหมาะสมในการทำสิ่งนี้ซึ่งเป็นส่วนหนึ่งของตรรกะของแอพไม่ใช่ในตัวควบคุม (ในกรณีนี้คือส่วนต่อประสานในการโหลดแบบจำลอง) การดำเนินการทั้งหมดในชั้นบริการควรเป็นธุรกรรม ie: ย้ายบรรทัดนี้ไปยังเมธอด TopicService.findTopicByID:

    การเก็บรวบรวม commentList = topicById.getComments ();

  2. ใช้ 'อยาก' แทน 'ขี้เกียจ' ตอนนี้คุณไม่ได้ใช้ 'ขี้เกียจ' .. มันไม่ใช่ทางออกที่แท้จริงหากคุณต้องการใช้สันหลังยาวทำงานเหมือนวิธีแก้ปัญหาชั่วคราว (ชั่วคราวมาก)

  3. ใช้ @Transactional ในตัวควบคุม ไม่ควรใช้ที่นี่คุณกำลังผสมชั้นบริการกับการนำเสนอไม่ใช่การออกแบบที่ดี
  4. ใช้ OpenSessionInViewFilterข้อเสียมากมายที่ได้รับรายงานความไม่แน่นอนที่อาจเกิดขึ้นได้

โดยทั่วไปทางออกที่ดีที่สุดคือ 1


2
ประเภทการดึงข้อมูลของ Eager สันนิษฐานว่าการจำศีลจะถูกดึงข้อมูลทั้งหมดในแบบสอบถามแรกไม่ใช่สถานที่ทั้งหมดที่ถูกต้อง
ЖасуланБердибеков

คุณควรที่จะทราบว่าทางออกที่ดีที่สุดคือ 1 ... ในความเป็นจริงเป็นทางออกที่ดีเท่านั้นเนื่องจากคนอื่น ๆ ทั้งหมดมีรูปแบบการต่อต้าน!
Rafael

19

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

อีกวิธีหนึ่งในการจัดการสิ่งนี้คือการรวบรวมข้อมูลทั้งหมดที่คุณต้องการในคอนโทรลเลอร์ปิดเซสชันและเก็บข้อมูลลงในแบบจำลองของคุณ ฉันชอบวิธีนี้เป็นการส่วนตัวมากขึ้นเพราะดูเหมือนว่าใกล้กับจิตวิญญาณของรูปแบบ MVC เล็กน้อย นอกจากนี้หากคุณได้รับข้อผิดพลาดจากฐานข้อมูลด้วยวิธีนี้คุณสามารถจัดการได้ดีกว่ามากหากเกิดขึ้นในตัวแสดงมุมมองของคุณ เพื่อนของคุณในสถานการณ์นี้คือHibernate.initialize (myTopic.getComments ()) นอกจากนี้คุณยังจะต้องแนบวัตถุในเซสชันอีกครั้งเนื่องจากคุณกำลังสร้างธุรกรรมใหม่พร้อมกับทุกคำขอ ใช้ session.lock (myTopic, LockMode.NONE) สำหรับสิ่งนั้น


15

ตามที่ฉันได้อธิบายไว้ในบทความนี้วิธีที่ดีที่สุดในการจัดการLazyInitializationExceptionคือดึงข้อมูลเมื่อเวลาสอบถามเช่นนี้:

select t
from Topic t
left join fetch t.comments

คุณควรหลีกเลี่ยงรูปแบบการต่อต้านต่อไปนี้:

ดังนั้นตรวจสอบให้แน่ใจว่าการFetchType.LAZYเชื่อมโยงของคุณถูกเตรียมใช้งานในเวลาสอบถามหรือภายใน@Transactionalขอบเขตดั้งเดิมที่ใช้Hibernate.initializeสำหรับคอลเลกชันรอง


1
วลาดคุณมีข้อเสนอแนะใด ๆ สำหรับการทำงานกับคอลเลกชันขี้เกียจเริ่มต้นในเอนทิตีที่ดึงมาโดยวิธีการ findById () ของที่เก็บสร้างสปริง? ฉันไม่ได้เขียนแบบสอบถามและธุรกรรมอยู่นอกรหัสของฉัน
chrisinmtown

ตรวจสอบบทความนี้สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับการเริ่มต้นการรวบรวมคอลเลกชันขี้เกียจ
Vlad Mihalcea

คุณสามารถอธิบายสิ่งที่คุณหมายถึงโดย 'ภายใน @transactional ขอบเขตเดิม' ไม่ชัดเจนสำหรับฉันเนื่องจากฉันดูเหมือนจะได้รับข้อผิดพลาดนี้ในขณะที่เซสชันเปิด (แต่ไม่ใช่ที่ถูกต้องหรือไม่)
Michiel Haisma

ในขณะที่อยู่ในขอบเขตของวิธีการบริการการทำธุรกรรมบนสุดที่รู้จักกันว่าเกตเวย์การทำธุรกรรม ตรวจสอบTrassctionInterceptorในการติดตามสแต็กและนั่นคือหนึ่ง
Vlad Mihalcea

หนึ่งในคำตอบที่ดีที่สุดในตอนนี้ ... ควรทำเครื่องหมายว่าถูกต้อง BTW ... สมมติว่า OSIV เป็นรูปแบบต่อต้านที่เป็นไปได้ที่เปิดใช้งานโดยค่าเริ่มต้นในเวอร์ชั่นล่าสุดของ spring-boot หรือไม่ ... อาจจะไม่เลวเลยเหรอ?
Rafael

10

หากคุณพยายามที่จะมีความสัมพันธ์ระหว่างเอนทิตีและชุดรวมหรือรายการของวัตถุจาวา (ตัวอย่างเช่นชนิดยาว) มันจะเป็นดังนี้:

@ElementCollection(fetch = FetchType.EAGER)
    public List<Long> ids;

1
ในหลายกรณีคุณไม่ต้องการทำเช่นนั้นจริงๆ คุณปล่อยผลประโยชน์ทั้งหมดของการโหลดขี้เกียจที่นี่
kiedysktos

การใช้ EAGER ไม่ใช่วิธีแก้ปัญหาอย่างมืออาชีพ
Rafael

9

หนึ่งในโซลูชันที่ดีที่สุดคือการเพิ่มสิ่งต่อไปนี้ในไฟล์ application.properties ของคุณ: spring.jpa.properties.hibernate.enable_lazy_load_no_trans = true


1
คุณสามารถบอก OP ว่ามันทำอะไรได้อย่างแม่นยำผลข้างเคียงใด ๆ ที่ส่งผลต่อประสิทธิภาพ
PeS

3
ที่ด้านหลังของ lazy loading เซสชั่นใหม่จะถูกแยกทุกครั้งที่การเชื่อมโยงถูกโหลดอย่างเกียจคร้านดังนั้นการเชื่อมต่อเพิ่มเติมจะถูกแยกออกและสร้างแรงกดดันเล็กน้อยบนพูลการเชื่อมต่อ หากคุณมีข้อ จำกัด เกี่ยวกับจำนวนการเชื่อมต่อคุณสมบัตินี้อาจไม่ถูกต้องที่จะใช้
sreekmatta

2
สำหรับบางคนก็ถือว่าเป็นรูปแบบการต่อต้านvladmihalcea.com/ ..
Uri Loya

7

ผมพบว่าการประกาศ@PersistenceContextเป็นEXTENDEDยังแก้ปัญหานี้:

@PersistenceContext(type = PersistenceContextType.EXTENDED)

1
สวัสดีระวังการเปลี่ยนแปลงเหล่านี้ด้วย การทำธุรกรรมการสร้างบริบทการคงอยู่ในขอบเขตเป็นเรื่องขี้เกียจซึ่งเป็นเจตนาของ OP ดังนั้นคำถามคือคุณต้องการไร้สัญชาติหรือไม่ การตั้งค่านี้ขึ้นอยู่กับวัตถุประสงค์ของระบบและไม่ควรเปลี่ยนแปลงด้วย ... อย่างกระตือรือร้น ถ้าคุณรู้ว่าฉันหมายถึงอะไร. อ่านที่นี่stackoverflow.com/questions/2547817/…
kiedysktos

เป็นอันตราย นี่ไม่ใช่คำตอบที่ถูกต้อง มีคนอื่น ๆ ข้างต้นถูกต้องและปลอดภัยมากขึ้น
Rafael

5

มันเป็นปัญหาที่ฉันเผชิญเมื่อเร็ว ๆ นี้ซึ่งฉันแก้ไขด้วยการใช้

<f:attribute name="collectionType" value="java.util.ArrayList" />

decription รายละเอียดเพิ่มเติมที่นี่และสิ่งนี้ช่วยวันของฉัน


5

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

Node n = // .. get the node
Hibernate.initialize(n); // initializes 'parent' similar to getParent.
Hibernate.initialize(n.getChildren()); // pass the lazy collection into the session 

4

เพื่อแก้ปัญหาในกรณีของฉันมันหายไปเพียงแค่บรรทัดนี้

<tx:annotation-driven transaction-manager="myTxManager" />

ในไฟล์แอปพลิเคชันบริบท

@Transactionalคำอธิบายประกอบวิธีการที่ไม่ได้ถูกนำเข้าบัญชี

หวังว่าคำตอบจะช่วยใครซักคน


4

@ คำอธิบายประกอบธุรกรรมบนตัวควบคุมหายไป

@Controller
@RequestMapping("/")
@Transactional
public class UserController {
}

17
ฉันจะยืนยันว่าการจัดการธุรกรรมเป็นของชั้นบริการที่มีตรรกะทางธุรกิจอยู่
ธันวาคม

คำอธิบายประกอบการทำธุรกรรมไม่ได้หายไป ตัวควบคุมไม่ควรมีคำอธิบายประกอบดังกล่าว คำอธิบายประกอบเหล่านั้นควรอยู่ในระดับบริการ
Rafael

4

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

@Transactional
public void checkTicketSalePresence(UUID ticketUuid, UUID saleUuid) {
        Optional<Ticket> savedTicketOpt = ticketRepository.findById(ticketUuid);
        savedTicketOpt.ifPresent(ticket -> {
            Optional<Sale> saleOpt = ticket.getSales().stream().filter(sale -> sale.getUuid() == saleUuid).findFirst();
            assertThat(saleOpt).isPresent();
        });
}

ที่นี่ในธุรกรรมที่มีการจัดการพร็อกซีไฮเบอร์เนตข้อเท็จจริงของการโทรticket.getSales()ทำแบบสอบถามอื่นเพื่อดึงยอดขายเพราะคุณถามอย่างชัดเจน



2

สำหรับผู้ที่ทำงานกับเกณฑ์ฉันพบว่า

criteria.setFetchMode("lazily_fetched_member", FetchMode.EAGER);

ทำทุกอย่างที่ฉันต้องการได้ทำ

โหมดการดึงข้อมูลเริ่มต้นสำหรับคอลเลกชันถูกตั้งค่าเป็น FetchMode.LAZY เพื่อให้ประสิทธิภาพ แต่เมื่อฉันต้องการข้อมูลฉันเพิ่งเพิ่มบรรทัดนั้นและเพลิดเพลินไปกับวัตถุที่มีประชากรเต็ม


2

ในกรณีของฉันรหัสต่อไปนี้เป็นปัญหา:

entityManager.detach(topicById);
topicById.getComments() // exception thrown

เพราะมันแยกออกจากฐานข้อมูลและไฮเบอร์เนตจะไม่เรียกคืนรายการจากฟิลด์เมื่อจำเป็น ดังนั้นฉันเริ่มต้นก่อนที่จะแยกออก:

Hibernate.initialize(topicById.getComments());
entityManager.detach(topicById);
topicById.getComments() // works like a charm

1

เหตุผลคือคุณพยายามรับ commentList ในคอนโทรลเลอร์ของคุณหลังจากปิดเซสชันภายในบริการ

topicById.getComments();

ด้านบนจะโหลดรายการความคิดเห็นเฉพาะเมื่อเซสชันไฮเบอร์เนตของคุณเปิดใช้งานซึ่งฉันเดาว่าคุณจะปิดบริการของคุณ

ดังนั้นคุณต้องรับ commentList ก่อนที่จะปิดเซสชัน


2
ใช่นี่คือคำแถลงปัญหาคุณควรให้คำตอบในAnswer
Sarz

1

คอลเล็กชันcommentsในคลาสโมเดลของคุณTopicถูกโหลดอย่างเกียจคร้านซึ่งเป็นลักษณะการทำงานเริ่มต้นหากคุณไม่ใส่หมายเหตุประกอบไว้fetch = FetchType.EAGERเฉพาะ

เป็นไปได้มากว่าfindTopicByIDบริการของคุณกำลังใช้เซสชันไฮเบอร์เนตแบบไร้รัฐ เซสชันไร้สัญชาติไม่มีแคชระดับแรกกล่าวคือไม่มีบริบทการคงอยู่ ในภายหลังเมื่อคุณพยายามที่จะทำซ้ำcommentsไฮเบอร์เนตจะโยนข้อยกเว้น

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: mvc3.model.Topic.comments, no session or session was closed

การแก้ปัญหาสามารถ:

  1. ใส่คำอธิบายcommentsประกอบด้วยfetch = FetchType.EAGER

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "topic", cascade = CascadeType.ALL)   
    private Collection<Comment> comments = new LinkedHashSet<Comment>();
  2. หากคุณยังคงต้องการให้ความคิดเห็นโหลดอย่างเกียจคร้านให้ใช้ช่วงสถานะของไฮเบอร์เนตเพื่อให้คุณสามารถดึงความคิดเห็นได้ตามต้องการ


1

ในกรณีของฉันฉันมีการทำแผนที่ b / w AและBชอบ

A มี

@OneToMany(mappedBy = "a", cascade = CascadeType.ALL)
Set<B> bs;

ในDAOเลเยอร์วิธีการที่จะต้องมีข้อเขียนด้วย@Transactionalถ้าคุณยังไม่ได้ใส่คำอธิบายประกอบการทำแผนที่ด้วยประเภทการเรียก - Eager


1

ไม่ใช่ทางออกที่ดีที่สุด แต่สำหรับผู้ที่เผชิญLazyInitializationExceptionโดยเฉพาะในSerializationสิ่งนี้จะช่วยได้ ที่นี่คุณจะตรวจสอบคุณสมบัติเริ่มต้นอย่างเกียจคร้านและการตั้งค่าnullเหล่านั้น สำหรับการสร้างคลาสด้านล่าง

public class RepositoryUtil {
    public static final boolean isCollectionInitialized(Collection<?> collection) {
        if (collection instanceof PersistentCollection)
            return ((PersistentCollection) collection).wasInitialized();
        else 
            return true;
    }   
}

ภายในคลาสเอนทิตีของคุณซึ่งคุณมีคุณสมบัติเริ่มต้นอย่างขี้เกียจเพิ่มวิธีการดังแสดงด้านล่าง เพิ่มคุณสมบัติการโหลดอย่างเกียจคร้านทั้งหมดภายในวิธีนี้

public void checkLazyIntialzation() {
    if (!RepositoryUtil.isCollectionInitialized(yourlazyproperty)) {
        yourlazyproperty= null;
    }

เรียกcheckLazyIntialzation()ใช้วิธีนี้ในทุกที่ที่คุณโหลดข้อมูล

 YourEntity obj= entityManager.find(YourEntity.class,1L);
  obj.checkLazyIntialzation();

0

สวัสดีทุกคนโพสต์ค่อนข้างช้าหวังว่าจะช่วยคนอื่นขอบคุณล่วงหน้า @GMK สำหรับโพสต์นี้ Hibernate.initialize (วัตถุ)

เมื่อ Lazy = "true"

Set<myObject> set=null;
hibernateSession.open
set=hibernateSession.getMyObjects();
hibernateSession.close();

ตอนนี้ถ้าฉันเข้าถึง 'ตั้งค่า' หลังจากปิดเซสชั่นก็มีข้อยกเว้น

ทางออกของฉัน:

Set<myObject> set=new HashSet<myObject>();
hibernateSession.open
set.addAll(hibernateSession.getMyObjects());
hibernateSession.close();

ตอนนี้ฉันสามารถเข้าถึง 'set' แม้หลังจากปิด Hibernate Session


0

อีกวิธีหนึ่งในการทำสิ่งนี้คุณสามารถใช้TransactionTemplateเพื่อล้อมรอบการดึงข้อมูลที่ขี้เกียจ ชอบ

Collection<Comment> commentList = this.transactionTemplate.execute
(status -> topicById.getComments());

0

ปัญหาเกิดขึ้นเนื่องจากรหัสกำลังเข้าถึงความสัมพันธ์ JPA แบบสันหลังยาวเมื่อ "การเชื่อมต่อ" กับฐานข้อมูลถูกปิด ( บริบทการมีอยู่คือชื่อที่ถูกต้องในแง่ของ Hibernate / JPA)

วิธีง่ายๆในการแก้ไขใน Spring Boot คือการกำหนดเลเยอร์บริการและการใช้ @Transactionalคำอธิบายประกอบ คำอธิบายประกอบนี้ในวิธีการสร้างธุรกรรมที่แพร่กระจายเข้าไปในเลเยอร์ที่เก็บและเปิดบริบทการคงอยู่จนกระทั่งวิธีการเสร็จสิ้น หากคุณเข้าถึงการรวบรวมภายในวิธีการทำธุรกรรม Hibernate / JPA จะดึงข้อมูลจากฐานข้อมูล

ในกรณีของคุณคุณเพียงแค่ต้องใส่คำอธิบายประกอบด้วย@TransactionalวิธีการfindTopicByID(id)ของคุณTopicServiceและบังคับให้เรียกเก็บในวิธีการ (เช่นโดยขอขนาดของมัน):

    @Transactional(readOnly = true)
    public Topic findTopicById(Long id) {
        Topic topic = TopicRepository.findById(id).orElse(null);
        topic.getComments().size();
        return topic;
    }

0

เพื่อกำจัดข้อยกเว้นการเริ่มต้นขี้เกียจคุณไม่ควรเรียกหาคอลเล็กชันขี้เกียจเมื่อคุณทำงานกับวัตถุเดี่ยว

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

สำหรับบางกรณีคุณสามารถใช้@EntityGrpaphคำอธิบายประกอบซึ่งช่วยให้คุณสามารถeagerโหลดได้แม้ว่าคุณจะfetchType=lazyอยู่ในเอนทิตีของคุณ


0

มีวิธีแก้ไขปัญหาหลายอย่างสำหรับปัญหาการเริ่มต้น Lazy นี้ -

1) เปลี่ยนประเภทการดึงข้อมูลการเชื่อมโยงจาก LAZY เป็น EAGER แต่นี่ไม่ใช่วิธีปฏิบัติที่ดีเพราะจะทำให้ประสิทธิภาพลดลง

2) ใช้ FetchType.LAZY ในวัตถุที่เกี่ยวข้องและใช้คำอธิบายประกอบธุรกรรมในวิธีการเลเยอร์บริการของคุณเพื่อให้เซสชันจะยังคงเปิดอยู่และเมื่อคุณจะเรียก topicById.getComments () วัตถุลูก (ความคิดเห็น) จะได้รับการโหลด

3) นอกจากนี้โปรดลองใช้วัตถุ DTO แทนเอนทิตีในชั้นควบคุม ในกรณีของคุณเซสชันจะปิดที่เลเยอร์ควบคุม ดังนั้นควรแปลงเอนทิตีเป็น DTO ในเลเยอร์บริการ


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