Sự khác biệt giữa FetchType LAZY và EAGER trong Java Persistence API?


552

Tôi là người mới sử dụng API liên tục Java và Hibernate.

Sự khác biệt giữa FetchType.LAZYFetchType.EAGERtrong API liên tục Java là gì?


1
Tải bộ sưu tập EAGER có nghĩa là chúng được tìm nạp đầy đủ tại thời điểm cha mẹ của chúng được tìm nạp. Trong khi tải EAGER, sau đó tất cả con tôi được lấy. Đứa trẻ được tìm nạp trong Persistentset và PersistentList (hoặc PersistentBag), bên trong Túi liên tục, nó được hiển thị dưới dạng Danh sách mảng. Có đúng không ?? ..
geetha

Câu trả lời:


1064

Đôi khi bạn có hai thực thể và có một mối quan hệ giữa chúng. Ví dụ: bạn có thể có một thực thể được gọi Universityvà một thực thể khác được gọi Studentvà một trường đại học có thể có nhiều Sinh viên:

Thực thể Đại học có thể có một số thuộc tính cơ bản như id, tên, địa chỉ, v.v. cũng như một thuộc tính bộ sưu tập được gọi là sinh viên trả về danh sách sinh viên cho một trường đại học nhất định:

Một trường đại học có nhiều sinh viên

public class University {
   private String id;
   private String name;
   private String address;
   private List<Student> students;

   // setters and getters
}

Bây giờ khi bạn tải một trường đại học từ cơ sở dữ liệu, JPA sẽ tải các trường id, tên và địa chỉ của nó cho bạn. Nhưng bạn có hai lựa chọn về cách sinh viên nên được tải:

  1. Để tải nó cùng với các trường còn lại (tức là háo hức), hoặc
  2. Để tải nó theo yêu cầu (tức là lười biếng) khi bạn gọi getStudents()phương thức của trường đại học .

Khi một trường đại học có nhiều sinh viên, việc tải tất cả các sinh viên cùng với nó là không hiệu quả, đặc biệt là khi họ không cần thiết và trong những trường hợp tương tự, bạn có thể tuyên bố rằng bạn muốn sinh viên được tải khi họ thực sự cần thiết. Điều này được gọi là lười tải.

Đây là một ví dụ, nơi studentsđược đánh dấu rõ ràng sẽ được tải một cách háo hức:

@Entity
public class University {

    @Id
    private String id;

    private String name;

    private String address;

    @OneToMany(fetch = FetchType.EAGER)
    private List<Student> students;

    // etc.    
}

Và đây là một ví dụ studentsđược đánh dấu rõ ràng để được tải một cách lười biếng:

@Entity
public class University {

    @Id
    private String id;

    private String name;

    private String address;

    @OneToMany(fetch = FetchType.LAZY)
    private List<Student> students;

    // etc.
}

5
@BehrangSaeedzadeh bạn có thể liệt kê một số khác biệt thực tế hoặc ưu điểm và nhược điểm của từng loại tải (ngoài hiệu quả bạn đã đề cập). Tại sao một người muốn sử dụng tải háo hức?
ADTC

73
@ADTC Để tải lười hoạt động, phiên JDBC vẫn phải được mở khi các thực thể đích muốn được tải vào bộ nhớ bằng cách gọi phương thức getter (ví dụ getStudents()), nhưng đôi khi điều này là không thể, bởi vì tại thời điểm phương thức này được gọi, phiên đã đóng và thực thể tách ra. Tương tự, đôi khi chúng ta có kiến ​​trúc máy khách / máy chủ (ví dụ: máy khách Swing / máy chủ JEE) và các thực thể / DTO được truyền qua dây cho máy khách và thường xuyên nhất trong các trường hợp tải lười biếng này sẽ không hoạt động do cách các thực thể được nối tiếp qua dây.
Chương trình lập trình

4
Tôi muốn thêm một số thông tin vào câu trả lời này từ cuốn sách của mình - Để tiết kiệm bộ nhớ, tải nhanh thường được sử dụng cho một đến nhiều và nhiều mối quan hệ. Đối với một, nói chung, Eager được sử dụng.
Erran Morad

2
Trong tải lười biếng, khi tôi gọi getStudents()phương thức lần đầu tiên, các kết quả có được lưu trong bộ nhớ cache không? để lần sau tôi có thể truy cập những kết quả đó nhanh hơn?
JavaT kỹ thuật

2
@JavaT kỹ thuật tùy thuộc vào việc bạn bật bộ đệm cấp hai (được bật theo mặc định)
Ced

285

Về cơ bản,

LAZY = fetch when needed
EAGER = fetch immediately

11
Rất rõ ràng nhưng chỉ sau khi đọc câu trả lời của @ Behang. Cảm ơn bạn đã tóm tắt rõ ràng. :-)
Nabin

66

EAGERtải các bộ sưu tập có nghĩa là chúng được tìm nạp đầy đủ tại thời điểm cha mẹ của chúng được tìm nạp. Vì vậy, nếu bạn có Coursevà nó có List<Student>, tất cả các sinh viên được tìm nạp từ cơ sở dữ liệu tại thời điểm Courseđược tìm nạp.

LAZYmặt khác có nghĩa là nội dung của Listchỉ được tìm nạp khi bạn cố gắng truy cập chúng. Ví dụ, bằng cách gọi course.getStudents().iterator(). Gọi bất kỳ phương thức truy cập nào trên Listsẽ bắt đầu một cuộc gọi đến cơ sở dữ liệu để lấy các phần tử. Điều này được thực hiện bằng cách tạo Proxy xung quanh List(hoặc Set). Vì vậy, các bộ sưu tập của bạn lười biếng, các loại bê tông không ArrayListHashSet, nhưng PersistentSetPersistentList(hoặc PersistentBag)


Tôi đã sử dụng khái niệm đó trong việc tìm nạp các chi tiết của một thực thể con, nhưng tôi không thể thấy bất kỳ sự khác biệt nào giữa chúng. Khi tôi chỉ định tìm nạp Eager, nó tìm nạp mọi thứ và khi tôi gỡ lỗi, tôi thấy "Bean hoãn lại" ở thực thể con. Khi tôi nói course.getStudents(), nó kích hoạt một truy vấn SQL (đã thấy trên bảng điều khiển). Trong kiểu tìm nạp Lazy cũng vậy, điều tương tự cũng xảy ra. Vì vậy, sự khác biệt là gì ??
Neha Choudhary

bộ sưu tập háo hức được tìm nạp khi thực thể sở hữu được tải. Bộ sưu tập lười biếng được lấy khi bạn truy cập chúng. Nếu đây không phải là hành vi mà bạn đã thấy, có lẽ đã xảy ra sự cố với môi trường của bạn (ví dụ: chạy các phiên bản cũ của một lớp)
Bozho

1
@Bozho Bạn chỉ định tải bộ sưu tập lười biếng. Một trường chuỗi đơn giản có thể được tải lười biếng?
vikiiii

Không. Bạn cần sử dụng truy vấn hoặc thực thể được ánh xạ khác để nhận tập hợp con của các cột
Bozho

@Bozho, hey bạn có thể xin vui lòng trả lời này sau đó nếu nó được đặt trên fetchtype = LAZYmột trong những mặc định ngay cả khi cố gắng để có được bộ sưu tập với hibernete getter ném một lỗi nói với tôi nó không thể đánh giá
Все Едно

16

Tôi có thể xem xét hiệu suất và sử dụng bộ nhớ. Một điểm khác biệt lớn là chiến lược tìm nạp EAGER cho phép sử dụng đối tượng dữ liệu được tìm nạp mà không cần phiên. Tại sao?
Tất cả dữ liệu được tìm nạp khi dữ liệu được đánh dấu háo hức trong đối tượng khi phiên được kết nối. Tuy nhiên, trong trường hợp chiến lược tải lười biếng, lười biếng tải đối tượng được đánh dấu sẽ không truy xuất dữ liệu nếu phiên bị ngắt kết nối (sau session.close()câu lệnh). Tất cả những gì có thể được thực hiện bởi proxy ngủ đông. Chiến lược háo hức cho phép dữ liệu vẫn có sẵn sau khi kết thúc phiên.


11

Theo kiến ​​thức của tôi cả hai loại tìm nạp phụ thuộc vào yêu cầu của bạn.

FetchType.LAZY là theo yêu cầu (tức là khi chúng tôi yêu cầu dữ liệu).

FetchType.EAGER là ngay lập tức (tức là trước khi yêu cầu của chúng tôi đến, chúng tôi đang tìm nạp bản ghi một cách không cần thiết)


11

Theo mặc định, đối với tất cả các đối tượng bộ sưu tập và bản đồ, quy tắc tìm nạp là FetchType.LAZYvà đối với các trường hợp khác, nó tuân theo FetchType.EAGERchính sách.
Tóm lại, @OneToManyvà các @ManyToManymối quan hệ không tìm nạp các đối tượng liên quan (bộ sưu tập và bản đồ) một cách rõ ràng nhưng hoạt động truy xuất được xếp tầng qua trường trong @OneToOne@ManyToOnecác đối tượng.

(lịch sự: - objectdbcom)


9

Cả hai FetchType.LAZYFetchType.EAGERđược sử dụng để xác định kế hoạch tìm nạp mặc định .

Thật không may, bạn chỉ có thể ghi đè gói tìm nạp mặc định cho tìm nạp LAZY. Tìm nạp EAGER kém linh hoạt và có thể dẫn đến nhiều vấn đề về hiệu suất .

Lời khuyên của tôi là kiềm chế sự thôi thúc làm cho các hiệp hội của bạn EAGER bởi vì việc tìm nạp là một trách nhiệm về thời gian truy vấn. Vì vậy, tất cả các truy vấn của bạn nên sử dụng chỉ thị tìm nạp để chỉ truy xuất những gì cần thiết cho trường hợp kinh doanh hiện tại.


2
"Tìm nạp EAGER kém linh hoạt và có thể dẫn đến nhiều vấn đề về hiệu suất." ... Một tuyên bố xác thực là "Sử dụng hoặc không sử dụng tìm nạp EAGER có thể dẫn đến các vấn đề về hiệu suất". Trong trường hợp cụ thể đó khi một trường khởi tạo lười biếng tốn kém để truy cập VÀ được sử dụng không thường xuyên, việc tìm nạp lười biếng sẽ có lợi cho hiệu suất. Nhưng, trong trường hợp khi một biến được sử dụng thường xuyên, việc khởi tạo lười biếng thực sự có thể làm giảm hiệu suất bằng cách yêu cầu nhiều chuyến đi đến cơ sở dữ liệu hơn là khởi tạo háo hức. Tôi sẽ đề nghị áp dụng FetchType một cách chính xác, không phải giáo điều.
scottb

Bạn đang quảng bá sách của bạn ở đây !!. Nhưng vâng, tôi cảm thấy nó phụ thuộc vào trường hợp sử dụng và kích thước đối tượng được đề cập trong mối quan hệ cardinality.
John Doe

6

Từ Javadoc :

Chiến lược EAGER là một yêu cầu đối với thời gian chạy của nhà cung cấp kiên trì rằng dữ liệu phải được lấy một cách háo hức. Chiến lược LAZY là một gợi ý cho thời gian chạy của nhà cung cấp kiên trì rằng dữ liệu nên được tải xuống một cách lười biếng khi lần đầu tiên được truy cập.

Ví dụ, háo hức là chủ động hơn lười biếng. Lười biếng chỉ xảy ra khi sử dụng lần đầu (nếu nhà cung cấp đưa ra gợi ý), trong khi với những điều háo hức (có thể) được tải trước.


1
"lần đầu tiên sử dụng" nghĩa là gì?
leon

@leon: Giả sử bạn có một thực thể với trường háo hức và trường lười. Khi bạn nhận được thực thể, trường háo hức sẽ được tải từ DB vào thời điểm bạn nhận được tham chiếu thực thể, nhưng trường lười có thể không có. Nó sẽ chỉ được tìm nạp khi bạn cố truy cập vào trường thông qua trình truy cập của nó.
TJ Crowder

@TJ Crowder, mặc định khi không có fetchtype được xác định là gì?
Mahmoud Saleh

@MahmoudSaleh: Tôi không có ý kiến ​​gì. Nó có thể thay đổi dựa trên một cái gì đó. Tôi đã không sử dụng JPA trong một dự án thực tế vì vậy tôi đã không hiểu được ý nghĩa của nó.
TJ Crowder

2
@MahmoudS: Fetchtypes mặc định: OneToMany: LAZY, ManyToOne: EAGER, ManyToMany: LAZY, OneToOne: EAGER, Cột: EAGER
Markus Pscheidt

5

Các LazyFetch loại là theo mặc định lựa chọn bởi Hibernate trừ khi bạn đánh dấu một cách rõ ràngEager Fetch loại. Để chính xác hơn và súc tích, sự khác biệt có thể được nêu ra dưới đây.

FetchType.LAZY = Điều này không tải các mối quan hệ trừ khi bạn gọi nó thông qua phương thức getter.

FetchType.EAGER = Điều này tải tất cả các mối quan hệ.

Ưu và nhược điểm của hai loại tìm nạp này.

Lazy initialization cải thiện hiệu suất bằng cách tránh tính toán không cần thiết và giảm yêu cầu bộ nhớ.

Eager initialization tiêu thụ nhiều bộ nhớ hơn và tốc độ xử lý chậm.

Có nói rằng, tùy thuộc vào tình huống một trong những khởi tạo này có thể được sử dụng.


1
Tuyên bố rằng nó "không tải các mối quan hệ trừ khi bạn gọi nó thông qua phương thức getter" rất quan trọng cần lưu ý, và cũng là một quyết định thiết kế bị trì hoãn khá nhiều trong trường hợp của tôi. Tôi vừa gặp một trường hợp mà tôi cho rằng nó sẽ tải nó khi truy cập và không, bởi vì tôi đã không gọi một hàm getter cho nó. Nhân tiện, cái gì tạo thành một chức năng "getter"? JPA sẽ trì hoãn tải tài sản cho đến khi một hàm được gọi getMemberđược gọi chính xác khớp với mẫu tên của thành viên?
ToVine

3

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;
        }

        }

Tiêu đề

    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();
        }
    }

    }

Kiểm tra phương thức lấy () của Main.java. Khi chúng tôi nhận đề, sau đó bộ sưu tập của mình listBooks , chú thích với @OneToMany, sẽ được nạp một cách lười biếng. Tuy nhiên, mặt khác, các hiệp hội liên quan đến Sách của chủ đề bộ sưu tập , được chú thích @ManyToOne, tải nhanh chóng (bởi [default][1]for @ManyToOne, fetchType=EAGER). Chúng tôi có thể thay đổi hành vi bằng cách đặt fetchType.EAGER trên @OneToManySubject.java hoặc fetchType.LAZY trên @ManyToOneBooks.java.


1

chung enum FetchType mở rộng java.lang.Enum Xác định các chiến lược để tìm nạp dữ liệu từ cơ sở dữ liệu. Chiến lược EAGER là một yêu cầu đối với thời gian chạy của nhà cung cấp kiên trì rằng dữ liệu phải được lấy một cách háo hức. Chiến lược LAZY là một gợi ý cho thời gian chạy của nhà cung cấp kiên trì rằng dữ liệu nên được tải xuống một cách lười biếng khi lần đầu tiên được truy cập. Việc triển khai được phép lấy dữ liệu một cách háo hức mà gợi ý chiến lược LAZY đã được chỉ định. Ví dụ: @Basic (fetch = LAZY) Chuỗi được bảo vệ getName () {tên trả về; }

Nguồn


1

Tôi muốn thêm ghi chú này vào những gì "Kyung Hwan Min" đã nói ở trên.

Giả sử bạn đang sử dụng Spring Rest với kiến ​​trúc sư đơn giản này:

Trình điều khiển <-> Dịch vụ <-> Kho lưu trữ

Và bạn muốn trả lại một số dữ liệu cho giao diện người dùng, nếu bạn đang sử dụng FetchType.LAZY, bạn sẽ có một ngoại lệ sau khi bạn trả lại dữ liệu cho phương thức điều khiển do phiên được đóng trong Dịch vụ để JSON Mapper Objectkhông thể lấy dữ liệu.

Có ba tùy chọn phổ biến để giải quyết vấn đề này, tùy thuộc vào thiết kế, hiệu suất và nhà phát triển:

  1. Cách dễ nhất là sử dụng FetchType.EAGER, do đó phiên sẽ vẫn tồn tại ở phương thức điều khiển.
  2. Các giải pháp chống mô hình , để làm cho phiên hoạt động cho đến khi việc thực thi kết thúc, nó là một vấn đề hiệu năng rất lớn trong hệ thống.
  3. Cách thực hành tốt nhất là sử dụng FetchType.LAZYvới phương thức chuyển đổi để truyền dữ liệu từ Entityđối tượng dữ liệu khác DTOvà gửi nó đến bộ điều khiển, do đó không có ngoại lệ nếu phiên đóng.

1

Xin chào, tôi đã đính kèm 2 ảnh để giúp bạn hiểu điều này. nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây


0

@ drop-Shadow nếu bạn đang sử dụng Hibernate, bạn có thể gọi Hibernate.initialize()khi bạn gọi getStudents()phương thức:

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;
    }
    //...
}

0

LAZY: Nó tìm nạp các thực thể con một cách lười biếng, tức là tại thời điểm tìm nạp thực thể cha mẹ, nó chỉ tìm nạp proxy (được tạo bởi cglib hoặc bất kỳ tiện ích nào khác) của các thực thể con và khi bạn truy cập vào bất kỳ thuộc tính nào của thực thể con thì nó thực sự được tìm nạp bởi chế độ ngủ đông.

EAGER: nó tìm nạp các thực thể con cùng với cha mẹ.

Để hiểu rõ hơn, hãy truy cập tài liệu của Jboss hoặc bạn có thể sử dụng hibernate.show_sql=truecho ứng dụng của mình và kiểm tra các truy vấn do chế độ ngủ đông phát hành.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.