ฉันเป็นมือใหม่กับ Java Persistence API และ Hibernate
ความแตกต่างระหว่างคืออะไรFetchType.LAZY
และFetchType.EAGER
ใน Java Api คงทน?
ฉันเป็นมือใหม่กับ Java Persistence API และ Hibernate
ความแตกต่างระหว่างคืออะไรFetchType.LAZY
และFetchType.EAGER
ใน Java Api คงทน?
คำตอบ:
บางครั้งคุณมีสองหน่วยงานและมีความสัมพันธ์ระหว่างพวกเขา ตัวอย่างเช่นคุณอาจมีเอนทิตีที่เรียกว่าUniversity
และเอนทิตีอื่นที่เรียกว่าStudent
และมหาวิทยาลัยอาจมีนักเรียนหลายคน:
หน่วยงานของมหาวิทยาลัยอาจมีคุณสมบัติพื้นฐานบางอย่างเช่น id, ชื่อ, ที่อยู่ ฯลฯ รวมถึงคุณสมบัติการรวบรวมที่เรียกว่านักเรียนที่ส่งคืนรายชื่อนักเรียนสำหรับมหาวิทยาลัยที่กำหนด:
public class University {
private String id;
private String name;
private String address;
private List<Student> students;
// setters and getters
}
ตอนนี้เมื่อคุณโหลดมหาวิทยาลัยจากฐานข้อมูล JPA จะโหลด id, ชื่อและฟิลด์ที่อยู่ของคุณ แต่คุณมีสองตัวเลือกสำหรับวิธีการโหลดนักเรียน:
getStudents()
วิธีการของมหาวิทยาลัยเมื่อมหาวิทยาลัยมีนักเรียนจำนวนมากมันไม่ได้มีประสิทธิภาพในการโหลดนักเรียนทั้งหมดพร้อมกับมันโดยเฉพาะอย่างยิ่งเมื่อพวกเขาไม่ต้องการและในกรณีเช่นนี้คุณสามารถประกาศว่าคุณต้องการให้นักเรียนโหลดเมื่อพวกเขาต้องการจริง ๆ สิ่งนี้เรียกว่าการโหลดแบบขี้เกียจ
นี่คือตัวอย่างที่students
มีการทำเครื่องหมายอย่างชัดเจนว่าจะโหลดอย่างกระตือรือร้น:
@Entity
public class University {
@Id
private String id;
private String name;
private String address;
@OneToMany(fetch = FetchType.EAGER)
private List<Student> students;
// etc.
}
และนี่คือตัวอย่างที่students
มีการทำเครื่องหมายอย่างชัดเจนว่าให้โหลดอย่างเกียจคร้าน:
@Entity
public class University {
@Id
private String id;
private String name;
private String address;
@OneToMany(fetch = FetchType.LAZY)
private List<Student> students;
// etc.
}
getStudents()
) แต่บางครั้งก็เป็นไปไม่ได้เพราะตามเวลาที่วิธีนี้ ถูกเรียกเซสชันถูกปิดไปแล้วและไม่ได้ใช้เอนทิตี ในทำนองเดียวกันบางครั้งเรามีสถาปัตยกรรมไคลเอนต์ / เซิร์ฟเวอร์ (เช่น Swing client / เซิร์ฟเวอร์ JEE) และเอนทิตี / DTOs จะถูกโอนผ่านสายไปยังไคลเอนต์และอีกครั้งบ่อยที่สุดในสถานการณ์เหล่านี้การโหลดที่ขี้เกียจจะไม่ทำงาน ถูกทำให้เป็นอนุกรมมากกว่าลวด
getStudents()
วิธีการนี้เป็นครั้งแรกผลลัพธ์จะถูกแคชหรือไม่ เพื่อที่ฉันจะสามารถเข้าถึงผลลัพธ์เหล่านั้นได้เร็วขึ้นในครั้งต่อไป
โดยทั่วไป
LAZY = fetch when needed
EAGER = fetch immediately
EAGER
โหลดคอลเลกชันหมายความว่าพวกเขาจะถูกดึงอย่างเต็มที่ในเวลาที่ผู้ปกครองจะถูกดึง ดังนั้นหากคุณมีCourse
และมี List<Student>
นักเรียนทุกคนจะดึงข้อมูลจากฐานข้อมูลในเวลาที่Course
ดึงข้อมูล
LAZY
ในทางตรงกันข้ามหมายความว่าเนื้อหาของList
จะถูกดึงเมื่อคุณพยายามที่จะเข้าถึงพวกเขา course.getStudents().iterator()
ยกตัวอย่างเช่นโดยการเรียก เรียกวิธีการเข้าถึงใด ๆ บนList
จะเริ่มต้นการโทรไปยังฐานข้อมูลเพื่อดึงองค์ประกอบ สิ่งนี้ถูกนำไปใช้โดยการสร้าง Proxy รอบ ๆList
(หรือSet
) ดังนั้นสำหรับคอลเล็กชั่นขี้เกียจของคุณประเภทคอนกรีตจะไม่ArrayList
และHashSet
แต่PersistentSet
และPersistentList
(หรือPersistentBag
)
course.getStudents()
มันจะทำการสืบค้น SQL (เห็นว่าบนคอนโซล) ในประเภทการดึงข้อมูลของ Lazy สิ่งเดียวกันก็เกิดขึ้นเช่นกัน ดังนั้นอะไรคือความแตกต่าง ??
fetchtype = LAZY
หนึ่งเริ่มต้นแม้ว่าพยายามที่จะได้รับคอลเลกชันที่มี hibernete ทะเยอทะยานพ่นข้อผิดพลาดบอกฉันมันไม่สามารถประเมิน
ฉันอาจพิจารณาถึงประสิทธิภาพและการใช้งานหน่วยความจำ ความแตกต่างที่สำคัญอย่างหนึ่งคือกลยุทธ์ดึงข้อมูลของ EAGER อนุญาตให้ใช้วัตถุดึงข้อมูลโดยไม่ต้องใช้เซสชัน ทำไม?
ข้อมูลทั้งหมดจะถูกดึงเมื่อกระตือรือร้นที่จะทำเครื่องหมายข้อมูลในวัตถุเมื่อเชื่อมต่อกับเซสชัน อย่างไรก็ตามในกรณีของกลยุทธ์การโหลดแบบสันหลังยาววัตถุที่ทำเครื่องหมายการโหลดแบบสันหลังยาวจะไม่ดึงข้อมูลหากเซสชันถูกตัดการเชื่อมต่อ (หลังจากsession.close()
คำสั่ง) ทุกสิ่งที่สามารถทำได้โดยพร็อกซีไฮเบอร์เนต กลยุทธ์กระตือรือร้นช่วยให้ข้อมูลยังคงพร้อมใช้งานหลังจากปิดเซสชัน
ตามความรู้ของฉันทั้งการดึงข้อมูลขึ้นอยู่กับความต้องการของคุณ
FetchType.LAZY
อยู่ในความต้องการ (เช่นเมื่อเราต้องการข้อมูล)
FetchType.EAGER
เป็นทันที (เช่นก่อนที่ความต้องการของเรามาเราจะดึงข้อมูลบันทึกโดยไม่จำเป็น)
ตามค่าเริ่มต้นสำหรับคอลเลกชันและวัตถุแผนที่ทั้งหมดกฎการดึงข้อมูลFetchType.LAZY
และสำหรับอินสแตนซ์อื่นนั้นเป็นไปตามFetchType.EAGER
นโยบาย
โดยสังเขป@OneToMany
และ@ManyToMany
ความสัมพันธ์ไม่ได้ดึงเอาวัตถุที่เกี่ยวข้อง (การรวบรวมและแผนที่) โดยนัย แต่การดำเนินการดึงข้อมูลจะถูกลดหลั่นผ่านเขตข้อมูลใน@OneToOne
และ@ManyToOne
วัตถุ
ทั้งสองFetchType.LAZY
และFetchType.EAGER
ถูกนำมาใช้ในการกำหนดค่าเริ่มต้นดึงข้อมูลแผน
ขออภัยคุณสามารถลบล้างแผนการดึงข้อมูลเริ่มต้นสำหรับการดึงข้อมูล LAZY เท่านั้น การดึงข้อมูล EAGER นั้นมีความยืดหยุ่นน้อยกว่าและสามารถนำไปสู่ปัญหาด้านประสิทธิภาพได้มากมาย
คำแนะนำของฉันคือยับยั้งการสร้างการเชื่อมโยงของคุณ EAGER เพราะการดึงข้อมูลเป็นความรับผิดชอบของเวลาสอบถาม ดังนั้นข้อความค้นหาทั้งหมดของคุณควรใช้คำสั่งดึงข้อมูลเพื่อดึงข้อมูลสิ่งที่จำเป็นสำหรับกรณีธุรกิจปัจจุบันเท่านั้น
จากJavadoc :
กลยุทธ์ EAGER เป็นข้อกำหนดของผู้ให้บริการคงอยู่ที่ข้อมูลจะต้องดึงข้อมูลอย่างกระตือรือร้น กลยุทธ์ของ LAZY คือคำใบ้ของผู้ให้บริการคงอยู่ที่ข้อมูลควรถูกเรียกอย่างเกียจคร้านเมื่อมีการเข้าถึงครั้งแรก
เช่นความกระตือรือร้นในเชิงรุกมากกว่าขี้เกียจ Lazy จะเกิดขึ้นเมื่อใช้ครั้งแรกเท่านั้น (หากผู้ให้บริการใช้คำใบ้) ในขณะที่มีสิ่งที่กระตือรือร้น (อาจ) ได้รับการดึงข้อมูลล่วงหน้า
Lazy
Fetch ประเภทเป็นค่าเริ่มต้นเลือกโดย Hibernate เว้นแต่ว่าคุณได้ทำเครื่องหมายEager
Fetch ประเภท เพื่อความแม่นยำและรัดกุมยิ่งขึ้นสามารถระบุความแตกต่างได้ดังต่อไปนี้
FetchType.LAZY
= สิ่งนี้ไม่ได้โหลดความสัมพันธ์เว้นแต่ว่าคุณจะเรียกใช้ผ่านวิธีการทะลุผ่าน
FetchType.EAGER
= สิ่งนี้จะโหลดความสัมพันธ์ทั้งหมด
ข้อดีและข้อเสียของการดึงข้อมูลทั้งสองประเภท
Lazy initialization
ปรับปรุงประสิทธิภาพโดยหลีกเลี่ยงการคำนวณที่ไม่จำเป็นและลดความต้องการหน่วยความจำ
Eager initialization
ใช้หน่วยความจำมากขึ้นและความเร็วในการประมวลผลช้า
ต้องบอกว่าขึ้นอยู่กับสถานการณ์อย่างใดอย่างหนึ่งของการเริ่มต้นเหล่านี้สามารถนำมาใช้
getMember
ว่าตรงกับรูปแบบชื่อของสมาชิกหรือไม่
Book.java
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
@Entity
@Table(name="Books")
public class Books implements Serializable{
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="book_id")
private int id;
@Column(name="book_name")
private String name;
@Column(name="author_name")
private String authorName;
@ManyToOne
Subject subject;
public Subject getSubject() {
return subject;
}
public void setSubject(Subject subject) {
this.subject = subject;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthorName() {
return authorName;
}
public void setAuthorName(String authorName) {
this.authorName = authorName;
}
}
Subject.java
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
@Entity
@Table(name="Subject")
public class Subject implements Serializable{
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="subject_id")
private int id;
@Column(name="subject_name")
private String name;
/**
Observe carefully i have mentioned fetchType.EAGER. By default its is fetchType.LAZY for @OneToMany i have mentioned it but not required. Check the Output by changing it to fetchType.EAGER
*/
@OneToMany(mappedBy="subject",cascade=CascadeType.ALL,fetch=FetchType.LAZY,
orphanRemoval=true)
List<Books> listBooks=new ArrayList<Books>();
public List<Books> getListBooks() {
return listBooks;
}
public void setListBooks(List<Books> listBooks) {
this.listBooks = listBooks;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
HibernateUtil.java
import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {
private static SessionFactory sessionFactory ;
static {
Configuration configuration = new Configuration();
configuration.addAnnotatedClass (Com.OneToMany.Books.class);
configuration.addAnnotatedClass (Com.OneToMany.Subject.class);
configuration.setProperty("connection.driver_class","com.mysql.jdbc.Driver");
configuration.setProperty("hibernate.connection.url", "jdbc:mysql://localhost:3306/hibernate");
configuration.setProperty("hibernate.connection.username", "root");
configuration.setProperty("hibernate.connection.password", "root");
configuration.setProperty("dialect", "org.hibernate.dialect.MySQLDialect");
configuration.setProperty("hibernate.hbm2ddl.auto", "update");
configuration.setProperty("hibernate.show_sql", "true");
configuration.setProperty(" hibernate.connection.pool_size", "10");
configuration.setProperty(" hibernate.cache.use_second_level_cache", "true");
configuration.setProperty(" hibernate.cache.use_query_cache", "true");
configuration.setProperty(" cache.provider_class", "org.hibernate.cache.EhCacheProvider");
configuration.setProperty("hibernate.cache.region.factory_class" ,"org.hibernate.cache.ehcache.EhCacheRegionFactory");
// configuration
StandardServiceRegistryBuilder builder = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties());
sessionFactory = configuration.buildSessionFactory(builder.build());
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
}
Main.java
import org.hibernate.Session;
import org.hibernate.SessionFactory;
public class Main {
public static void main(String[] args) {
SessionFactory factory=HibernateUtil.getSessionFactory();
save(factory);
retrieve(factory);
}
private static void retrieve(SessionFactory factory) {
Session session=factory.openSession();
try{
session.getTransaction().begin();
Subject subject=(Subject)session.get(Subject.class, 1);
System.out.println("subject associated collection is loading lazily as @OneToMany is lazy loaded");
Books books=(Books)session.get(Books.class, 1);
System.out.println("books associated collection is loading eagerly as by default @ManyToOne is Eagerly loaded");
/*Books b1=(Books)session.get(Books.class, new Integer(1));
Subject sub=session.get(Subject.class, 1);
sub.getListBooks().remove(b1);
session.save(sub);
session.getTransaction().commit();*/
}catch(Exception e){
e.printStackTrace();
}finally{
session.close();
}
}
private static void save(SessionFactory factory){
Subject subject=new Subject();
subject.setName("C++");
Books books=new Books();
books.setAuthorName("Bala");
books.setName("C++ Book");
books.setSubject(subject);
subject.getListBooks().add(books);
Session session=factory.openSession();
try{
session.beginTransaction();
session.save(subject);
session.getTransaction().commit();
}catch(Exception e){
e.printStackTrace();
}finally{
session.close();
}
}
}
ตรวจสอบวิธีการดึงข้อมูล () ของ Main.java เมื่อเราได้รับหัวเรื่องจากนั้นรายการคอลเลกชันหนังสือที่มีคำอธิบายประกอบ@OneToMany
จะถูกโหลดอย่างเกียจคร้าน แต่ในทางกลับกันสมาคมที่เกี่ยวข้องกับหนังสือเกี่ยวกับคอลเลกชันที่มีคำอธิบายประกอบ@ManyToOne
โหลดโหลดอย่างช้าๆ (โดย[default][1]
for @ManyToOne
, fetchType=EAGER
) เราสามารถเปลี่ยนพฤติกรรมโดยวาง fetchType.EAGER ใน@OneToMany
Subject.java หรือ fetchType.LAZY @ManyToOne
ใน Books.java
public enum FetchType ขยาย java.lang.Enum กำหนดกลยุทธ์สำหรับการดึงข้อมูลจากฐานข้อมูล กลยุทธ์ EAGER เป็นข้อกำหนดของผู้ให้บริการคงอยู่ที่ข้อมูลจะต้องดึงข้อมูลอย่างกระตือรือร้น กลยุทธ์ของ LAZY คือคำใบ้ของผู้ให้บริการคงอยู่ที่ข้อมูลควรถูกเรียกอย่างเกียจคร้านเมื่อมีการเข้าถึงครั้งแรก การดำเนินการได้รับอนุญาตให้ดึงข้อมูลอย่างกระตือรือร้นซึ่งคำใบ้กลยุทธ์ของ LAZY ได้ถูกระบุไว้ ตัวอย่าง: @Basic (fetch = LAZY) String ที่ป้องกัน getName () {ชื่อคืน; }
ฉันต้องการที่จะเพิ่มบันทึกนี้ในสิ่งที่ "Kyung Hwan Min" กล่าวไว้ข้างต้น
สมมติว่าคุณกำลังใช้ Spring Rest กับสถาปนิกที่เรียบง่ายนี้:
คอนโทรลเลอร์ <-> บริการ <-> พื้นที่เก็บข้อมูล
และคุณต้องการส่งคืนข้อมูลบางส่วนไปยังส่วนหน้าหากคุณใช้FetchType.LAZY
คุณจะได้รับข้อยกเว้นหลังจากที่คุณส่งคืนข้อมูลไปยังวิธีการควบคุมเนื่องจากเซสชันถูกปิดในบริการดังนั้นจึงJSON Mapper Object
ไม่สามารถรับข้อมูลได้
มีสามตัวเลือกทั่วไปในการแก้ปัญหานี้ขึ้นอยู่กับการออกแบบประสิทธิภาพและนักพัฒนา:
FetchType.EAGER
เพื่อให้เซสชันยังคงอยู่ที่วิธีการควบคุมFetchType.LAZY
กับวิธีการแปลงเพื่อถ่ายโอนข้อมูลจากEntity
ไปยังวัตถุข้อมูลอื่นDTO
และส่งไปยังตัวควบคุมดังนั้นจึงไม่มีข้อยกเว้นหากปิดเซสชัน@ drop-shadow ถ้าคุณใช้ Hibernate คุณสามารถโทรหาHibernate.initialize()
เมื่อคุณเรียกใช้getStudents()
เมธอด:
Public class UniversityDaoImpl extends GenericDaoHibernate<University, Integer> implements UniversityDao {
//...
@Override
public University get(final Integer id) {
Query query = getQuery("from University u where idUniversity=:id").setParameter("id", id).setMaxResults(1).setFetchSize(1);
University university = (University) query.uniqueResult();
***Hibernate.initialize(university.getStudents());***
return university;
}
//...
}
LAZY: มันดึงเอนทิตี้ของเด็กอย่างขี้เกียจนั่นคือตอนที่ดึงเอนทิตี้ของพาเรนต์มันเพิ่งดึงพร็อกซี (สร้างโดย cglib หรือยูทิลิตี้อื่น ๆ ) ของเอนทิตีลูกและเมื่อคุณเข้าถึงคุณสมบัติของเอนทิตีลูก
EAGER: ดึงเอนทิตีย่อยพร้อมกับพาเรนต์
เพื่อความเข้าใจที่ดีขึ้นให้ไปที่เอกสาร Jboss หรือคุณสามารถใช้hibernate.show_sql=true
แอพของคุณและตรวจสอบคำสั่งที่จำศีลของไฮเบอร์เนต