JPA: อะไรคือรูปแบบที่เหมาะสมสำหรับการทำซ้ำชุดผลลัพธ์ขนาดใหญ่?


114

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

ตัวอย่างเช่นฉันสงสัยว่าสิ่งต่อไปนี้จะระเบิดหากตารางมีขนาดใหญ่:

List<Model> models = entityManager().createQuery("from Model m", Model.class).getResultList();

for (Model model : models)
{
     System.out.println(model.getId());
}

การแบ่งหน้า (การวนซ้ำและการอัปเดตsetFirstResult()/ setMaxResult()) ด้วยตนเองเป็นทางออกที่ดีที่สุดหรือไม่?

แก้ไข : กรณีการใช้งานหลักที่ฉันกำหนดเป้าหมายเป็นงานแบตช์ประเภทหนึ่ง ถ้าใช้เวลานานจะดีกว่า ไม่มีเว็บไคลเอ็นต์ที่เกี่ยวข้อง ฉันแค่ต้อง "ทำอะไรบางอย่าง" สำหรับแต่ละแถวทีละหนึ่ง (หรือ N ขนาดเล็ก) ฉันแค่พยายามหลีกเลี่ยงไม่ให้พวกเขาทั้งหมดอยู่ในความทรงจำในเวลาเดียวกัน


คุณใช้ฐานข้อมูลและไดรเวอร์ JDBC อะไร

คำตอบ:


55

หน้า 537 ของJava Persistence กับ Hibernateให้วิธีแก้ปัญหาโดยใช้ScrollableResultsแต่อนิจจามันมีไว้สำหรับ Hibernate เท่านั้น

ดูเหมือนว่าการใช้setFirstResult/ setMaxResultsและการทำซ้ำด้วยตนเองเป็นสิ่งที่จำเป็นจริงๆ นี่คือวิธีแก้ปัญหาของฉันโดยใช้ JPA:

private List<Model> getAllModelsIterable(int offset, int max)
{
    return entityManager.createQuery("from Model m", Model.class).setFirstResult(offset).setMaxResults(max).getResultList();
}

จากนั้นใช้ดังนี้:

private void iterateAll()
{
    int offset = 0;

    List<Model> models;
    while ((models = Model.getAllModelsIterable(offset, 100)).size() > 0)
    {
        entityManager.getTransaction().begin();
        for (Model model : models)
        {
            log.info("do something with model: " + model.getId());
        }

        entityManager.flush();
        entityManager.clear();
        em.getTransaction().commit();
        offset += models.size();
    }
}

33
ฉันคิดว่าตัวอย่างไม่ปลอดภัยหากมีเม็ดมีดใหม่ในระหว่างกระบวนการแบทช์ ผู้ใช้ต้องเรียงลำดับตามคอลัมน์ที่มั่นใจได้ว่าข้อมูลที่แทรกใหม่จะอยู่ที่ส่วนท้ายของรายการผลลัพธ์
Balazs Zsoldos

เมื่อหน้าปัจจุบันเป็นหน้าสุดท้ายและมีการตรวจสอบองค์ประกอบน้อยกว่า 100 รายการsize() == 100แทนจะข้ามแบบสอบถามเพิ่มเติมหนึ่งรายการที่ส่งคืนรายการว่าง
cdalxndr

38

ฉันลองคำตอบที่นำเสนอที่นี่ แต่ JBoss 5.1 + MySQL Connector / J 5.1.15 + Hibernate 3.3.2 ไม่สามารถใช้งานได้ เราเพิ่งย้ายจาก JBoss 4.x ไปเป็น JBoss 5.1 ดังนั้นเราจึงติดอยู่กับมันในตอนนี้ดังนั้น Hibernate ล่าสุดที่เราสามารถใช้ได้คือ 3.3.2

การเพิ่มพารามิเตอร์พิเศษสองสามตัวได้ผลและโค้ดเช่นนี้จะทำงานโดยไม่มี OOME:

        StatelessSession session = ((Session) entityManager.getDelegate()).getSessionFactory().openStatelessSession();

        Query query = session
                .createQuery("SELECT a FROM Address a WHERE .... ORDER BY a.id");
        query.setFetchSize(Integer.valueOf(1000));
        query.setReadOnly(true);
        query.setLockMode("a", LockMode.NONE);
        ScrollableResults results = query.scroll(ScrollMode.FORWARD_ONLY);
        while (results.next()) {
            Address addr = (Address) results.get(0);
            // Do stuff
        }
        results.close();
        session.close();

บรรทัดที่สำคัญคือพารามิเตอร์การสืบค้นระหว่าง createQuery และ scroll หากไม่มีพวกเขาการเรียก "เลื่อน" จะพยายามโหลดทุกอย่างลงในหน่วยความจำและจะไม่เสร็จสิ้นหรือเรียกใช้ไปยัง OutOfMemoryError


2
สวัสดี Zds กรณีการใช้งานของคุณในการสแกนแถวนับล้านเป็นเรื่องปกติสำหรับฉันและขอขอบคุณสำหรับการโพสต์รหัสสุดท้าย ในกรณีของฉันฉันกำลังยัดระเบียนลงใน Solr เพื่อจัดทำดัชนีสำหรับการค้นหาแบบเต็มข้อความ และเนื่องจากกฎทางธุรกิจฉันจะไม่เข้าไปฉันจึงต้องดำเนินการผ่าน Hibernate เทียบกับการใช้โมดูลในตัวของ JDBC หรือ Solr
Mark Bennett

ยินดีที่ได้ช่วย :-). นอกจากนี้เรายังจัดการกับชุดข้อมูลขนาดใหญ่ในกรณีนี้ทำให้ผู้ใช้สามารถสืบค้นชื่อถนนทั้งหมดภายในเมือง / เขตเดียวกันหรือบางครั้งอาจเป็นรัฐดังนั้นการสร้างดัชนีจึงต้องอ่านข้อมูลจำนวนมาก
Zds

ปรากฏขึ้นพร้อมกับ MySQL คุณต้องผ่านห่วงเหล่านั้นจริงๆ: stackoverflow.com/a/20900045/32453 (DB อื่นอาจเข้มงวดน้อยกว่าที่ฉันคิด ... )
rogerdpack

32

คุณไม่สามารถทำสิ่งนี้ได้ใน JPA โดยตรงอย่างไรก็ตาม Hibernate รองรับเซสชันแบบไร้สัญชาติและชุดผลลัพธ์ที่เลื่อนได้

เราประมวลผลแถวหลายพันล้านแถวเป็นประจำด้วยความช่วยเหลือ

นี่คือลิงค์ไปยังเอกสาร: http://docs.jboss.org/hibernate/core/3.3/reference/en/html/batch.html#batch-statelesssession


17
ขอบคุณ อยากรู้ว่ามีคนทำแถวหลายพันล้านแถวผ่านไฮเบอร์เนต บางคนที่นี่อ้างว่าเป็นไปไม่ได้ :-)
George Armhold

2
เป็นไปได้ที่จะเพิ่มตัวอย่างที่นี่ด้วยหรือไม่? ฉันคิดว่ามันคล้ายกับตัวอย่างของ Zds?
rogerdpack

19

พูดตามตรงฉันขอแนะนำให้ออกจาก JPA และใช้ JDBC (แต่แน่นอนว่าใช้JdbcTemplateคลาสการสนับสนุนหรือแบบนั้น) JPA (และผู้ให้บริการ / ข้อกำหนด ORM อื่น ๆ ) ไม่ได้ออกแบบมาเพื่อใช้งานกับวัตถุจำนวนมากภายในธุรกรรมเดียวเนื่องจากถือว่าทุกสิ่งที่โหลดควรอยู่ในแคชระดับแรก (ดังนั้นความจำเป็นclear()ใน JPA)

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

JPA ไม่ได้ออกแบบมาเพื่อดำเนินการกับเอนทิตีจำนวนมาก คุณอาจเล่นด้วยflush()/ clear()เพื่อหลีกเลี่ยงOutOfMemoryErrorแต่ลองพิจารณาอีกครั้ง คุณได้รับเงินเพียงเล็กน้อยจากการใช้ทรัพยากรจำนวนมาก


ข้อดีของ JPA ไม่ใช่แค่ฐานข้อมูลที่ไม่เชื่อเรื่องพระเจ้าเท่านั้น แต่ยังมีความเป็นไปได้ที่จะไม่ใช้ฐานข้อมูลแบบเดิม (NoSQL) ด้วยซ้ำ ไม่ใช่เรื่องยากที่จะทำการล้าง / ล้างทุก ๆ ครั้งและโดยปกติการดำเนินการเป็นกลุ่มจะทำไม่บ่อยนัก
Adam Gent

1
สวัสดี Thomasz ฉันมีเหตุผลมากมายที่จะบ่นเกี่ยวกับ JPA / Hibernate แต่ด้วยความเคารพฉันสงสัยจริงๆว่าพวกเขา "ไม่ได้ออกแบบมาให้ทำงานกับวัตถุจำนวนมาก" ฉันสงสัยว่าฉันแค่ต้องเรียนรู้รูปแบบที่เหมาะสมสำหรับกรณีการใช้งานนี้
George Armhold

4
ดีฉันสามารถคิดเพียงสองรูปแบบ: paginations (กล่าวถึงหลายครั้ง) และ/flush() clear()หนึ่งคือ IMHO ไม่ได้ออกแบบมาเพื่อวัตถุประสงค์ในการประมวลผลชุดในขณะที่ใช้ลำดับของการล้าง () / ชัดเจน () มีกลิ่นเหมือนนามธรรมรั่ว
Tomasz Nurkiewicz

ใช่มันเป็นการรวมกันของเลขหน้าและการล้าง / ล้างตามที่คุณกล่าวมา ขอบคุณ!
George Armhold

7

หากคุณใช้ EclipseLink I โดยใช้วิธีนี้เพื่อให้ได้ผลลัพธ์เป็น Iterable

private static <T> Iterable<T> getResult(TypedQuery<T> query)
{
  //eclipseLink
  if(query instanceof JpaQuery) {
    JpaQuery<T> jQuery = (JpaQuery<T>) query;
    jQuery.setHint(QueryHints.RESULT_SET_TYPE, ResultSetType.ForwardOnly)
       .setHint(QueryHints.SCROLLABLE_CURSOR, true);

    final Cursor cursor = jQuery.getResultCursor();
    return new Iterable<T>()
    {     
      @SuppressWarnings("unchecked")
      @Override
      public Iterator<T> iterator()
      {
        return cursor;
      }
    }; 
   }
  return query.getResultList();  
}  

ปิดวิธีการ

static void closeCursor(Iterable<?> list)
{
  if (list.iterator() instanceof Cursor)
    {
      ((Cursor) list.iterator()).close();
    }
}

6
วัตถุjQuery ที่ดี
usr-local-ΕΨΗΕΛΩΝ

ฉันลองใช้รหัสของคุณแล้ว แต่ยังได้รับ OOM - ดูเหมือนว่าวัตถุ T ทั้งหมด (และวัตถุตารางที่เข้าร่วมทั้งหมดที่อ้างถึงจาก T) จะไม่ GC การทำโปรไฟล์แสดงให้เห็นว่าถูกอ้างถึงจาก "table" ใน org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork พร้อมกับ org.eclipse.persistence.internal.identitymaps.CacheKey ฉันตรวจสอบแคชและการตั้งค่าของฉันเป็นค่าเริ่มต้นทั้งหมด (ปิดการใช้งาน Selective, อ่อนแอด้วย Soft Subcache, Cache Size 100, Drop Invalidate) ฉันจะตรวจสอบการปิดใช้งานและดูว่าช่วยได้หรือไม่ BTW ฉันเพียงแค่วนซ้ำเคอร์เซอร์กลับโดยใช้ "for (T o: results)"
Edi Bice

Badum tssssssss
dctremblay

5

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

หากคุณกำลังจะแสดงข้อมูลหนึ่งล้านรายการให้กับลูกค้าโปรดพิจารณาอินเทอร์เฟซผู้ใช้ของคุณใหม่ ในกรณีนี้การแก้ปัญหาที่เหมาะสมเลขหน้าผลลัพธ์ของคุณและการใช้และsetFirstResult()setMaxResult()

หากคุณได้เปิดตัวการอัปเดตของบันทึกจำนวนมากคุณควรทำให้การอัปเดตเป็นเรื่องง่ายและใช้งานQuery.executeUpdate()ได้ดีกว่า คุณสามารถดำเนินการอัปเดตในโหมดอะซิงโครนัสได้โดยใช้ Message-Driven Bean oa Work Manager

หากคุณกำลังคำนวณสถิติบางอย่างตามเอนทิตีที่ดึงมาคุณสามารถใช้ประโยชน์จากฟังก์ชันการจัดกลุ่มที่กำหนดโดยข้อกำหนดของ JPA

สำหรับกรณีอื่น ๆ โปรดเจาะจงมากขึ้น :)


ค่อนข้างง่ายฉันต้องทำบางอย่าง "สำหรับแต่ละแถว" แน่นอนว่านี่เป็นกรณีการใช้งานทั่วไป ในกรณีเฉพาะตอนนี้ฉันกำลังดำเนินการอยู่ฉันจำเป็นต้องสอบถามบริการเว็บภายนอกที่อยู่นอกฐานข้อมูลโดยสิ้นเชิงโดยใช้ id (PK) จากแต่ละแถว ผลลัพธ์จะไม่แสดงกลับไปที่เว็บเบราว์เซอร์ไคลเอนต์ใด ๆ ดังนั้นจึงไม่มีส่วนต่อประสานผู้ใช้ที่จะพูดถึง มันเป็นงานแบทช์หรืออีกนัยหนึ่ง
George Armhold

หากคุณ "ต้องการ" รหัสการพิมพ์สำหรับแต่ละแถวไม่มีวิธีอื่นในการรับแต่ละแถวรับรหัสและพิมพ์ ทางออกที่ดีที่สุดขึ้นอยู่กับสิ่งที่คุณต้องทำ
Dainius

@ คาเฟอีนโคม่าหากคุณต้องการเพียง id ของแต่ละแถวการปรับปรุงที่ใหญ่ที่สุดน่าจะมาจากการดึงคอลัมน์SELECT m.id FROM Model mนั้นเท่านั้นจากนั้นจึงวนซ้ำรายการ <Integer>
Jörn Horstmann

1
@ Jörn Horstmann- ถ้ามีแถวเป็นล้านจะสำคัญจริงหรือ? ประเด็นของฉันคือ ArrayList ที่มีออบเจ็กต์นับล้าน (เล็กมาก) จะไม่ดีสำหรับ JVM heap
George Armhold

@Dainius: คำถามของฉันคือ: "ฉันจะทำซ้ำในแต่ละแถวโดยไม่ต้องมี ArrayList ในหน่วยความจำทั้งหมดได้อย่างไร" กล่าวอีกนัยหนึ่งฉันต้องการอินเทอร์เฟซสำหรับการดึง N ครั้งละครั้งโดยที่ N มีขนาดเล็กกว่า 1 ล้าน :-)
George Armhold

5

ไม่มีสิ่งที่ "เหมาะสม" ในการทำสิ่งนี้นี่ไม่ใช่สิ่งที่ JPA หรือ JDO หรือ ORM อื่น ๆ ตั้งใจจะทำ JDBC แบบตรงจะเป็นทางเลือกที่ดีที่สุดของคุณเนื่องจากคุณสามารถกำหนดค่าให้นำกลับมาจำนวนแถวเล็กน้อยที่ เวลาและล้างออกตามที่ใช้นั่นคือสาเหตุที่เคอร์เซอร์ฝั่งเซิร์ฟเวอร์มีอยู่

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

ใช้เครื่องมือที่เหมาะสม Straight JDBC และ Stored Procedures มีที่มาแน่นอนในปี 2011 โดยเฉพาะสิ่งที่ทำได้ดีกว่าเมื่อเทียบกับกรอบ ORM เหล่านี้

การดึงอะไรเป็นล้าน ๆ อย่างมารวมกันเป็นเรื่องธรรมดาList<Integer>ก็ไม่ได้มีประสิทธิภาพมากนักไม่ว่าคุณจะทำอย่างไรก็ตาม วิธีที่ถูกต้องในการทำสิ่งที่คุณขอคือง่ายๆSELECT id FROM tableตั้งค่าเป็นSERVER SIDE(ขึ้นอยู่กับผู้ขาย) และเคอร์เซอร์ไปที่FORWARD_ONLY READ-ONLYและทำซ้ำตามนั้น

หากคุณกำลังดึง id หลายล้านรายการมาประมวลผลโดยเรียกเว็บเซิร์ฟเวอร์บางตัวพร้อมกับแต่ละอันคุณจะต้องทำการประมวลผลพร้อมกันด้วยเพื่อให้สิ่งนี้ทำงานได้ในระยะเวลาที่เหมาะสม การดึงด้วยเคอร์เซอร์ JDBC และวางเคอร์เซอร์สองสามตัวพร้อมกันใน ConcurrentLinkedQueueและมีกลุ่มเธรดขนาดเล็ก (# CPU / Cores + 1) ดึงและประมวลผลเป็นวิธีเดียวที่จะทำให้งานของคุณเสร็จสมบูรณ์บนเครื่องด้วย " จำนวน RAM ตามปกติเนื่องจากหน่วยความจำของคุณใกล้หมดแล้ว

ดูคำตอบนี้ด้วย


1
คุณกำลังบอกว่าไม่มี บริษัท ใดที่ต้องการเยี่ยมชมตารางผู้ใช้ทุกแถว? โปรแกรมเมอร์ของพวกเขาแค่โยน Hibernate ออกไปนอกหน้าต่างเมื่อถึงเวลาต้องทำเช่นนี้? " มีวิธีการขั้นตอนหลายร้อยหลายพันแถวไม่มี " - ในคำถามของฉันฉันชี้ให้เห็น setFirstResult / setMaxResult ดังนั้นเห็นได้ชัดว่ามีเป็นทาง ฉันกำลังถามว่ามีอันไหนดีกว่า
George Armhold

"การดึงอะไรเป็นล้าน ๆ อย่างเข้ามาใน List <Integer> แบบธรรมดาก็จะไม่ได้ผลมากนักไม่ว่าคุณจะทำอย่างไรก็ตาม" นั่นคือประเด็นของฉัน ฉันกำลังถามว่าจะไม่สร้างรายการยักษ์ แต่ให้ทำซ้ำชุดผลลัพธ์
George Armhold

ใช้คำสั่งเลือก JDBC ที่เรียบง่ายด้วย FORWARD_ONLY READ_ONLY พร้อมเคอร์เซอร์ SERVER_SIDE ตามที่ฉันแนะนำในคำตอบของฉัน วิธีทำให้ JDBC ใช้เคอร์เซอร์ SERVER_SIDE ขึ้นอยู่กับไดรเวอร์ฐานข้อมูล

1
ฉันเห็นด้วยอย่างยิ่งกับคำตอบ ทางออกที่ดีที่สุดขึ้นอยู่กับปัญหา หากปัญหาคือการโหลดเอนทิตีไม่กี่อย่างง่ายๆ JPA ก็ดี หากปัญหาคือการใช้ข้อมูลจำนวนมากอย่างมีประสิทธิภาพโดยตรง JDBC จะดีกว่า
ภายนอก

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

4

คุณสามารถใช้ "เคล็ดลับ" อื่นได้ โหลดเฉพาะคอลเลกชันของตัวระบุของเอนทิตีที่คุณสนใจสมมติว่าตัวระบุเป็นประเภท long = 8 ไบต์จากนั้น 10 ^ 6 รายการของตัวระบุดังกล่าวจะมีขนาดประมาณ 8Mb หากเป็นกระบวนการแบตช์ (ทีละอินสแตนซ์) ก็จะสามารถรับได้ จากนั้นทำซ้ำและทำงาน

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

เมื่อพูดถึงการตั้งค่ากลยุทธ์ firstResult / maxRows - มันจะช้ามากสำหรับผลลัพธ์ที่อยู่ไกลจากจุดสูงสุด

นอกจากนี้ควรพิจารณาด้วยว่าฐานข้อมูลอาจทำงานในการแยกการอ่านที่ได้รับมอบหมายดังนั้นเพื่อหลีกเลี่ยงไม่ให้ phantom อ่านตัวระบุการโหลดแล้วโหลดเอนทิตีทีละรายการ (หรือ 10 คูณ 10 หรืออะไรก็ได้)


สวัสดี @Marcin คุณหรือใครก็ตามสามารถให้ลิงก์ไปยังโค้ดตัวอย่างที่ใช้วิธีการแบบทีละขั้นตอนและ id-first นี้ได้หรือไม่โดยควรใช้สตรีม Java8
krevelen

2

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

การประหยัดประสิทธิภาพที่นี่มีความสำคัญอาจเป็นลำดับขนาดได้เร็วกว่าสิ่งใด ๆ ที่คุณสามารถทำได้ในที่ดิน JPA / Hibernate / AppServer และเซิร์ฟเวอร์ฐานข้อมูลของคุณมักจะมีกลไกเคอร์เซอร์ฝั่งเซิร์ฟเวอร์ของตัวเองสำหรับการประมวลผลชุดผลลัพธ์ขนาดใหญ่อย่างมีประสิทธิภาพ การประหยัดประสิทธิภาพมาจากการไม่ต้องจัดส่งข้อมูลจากเซิร์ฟเวอร์ฐานข้อมูลไปยังแอ็พพลิเคชันเซิร์ฟเวอร์ซึ่งคุณประมวลผลข้อมูลแล้วจัดส่งกลับ

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


1
-2 downvotes - ผู้โหวตคนต่อไปจะช่วยปกป้องการโหวตของคุณหรือไม่?
อันตราย

1
ฉันคิดแบบเดียวกันในขณะที่อ่านสิ่งเหล่านี้ คำถามระบุงานแบตช์ปริมาณมากโดยไม่มี UI สมมติว่าคุณไม่ต้องการทรัพยากรเฉพาะของเซิร์ฟเวอร์แอปทำไมต้องใช้เซิร์ฟเวอร์แอปเลย? ขั้นตอนการจัดเก็บจะมีประสิทธิภาพมากขึ้น
jdessey

@jdessey ขึ้นอยู่กับสถานการณ์สมมติว่าเรามีสิ่งอำนวยความสะดวกในการนำเข้าซึ่งในการนำเข้าควรทำบางอย่างกับส่วนอื่น ๆ ของระบบเช่นเพิ่มแถวในตารางอื่นตามกฎทางธุรกิจบางอย่างที่ได้รับการเข้ารหัสแล้วเป็น EJB จากนั้นการทำงานในเซิร์ฟเวอร์แอปจะเหมาะสมกว่าเว้นแต่คุณจะได้รับ EJB เพื่อทำงานในโหมดฝังตัว
Archimedes Trajano

1

เพื่อขยายคำตอบของ @Tomasz Nurkiewicz คุณสามารถเข้าถึงสิ่งDataSourceที่สามารถให้การเชื่อมต่อกับคุณได้

@Resource(name = "myDataSource",
    lookup = "java:comp/DefaultDataSource")
private DataSource myDataSource;

ในรหัสของคุณคุณมี

try (Connection connection = myDataSource.getConnection()) {
    // raw jdbc operations
}

วิธีนี้จะช่วยให้คุณสามารถข้าม JPA สำหรับการดำเนินการแบทช์ขนาดใหญ่บางอย่างเช่นการนำเข้า / ส่งออกอย่างไรก็ตามคุณยังคงสามารถเข้าถึงผู้จัดการเอนทิตีสำหรับการดำเนินการ JPA อื่น ๆ ได้หากคุณต้องการ


0

ใช้Paginationแนวคิดในการดึงผลลัพธ์


4
Pagination ดีมากสำหรับ GUI แต่สำหรับการประมวลผลข้อมูลจำนวนมาก ScrollableResultSet ถูกคิดค้นขึ้นเมื่อนานมาแล้ว ไม่ใช่แค่ใน JPA
ภายนอก

0

ฉันเคยสงสัยตัวเองนี้ ดูเหมือนว่าจะมีความสำคัญ:

  • ชุดข้อมูลของคุณใหญ่แค่ไหน (แถว)
  • คุณใช้ JPA อะไร
  • คุณกำลังประมวลผลแบบไหนสำหรับแต่ละแถว

ฉันได้เขียน Iterator เพื่อให้ง่ายต่อการสลับทั้งสองวิธี (findAll vs findEntries)

ฉันแนะนำให้คุณลองทั้งสองอย่าง

Long count = entityManager().createQuery("select count(o) from Model o", Long.class).getSingleResult();
ChunkIterator<Model> it1 = new ChunkIterator<Model>(count, 2) {

    @Override
    public Iterator<Model> getChunk(long index, long chunkSize) {
        //Do your setFirst and setMax here and return an iterator.
    }

};

Iterator<Model> it2 = List<Model> models = entityManager().createQuery("from Model m", Model.class).getResultList().iterator();


public static abstract class ChunkIterator<T> 
    extends AbstractIterator<T> implements Iterable<T>{
    private Iterator<T> chunk;
    private Long count;
    private long index = 0;
    private long chunkSize = 100;

    public ChunkIterator(Long count, long chunkSize) {
        super();
        this.count = count;
        this.chunkSize = chunkSize;
    }

    public abstract Iterator<T> getChunk(long index, long chunkSize);

    @Override
    public Iterator<T> iterator() {
        return this;
    }

    @Override
    protected T computeNext() {
        if (count == 0) return endOfData();
        if (chunk != null && chunk.hasNext() == false && index >= count) 
            return endOfData();
        if (chunk == null || chunk.hasNext() == false) {
            chunk = getChunk(index, chunkSize);
            index += chunkSize;
        }
        if (chunk == null || chunk.hasNext() == false) 
            return endOfData();
        return chunk.next();
    }

}

ฉันลงเอยด้วยการไม่ใช้ตัววนซ้ำชิ้นส่วนของฉัน (ดังนั้นจึงอาจไม่ผ่านการทดสอบ) อย่างไรก็ตามคุณจะต้องมีคอลเล็กชันของ Google หากคุณต้องการใช้


เกี่ยวกับ "ประเภทของการประมวลผลที่คุณกำลังดำเนินการสำหรับแต่ละแถว" - ถ้า # ของแถวเป็นล้านฉันสงสัยว่าแม้แต่ออบเจ็กต์ธรรมดาที่มีเพียงคอลัมน์ id ก็ยังทำให้เกิดปัญหาได้ ฉันคิดเช่นกันเกี่ยวกับการเขียน Iterator ของตัวเองที่ห่อ setFirstResult / setMaxResult แต่ฉันคิดว่านี่จะต้องเป็นปัญหาทั่วไป (และหวังว่าจะแก้ไขได้!)
George Armhold

@ คาเฟอีนโคม่าฉันโพสต์ Iterator ของฉันคุณอาจจะปรับตัวให้เข้ากับ JPA ได้มากกว่านี้ บอกฉันว่ามันช่วยได้ ฉันไม่ได้ใช้ (ค้นหาทั้งหมด)
Adam Gent

0

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

  1. ใช้เซสชันแบบไม่ระบุสถานะด้วยการเลื่อน ()
  2. ใช้ session.clear () หลังการทำซ้ำทุกครั้ง เมื่อจำเป็นต้องแนบเอนทิตีอื่นให้โหลดในเซสชันแยกต่างหาก เซสชันแรกกำลังเลียนแบบเซสชันไร้สถานะอย่างมีประสิทธิภาพ แต่ยังคงรักษาคุณสมบัติทั้งหมดของเซสชันแบบ stateful ไว้จนกว่าอ็อบเจ็กต์จะถูกแยกออก
  3. ใช้การวนซ้ำ () หรือรายการ () แต่รับเฉพาะรหัสในแบบสอบถามแรกจากนั้นในเซสชันที่แยกจากกันในการวนซ้ำแต่ละครั้งให้ทำ session.load และปิดเซสชันเมื่อสิ้นสุดการทำซ้ำ
  4. ใช้ Query.iterate () กับ EntityManager.detach () aka Session.evict ();

0

นี่คือตัวอย่าง JPA ที่เรียบง่ายและตรงไปตรงมา (ใน Kotlin) ที่แสดงให้เห็นว่าคุณสามารถแบ่งหน้าบนชุดผลลัพธ์ขนาดใหญ่โดยพลการอ่านชิ้นละ 100 รายการโดยไม่ต้องใช้เคอร์เซอร์ (แต่ละเคอร์เซอร์ใช้ทรัพยากรในฐานข้อมูล) มันใช้การแบ่งหน้าของชุดคีย์

ดูhttps://use-the-index-luke.com/no-offsetสำหรับแนวคิดเรื่องการแบ่งหน้าของชุดคีย์และhttps://www.citusdata.com/blog/2016/03/30/five-ways-to- เลขหน้า /สำหรับการเปรียบเทียบวิธีต่างๆในการแบ่งหน้าพร้อมกับข้อเสีย

/*
create table my_table(
  id int primary key, -- index will be created
  my_column varchar
)
*/

fun keysetPaginationExample() {
    var lastId = Integer.MIN_VALUE
    do {

        val someItems =
        myRepository.findTop100ByMyTableIdAfterOrderByMyTableId(lastId)

        if (someItems.isEmpty()) break

        lastId = someItems.last().myTableId

        for (item in someItems) {
          process(item)
        }

    } while (true)
}

0

ตัวอย่างที่มี JPA และ NativeQuery ดึงข้อมูลทุกครั้งที่ขนาดองค์ประกอบโดยใช้ออฟเซ็ต

public List<X> getXByFetching(int fetchSize) {
        int totalX = getTotalRows(Entity);
        List<X> result = new ArrayList<>();
        for (int offset = 0; offset < totalX; offset = offset + fetchSize) {
            EntityManager entityManager = getEntityManager();
            String sql = getSqlSelect(Entity) + " OFFSET " + offset + " ROWS";
            Query query = entityManager.createNativeQuery(sql, X.class);
            query.setMaxResults(fetchSize);
            result.addAll(query.getResultList());
            entityManager.flush();
            entityManager.clear();
        return result;
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.