อะไรคือความแตกต่างระหว่าง JOIN และ JOIN FETCH เมื่อใช้ JPA และ Hibernate


183

โปรดช่วยฉันเข้าใจว่าจะใช้ JOIN ปกติที่ไหนและที่ไหน FETCH เข้าร่วม

ตัวอย่างเช่นหากเรามีสองข้อความค้นหาเหล่านี้

FROM Employee emp
JOIN emp.department dep

และ

FROM Employee emp
JOIN FETCH emp.department dep

มีความแตกต่างระหว่างพวกเขาหรือไม่? ถ้าใช่จะใช้อันไหนดี?


2
คุณสามารถค้นหาได้ที่นี่ลิงค์อ่าน14.3 สมาคมและผู้ร่วมงาน
Angga

4
ฉันผ่านการตรวจสอบเอกสารนั้นแล้ว แต่ยังไม่รู้ว่าฉันควรใช้ JOIN ที่ไหนและ FETCH เข้าร่วมที่ใด
abbas

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

เพื่อช่วยในการตามล่าหากลยุทธ์
Eddie B

1
@ShameeraAnuranga ฉันคิดว่าในกรณีนี้คุณจะต้องเข้าร่วมด้านซ้ายด้านนอก
abbas

คำตอบ:


181

ในแบบสอบถามทั้งสองนี้คุณกำลังใช้ JOIN เพื่อสอบถามพนักงานทั้งหมดที่มีแผนกที่เกี่ยวข้องอย่างน้อยหนึ่งแผนก

แต่ความแตกต่างคือ: ในแบบสอบถามแรกที่คุณส่งคืนเฉพาะผู้ว่าจ้างสำหรับไฮเบอร์เนต ในแบบสอบถามที่สองคุณจะส่งคืนนายจ้างและหน่วยงานที่เกี่ยวข้องทั้งหมด

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

คุณสามารถใช้แบบสอบถามที่สองเมื่อคุณแน่ใจว่าคุณต้องการแผนกของพนักงานแต่ละคน หากคุณไม่ต้องการแผนกให้ใช้แบบสอบถามแรก

ฉันขอแนะนำให้อ่านลิงค์นี้หากคุณต้องการใช้เงื่อนไข WHERE (สิ่งที่คุณอาจต้องการ): วิธีการแสดง JPQL อย่างถูกต้อง "เข้าร่วมเรียก" กับ "ที่ไหน" ประโยคเป็น JPA 2 CriteriaQuery?

ปรับปรุง

หากคุณไม่ได้ใช้fetchและหน่วยงานยังคงถูกส่งกลับเป็นเพราะการทำแผนที่ของคุณระหว่างพนักงานและกรม (ก@OneToMany) จะ setted FetchType.EAGERกับ ในกรณีนี้fetchแบบสอบถามHQL ใด ๆ (มีหรือไม่) FROM Employeeจะนำแผนกทั้งหมด โปรดจำไว้ว่าการทำแผนที่ * ToOne ( @ManyToOneและ@OneToOne) ทั้งหมดเป็น EAGER ตามค่าเริ่มต้น


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

1
@gstackoverflow ใช่
Dherik

ฉันใช้เคียวรีเนทีฟที่มีการดึงข้อมูล Lazy ทั้งสองด้านของความสัมพันธ์ แต่ยังคงโหลดลำดับชั้นของความสัมพันธ์ย่อย
Badamchi

มูลค่าการกล่าวขวัญว่าfetchจะต้องมีการใช้ถ้า (โดยใช้ตัวอย่างของเรา) คุณต้องการสั่งซื้อโดยแอตทริบิวต์บางแผนก มิฉะนั้น (ใช้ได้อย่างน้อย PG) คุณอาจได้รับERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list
ยาว

60

ในลิงค์นี้ฉันกล่าวถึงก่อนหน้านี้ในความคิดเห็นอ่านส่วนนี้:

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

"เข้าร่วม FETCH" นี้จะมีผลถ้าคุณมีคุณสมบัติ (fetch = FetchType.LAZY) สำหรับคอลเลกชันภายในเอนทิตี (ตัวอย่างร้อง)

และจะมีผลเฉพาะกับวิธีการ "เมื่อแบบสอบถามควรเกิดขึ้น" และคุณต้องรู้สิ่งนี้ด้วย :

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

สมาคมถูกดึงข้อมูล -> ประเภท "FETCH" ของคุณเมื่อใด

มันเป็นวิธีการ -> เข้าร่วม / เลือก / Subselect / แบทช์

ในกรณีของคุณ FETCH จะมีผลก็ต่อเมื่อคุณมีแผนกเป็นชุดภายในพนักงานบางสิ่งเช่นนี้ในเอนทิตี:

@OneToMany(fetch = FetchType.LAZY)
private Set<Department> department;

เมื่อคุณใช้

FROM Employee emp
JOIN FETCH emp.department dep

คุณจะได้รับและemp emp.depเมื่อคุณไม่ได้ใช้การดึงข้อมูลคุณยังสามารถรับได้emp.depแต่ไฮเบอร์เนตจะประมวลผลตัวเลือกอื่นไปยังฐานข้อมูลเพื่อรับชุดแผนกนั้น

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

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

ใช้การดึงข้อมูลเมื่อ:

  • ไม่มีชุด / ชุดใหญ่ที่ไม่ต้องการในเอนทิตีนั้นที่คุณจะได้รับ

  • การสื่อสารจากแอ็พพลิเคชันเซิร์ฟเวอร์ไปยังเซิร์ฟเวอร์ฐานข้อมูลไกลเกินไปและต้องการเวลานาน

  • คุณอาจจะต้องคอลเลกชันที่หลังเมื่อคุณไม่ได้มีการเข้าถึงมัน ( นอกของการทำธุรกรรมวิธีการ / ชั้น)


คุณช่วยอธิบายมันสำหรับคำค้นหาที่ฉันเพิ่งเขียนในคำถามที่ปรับปรุงแล้วได้ไหม
abbas

การพิจารณาที่มีประโยชน์: "ไม่มีคอลเลกชันที่ไม่จำเป็นจำนวนมาก / ชุดภายในนิติบุคคลที่คุณจะได้รับ"
Divs

แผนกต่างๆจะยังคงดึงข้อมูลอย่างกระตือรือร้นได้หรือไม่หากแผนกภายในพนักงานเป็นแบบListแทนที่จะเป็นSet?
Stephane

การใช้FETCHคำหลักในคำสั่ง JPQL บ่งบอกถึงคุณสมบัติที่ดึงมาอย่างกระตือรือร้นหรือไม่?
Stephane

15

เข้าร่วม

เมื่อใช้JOINกับการเชื่อมโยงเอนทิตี JPA จะสร้างการเข้าร่วมระหว่างเอนทิตีหลักและตารางเอนทิตีรองในคำสั่ง SQL ที่สร้างขึ้น

ดังนั้นให้ยกตัวอย่างของคุณเมื่อดำเนินการแบบสอบถาม JPQL นี้:

FROM Employee emp
JOIN emp.department dep

Hibernate กำลังสร้างคำสั่ง SQL ต่อไปนี้:

SELECT emp.*
FROM employee emp
JOIN department dep ON emp.department_id = dep.id

โปรดทราบว่าใน SQL SELECTข้อมีเพียงemployeeคอลัมน์ของตารางและไม่departmentคน เพื่อเรียกdepartmentคอลัมน์ของตารางที่เราจำเป็นต้องใช้แทนJOIN FETCHJOIN

เข้าร่วม FETCH

ดังนั้นเมื่อเปรียบเทียบกับJOINการJOIN FETCHอนุญาตให้คุณฉายคอลัมน์การเข้าร่วมในส่วนSELECTคำสั่ง SQL ที่สร้างขึ้น

ดังนั้นในตัวอย่างของคุณเมื่อดำเนินการแบบสอบถาม JPQL นี้:

FROM Employee emp
JOIN FETCH emp.department dep

Hibernate กำลังสร้างคำสั่ง SQL ต่อไปนี้:

SELECT emp.*, dept.*
FROM employee emp
JOIN department dep ON emp.department_id = dep.id

โปรดทราบว่าในเวลานี้ departmentคอลัมน์คอลัมน์จะถูกเลือกเช่นกันไม่ใช่เฉพาะคอลัมน์ที่เกี่ยวข้องกับเอนทิตีที่ระบุไว้ในFROMข้อ JPQL

นอกจากนี้ยังJOIN FETCHเป็นวิธีที่ดีในการจัดการกับการLazyInitializationExceptionใช้ Hibernate เมื่อคุณสามารถเริ่มต้นการเชื่อมโยงเอนทิตีโดยใช้FetchType.LAZYกลยุทธ์การดึงข้อมูลพร้อมกับเอนทิตีหลักที่คุณดึงข้อมูล


เป็นไปได้หรือไม่ที่จะใช้การเข้าร่วมจำนวนมากในแบบสอบถามเดียวกัน
A.Onur Özcan

2
คุณสามารถเข้าร่วมการเชื่อมโยงหลายต่อหลายและหนึ่งต่อหนึ่งและมากที่สุดหนึ่งคอลเลกชัน การดึงคอลเลกชันหลายอย่างหนึ่งต่อหลายคนหรือหลายต่อหลายสมาคมจะจบลงในCartesian สินค้า อย่างไรก็ตามหากคุณต้องการดึงหลาย ๆ คอลเล็กชันคุณสามารถใช้คิวรีรองสำหรับคอลเล็กชันที่สอง, สาม, ... , nth ตรวจสอบบทความนี้สำหรับรายละเอียดเพิ่มเติม
Vlad Mihalcea

5

ถ้าคุณมี@oneToOneการตั้งค่าการแมปFetchType.LAZYและคุณใช้แบบสอบถามที่สอง (เนื่องจากคุณต้องการให้โหลดวัตถุแผนกเป็นส่วนหนึ่งของวัตถุพนักงาน) สิ่งที่ไฮเบอร์เนตจะทำคืออะไรมันจะออกแบบสอบถามเพื่อดึงข้อมูลวัตถุแผนกสำหรับวัตถุพนักงานแต่ละคนที่ดึงจาก DB

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

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


2

Dherik: ฉันไม่แน่ใจเกี่ยวกับสิ่งที่คุณพูดเมื่อคุณไม่ใช้การดึงข้อมูลผลลัพธ์จะเป็นประเภท: List<Object[ ]>ซึ่งหมายถึงรายการของตารางวัตถุและไม่ใช่รายการของพนักงาน

Object[0] refers an Employee entity 
Object[1] refers a Departement entity 

เมื่อคุณใช้การดึงข้อมูลจะมีเพียงหนึ่งตัวเลือกและผลลัพธ์คือรายการของพนักงานList<Employee>ที่มีรายการขาออก มันจะแทนที่การประกาศที่ขี้เกียจของเอนทิตี


ฉันไม่รู้ว่าฉันเข้าใจความกังวลของคุณหรือไม่ หากคุณไม่ได้ใช้fetchแบบสอบถามของคุณจะส่งคืนเฉพาะพนักงาน หากแผนกแม้ในกรณีนี้จะยังคงถูกส่งคืนเป็นเพราะการจับคู่ระหว่างพนักงานและแผนก (@OneToMany) ของคุณถูกกำหนดด้วย FetchType.EAGER ในกรณีนี้fetchแบบสอบถามHQL ใด ๆ ( ไม่ว่าFROM Employeeจะมีหรือไม่) จะนำแผนกทั้งหมด
Dherik

โดยไม่ต้องใช้การดึงข้อมูล (เข้าร่วมเทอมเดียว) ผลลัพธ์จะเป็นอาร์เรย์ของคอลเลกชันสองแถวแรกคือคอลเลกชันของพนักงานและที่สองคือคอลเลกชันของแผนก ใช้การดึงข้อมูลแบบกระตือรือร้นหรือการดึงข้อมูลแบบขี้เกียจแผนกจะถูกดึงข้อมูล
Bilal BBB

หากไม่ดึงข้อมูล HQL สิ่งนี้จะเกิดขึ้นเฉพาะเมื่อการจับคู่ระหว่างพนักงานและแผนกเป็น EAGER ( @OneToMany(fetch = FetchType.EAGER) หากไม่ใช่ในกรณีนั้นแผนกจะไม่ถูกส่งคืน
Dherik

@Dherik ลองด้วยตัวคุณเองคุณจะได้ ClassCastException
Bilal BBB

ฉันคิดออกปัญหา ไม่ใช่ปัญหาการดึง แต่selectทำใน HQL ได้อย่างไร ลองSELECT emp FROM Employee emp JOIN FETCH emp.department depดู JPA / Hibernate มีพฤติกรรมของการกลับมานี้ListของObject[]เมื่อคุณ ommit SELECTส่วนหนึ่ง
Dherik
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.