JPA eager fetch ไม่เข้าร่วม


113

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

ตัวอย่าง: บุคคลมีที่อยู่เดียว ที่อยู่อาจเป็นของคนจำนวนมาก คลาสเอนทิตีที่มีคำอธิบายประกอบ JPA มีลักษณะดังนี้:

@Entity
public class Person {
    @Id
    public Integer id;

    public String name;

    @ManyToOne(fetch=FetchType.LAZY or EAGER)
    public Address address;
}

@Entity
public class Address {
    @Id
    public Integer id;

    public String name;
}

ถ้าฉันใช้แบบสอบถาม JPA:

select p from Person p where ...

JPA / Hibernate สร้างแบบสอบถาม SQL หนึ่งรายการเพื่อเลือกจากตารางบุคคลจากนั้นแบบสอบถามที่อยู่ที่แตกต่างกันสำหรับแต่ละคน:

select ... from Person where ...
select ... from Address where id=1
select ... from Address where id=2
select ... from Address where id=3

สิ่งนี้แย่มากสำหรับชุดผลลัพธ์ขนาดใหญ่ หากมี 1,000 คนจะสร้างแบบสอบถาม 1001 รายการ (1 จากบุคคลและ 1,000 คนที่แตกต่างจากที่อยู่) ฉันรู้สิ่งนี้เพราะฉันกำลังดูบันทึกการสืบค้นของ MySQL ฉันเข้าใจว่าการตั้งค่าประเภทการดึงข้อมูลของที่อยู่ให้กระตือรือร้นจะทำให้ JPA / Hibernate ค้นหาโดยอัตโนมัติด้วยการเข้าร่วม อย่างไรก็ตามไม่ว่าจะดึงข้อมูลประเภทใดก็ยังคงสร้างแบบสอบถามที่แตกต่างกันสำหรับความสัมพันธ์

เมื่อฉันบอกอย่างชัดเจนให้เข้าร่วมเท่านั้นที่จะเข้าร่วม:

select p, a from Person p left join p.address a where ...

ฉันขาดอะไรที่นี่? ตอนนี้ฉันต้องเขียนโค้ดด้วยมือทุกแบบสอบถามเพื่อให้เหลือรวมความสัมพันธ์แบบกลุ่มต่อหนึ่ง ฉันใช้การใช้งาน JPA ของ Hibernate กับ MySQL

แก้ไข:ปรากฏขึ้น (ดูคำถามที่พบบ่อยของไฮเบอร์เนตที่นี่และที่นี่ ) ซึ่งFetchTypeไม่ส่งผลต่อการค้นหาของ JPA ดังนั้นในกรณีของฉันฉันบอกให้เข้าร่วมอย่างชัดเจน


5
ลิงก์ไปยังรายการคำถามที่พบบ่อยใช้งานไม่ได้นี่คือหนึ่ง
n0weak

คำตอบ:


99

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

  • SELECT => หนึ่งแบบสอบถามสำหรับเอนทิตีรูท + หนึ่งแบบสอบถามสำหรับเอนทิตีที่แมปที่เกี่ยวข้อง / คอลเลกชันของแต่ละเอนทิตีราก = (n + 1) คิวรี
  • SUBSELECT => หนึ่งแบบสอบถามสำหรับเอนทิตีรูท + แบบสอบถามที่สองสำหรับเอนทิตีที่แมปที่เกี่ยวข้อง / คอลเลกชันของเอนทิตีรากทั้งหมดที่ดึงมาในคิวรีแรก = 2 คิวรี
  • JOIN => หนึ่งคิวรีเพื่อดึงทั้งเอนทิตีรูทและเอนทิตี / คอลเลกชันที่แมปทั้งหมด = 1 คิวรี

ดังนั้นSELECTและJOINเป็นสองขั้วและSUBSELECTอยู่ในระหว่าง เราสามารถเลือกกลยุทธ์ที่เหมาะสมตามรูปแบบโดเมนของเธอ / เขา

โดยค่าเริ่มต้นSELECTจะถูกใช้โดยทั้ง JPA / EclipseLink และ Hibernate สิ่งนี้สามารถแทนที่ได้โดยใช้:

@Fetch(FetchMode.JOIN) 
@Fetch(FetchMode.SUBSELECT)

ในโหมดไฮเบอร์เนต นอกจากนี้ยังช่วยให้การตั้งค่าSELECTโหมดอย่างชัดเจนโดยใช้ซึ่งสามารถปรับได้โดยใช้ชุดขนาดเช่น@Fetch(FetchMode.SELECT)@BatchSize(size=10)

คำอธิบายประกอบที่สอดคล้องกันใน EclipseLink คือ:

@JoinFetch
@BatchFetch

5
เหตุใดจึงมีการตั้งค่าเหล่านั้น คิดว่าต้องใช้ JOIN เกือบตลอดเวลา ตอนนี้ฉันต้องทำเครื่องหมายการแมปทั้งหมดด้วยคำอธิบายประกอบเฉพาะไฮเบอร์เนต
vbezhenar

4
ที่น่าสนใจ แต่น่าเศร้าที่ @Fetch (FetchMode.JOIN) ใช้ไม่ได้เลยสำหรับฉัน (ไฮเบอร์เนต 4.2.15) ใน JPQL เช่นเดียวกับในเกณฑ์
Aphax

3
คำอธิบายประกอบไฮเบอร์เนตดูเหมือนจะไม่ได้ผลสำหรับฉันเลยเช่นกันโดยใช้ Spring JPA
TheBakker

2
@Aphax อาจเป็นเพราะ Hibernate ใช้กลยุทธ์เริ่มต้นที่แตกต่างกันสำหรับ JPAQL / Criteria vs em.find () ดูvladmihalcea.com/2013/10/17/… และเอกสารอ้างอิง
Joshua Davis

1
@vbezhenar (และอ่านความคิดเห็นของเขาในภายหลัง): ข้อความค้นหา JOIN สร้างผลิตภัณฑ์คาร์ทีเซียนในฐานข้อมูลดังนั้นคุณควรแน่ใจว่าคุณต้องการให้คำนวณผลิตภัณฑ์คาร์ทีเซียนนั้น โปรดทราบว่าหากคุณใช้การเข้าร่วมการดึงข้อมูลแม้ว่าคุณจะใส่ LAZY มันก็จะถูกโหลดอย่างกระตือรือร้น
Walfrat

45

"mxc" ถูกต้อง fetchTypeเพียงระบุว่าเมื่อใดควรแก้ไขความสัมพันธ์

ในการเพิ่มประสิทธิภาพการโหลดโดยใช้การรวมภายนอกคุณต้องเพิ่ม

@Fetch(FetchMode.JOIN)

ไปยังสนามของคุณ นี่เป็นคำอธิบายประกอบเฉพาะแบบจำศีล


6
สิ่งนี้ใช้ไม่ได้สำหรับฉันกับ Hibernate 4.2.15 ใน JPQL หรือ Criteria
Aphax

6
@Aphax ฉันคิดว่านั่นเป็นเพราะ JPAQL และ Criteria ไม่เป็นไปตามข้อกำหนด Fetch คำอธิบายประกอบ Fetch ใช้งานได้กับ em.find (), AFAIK เท่านั้น ดูvladmihalcea.com/2013/10/17/… นอกจากนี้ดูไฮเบอร์เนต docos ฉันค่อนข้างมั่นใจว่านี่ครอบคลุมที่ไหนสักแห่ง
Joshua Davis

@JoshuaDavis สิ่งที่ฉันหมายถึงคือคำอธิบายประกอบ \ @Fetch ไม่ได้ใช้การเพิ่มประสิทธิภาพ JOIN ใด ๆ ในการค้นหาไม่ว่าจะเป็น JPQL หรือ em.find () ฉันเพิ่งลองใช้ Hibernate 5.2 อีกครั้ง + และมันก็ยังเหมือนเดิม
Aphax

38

แอตทริบิวต์ fetchType ควบคุมว่าจะดึงฟิลด์คำอธิบายประกอบทันทีเมื่อมีการดึงข้อมูลเอนทิตีหลัก ไม่จำเป็นต้องกำหนดวิธีการสร้างคำสั่ง fetch การใช้งาน sql จริงขึ้นอยู่กับผู้ให้บริการที่คุณใช้ toplink / hibernate เป็นต้น

หากคุณตั้งค่าfetchType=EAGERนี้หมายความว่าฟิลด์คำอธิบายประกอบจะถูกเติมด้วยค่าในเวลาเดียวกันกับฟิลด์อื่น ๆ ในเอนทิตี ดังนั้นหากคุณเปิดผู้จัดการเอนทิตีดึงวัตถุบุคคลของคุณจากนั้นปิดผู้จัดการเอนทิตีจากนั้นการทำบุคคลที่อยู่จะไม่ส่งผลให้มีการโยนข้อยกเว้นการโหลดขี้เกียจ

หากคุณตั้งค่าfetchType=LAZYฟิลด์จะถูกเติมเมื่อมีการเข้าถึงเท่านั้น หากคุณปิดผู้จัดการเอนทิตีไปแล้วข้อยกเว้นการโหลดแบบขี้เกียจจะถูกโยนทิ้งถ้าคุณทำ person.address ในการโหลดฟิลด์คุณต้องใส่เอนทิตีกลับเข้าไปในบริบทของเอนทิตีมังเกอร์ด้วย em.merge () จากนั้นทำการเข้าถึงฟิลด์จากนั้นปิดเอนทิตีผู้จัดการ

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

สำหรับส่วนที่สองของคำถาม - จะจำศีลเพื่อสร้าง SQL ที่เหมาะสมได้อย่างไร

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


20

ลองใช้:

select p from Person p left join FETCH p.address a where...

มันใช้ได้กับฉันในลักษณะเดียวกันกับ JPA2 / EclipseLink แต่ดูเหมือนว่าคุณสมบัตินี้จะมีอยู่ในJPA1 ด้วย :


7

หากคุณใช้ EclipseLink แทน Hibernate คุณสามารถเพิ่มประสิทธิภาพการสืบค้นของคุณโดย "คำแนะนำการสืบค้น" ดูบทความนี้จากคราสวิกิพีเดีย: EclipseLink / ตัวอย่าง / JPA / QueryOptimization

มีบทหนึ่งเกี่ยวกับ "การอ่านร่วม"


2

เพื่อเข้าร่วมคุณสามารถทำหลายสิ่งได้ (โดยใช้ eclipselink)

  • ใน jpql คุณสามารถทำการดึงเข้าร่วมทางซ้ายได้

  • ในแบบสอบถามที่ตั้งชื่อคุณสามารถระบุคำใบ้แบบสอบถาม

  • ใน TypedQuery คุณสามารถพูดอะไรบางอย่างเช่น

    query.setHint("eclipselink.join-fetch", "e.projects.milestones");

  • นอกจากนี้ยังมีคำแนะนำการดึงข้อมูลเป็นชุด

    query.setHint("eclipselink.batch", "e.address");

ดู

http://java-persistence-performance.blogspot.com/2010/08/batch-fetching-optimizing-object-graph.html


1

ฉันมีปัญหานี้อย่างแน่นอนยกเว้นว่าคลาส Person มีคลาสคีย์แบบฝัง วิธีแก้ปัญหาของฉันคือการรวมเข้ากับแบบสอบถามและลบ

@Fetch(FetchMode.JOIN)

คลาส ID ที่ฝังของฉัน:

@Embeddable
public class MessageRecipientId implements Serializable {

    @ManyToOne(targetEntity = Message.class, fetch = FetchType.LAZY)
    @JoinColumn(name="messageId")
    private Message message;
    private String governmentId;

    public MessageRecipientId() {
    }

    public Message getMessage() {
        return message;
    }

    public void setMessage(Message message) {
        this.message = message;
    }

    public String getGovernmentId() {
        return governmentId;
    }

    public void setGovernmentId(String governmentId) {
        this.governmentId = governmentId;
    }

    public MessageRecipientId(Message message, GovernmentId governmentId) {
        this.message = message;
        this.governmentId = governmentId.getValue();
    }

}

-1

สองสิ่งเกิดขึ้นกับฉัน

ขั้นแรกคุณแน่ใจหรือไม่ว่าคุณหมายถึง ManyToOne สำหรับที่อยู่ นั่นหมายความว่าหลายคนจะมีที่อยู่เดียวกัน หากมีการแก้ไขสำหรับหนึ่งในนั้นจะมีการแก้ไขทั้งหมด นั่นคือเจตนาของคุณหรือไม่? 99% ของเวลาที่อยู่เป็น "ส่วนตัว" (ในแง่ที่ว่าเป็นของคนเพียงคนเดียว)

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

ฉันบอกว่าเพราะความเข้าใจของคุณเกี่ยวกับวิธีการทำงานนี้ถูกต้องโดยพื้นฐานจากที่ฉันนั่ง


นี่คือตัวอย่างที่สร้างขึ้นซึ่งใช้หลายต่อหนึ่ง ที่อยู่บุคคลอาจไม่ใช่ตัวอย่างที่ดีที่สุด ฉันไม่เห็นประเภทการดึงข้อมูลอื่น ๆ ในโค้ดของฉัน
Steve Kuo

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

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