Різниця між FetchType LAZY та EAGER в API Java Persistent?


552

Я новачок у програмі Java Persistence API і в режимі глибокого сну.

У чому різниця між FetchType.LAZYі FetchType.EAGERв Java Persistence API?


1
Завантаження колекцій EAGER означає, що вони отримуються повністю під час отримання батьків. Поки EAGER завантажується, тоді вся моя дитина здобута. Дитина потрапляє у PersistentSet та PersistentList (або PersistentBag), всередині Persistent Bag, відображається у списку масивів. Правильно ?? ..
geetha

Відповіді:


1064

Іноді у вас є дві сутності, і між ними існують відносини. Наприклад, у вас може бути названа організація, Universityа інша названа, Studentа в університеті може бути багато студентів:

Суб'єкт університету може мати деякі основні властивості, такі як id, ім'я, адреса тощо, а також властивість колекції під назвою студентів, яка повертає список студентів для даного університету:

В університеті багато студентів

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

   // setters and getters
}

Тепер, коли ви завантажуєте університет із бази даних, JPA завантажує для вас поля ідентифікатора, імені та адреси. Але у вас є два варіанти завантаження студентів:

  1. Завантажити його разом з рештою полів (тобто охоче), або
  2. Завантажити його на вимогу (тобто ліниво), коли ви телефонуєте за 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.
}

5
@BehrangSaeedzadeh Ви можете перерахувати деякі практичні відмінності або переваги та недоліки кожного типу завантаження (крім ефективності, про яку Ви згадали). Навіщо хотіти використовувати нетерпляче завантаження?
ADTC

73
@ADTC Для того, щоб ледаче завантаження працювало, сеанс JDBC все одно повинен бути відкритим, коли цільові об'єкти хочуть завантажити в пам’ять шляхом виклику методу getter (наприклад getStudents()), але іноді це неможливо, оскільки до цього часу цей метод викликається, сесія вже закрита, а суб'єкт від'єднаний. Аналогічно, іноді ми маємо архітектуру клієнт / сервер (наприклад, Swing client / JEE-сервер), і сутності / DTO передаються клієнту по дроту, і знову ж таки найчастіше в цих сценаріях ледаче завантаження не працюватиме через те, як сутності серіалізуються по дроту.
TheFooProgrammer

4
Я хотів би додати цю відповідь до цієї відповіді у своїй книзі - Щоб зберегти пам’ять, Ледаче завантаження зазвичай використовується для одного до багатьох, а багато до багатьох стосунків. Для одного до одного, як правило, використовується Eager.
Ерран Морад

2
Під час ледачого завантаження, коли я getStudents()вперше викликаю метод, кешуються результати? щоб я міг отримати доступ до цих результатів швидше наступного разу?
JavaTechNI

2
@JavaTechnical залежить, якщо ви ввімкнете кеш другого рівня (увімкнено за замовчуванням)
Ced

285

В основному,

LAZY = fetch when needed
EAGER = fetch immediately

11
Дуже ясно, але лише після прочитання відповіді @ Беханга. Дякую за чітке резюме. :-)
Набін

66

EAGERзавантаження колекцій означає, що вони отримуються повністю в момент отримання батьком. Отже, якщо у вас є Courseі у неї є List<Student>, всі студенти виймаються з бази даних в момент її отримання Course.

LAZYз іншого боку, означає, що вміст Listфайлу дістається лише тоді, коли ви намагаєтесь отримати доступ до них. Наприклад, зателефонувавши course.getStudents().iterator(). Виклик будь-якого методу доступу за Listзаповітом ініціює виклик до бази даних для отримання елементів. Це реалізується шляхом створення проксі-сервера навколо List(або Set). Так що для ваших ледачих колекцій, типи бетону не ArrayListта HashSet, але PersistentSetі PersistentList(або PersistentBag)


Я використовував це поняття для отримання деталей дитячої сутності, але я не бачу різниці між ними. Коли я вказую Eager fetch, він отримує все, і коли я його налагоджую, я бачу "Бін відкладений" у дочірній сутності. Коли я кажу course.getStudents(), він запускає SQL-запит (бачив, що на консолі). У типу "Ледачий вибор" також відбувається те ж саме. Отже, в чому різниця ??
Neha Choudhary

нетерплячі колекції виймаються, коли завантажується власник. Ледачі колекції отримують, коли ви отримуєте доступ до них. Якщо це не поведінка вашої пилки, мабуть, щось не так з вашим оточенням (наприклад, запуск старих версій класу)
Bozho

1
@Bozho Ви вказали лише ледачу завантаження колекцій. Чи може просте рядове поле бути ледачим завантаженим?
vikiiii

Ні. Для отримання підмножини стовпців потрібно скористатися запитом або іншим відображеним об’єктом
Божо

@Bozho, ей, чи можете ви, будь ласка, відповісти на це тоді, якщо він встановлений за fetchtype = LAZYзамовчуванням, навіть якщо спробувати отримати колекцію з Getter hibernete кидає помилку, кажучи мені, що не може оцінити
Все Едно

16

Я можу врахувати продуктивність та використання пам'яті. Одна велика відмінність полягає в тому, що стратегія отримання EAGER дозволяє використовувати об'єкт даних без сеансу. Чому?
Усі дані дістаються, коли охоче позначені дані в об'єкті під час підключення сеансу. Однак, у випадку стратегії ледачого завантаження, лінивий завантажений позначений об'єкт не отримує дані, якщо сеанс відключений (після session.close()заяви). Все, що може зробити сплячий проксі. Страстна стратегія дозволяє залишатись доступними після закриття сесії.


11

Згідно з моїми знаннями, обидва типи вибору залежать від ваших вимог.

FetchType.LAZY є на вимогу (тобто коли нам потрібні дані).

FetchType.EAGER є негайним (тобто, перш ніж надходить наша вимога, ми зайво отримуємо запис)


11

За замовчуванням для всіх об’єктів колекції та картографування дістається правило вилучення, FetchType.LAZYа для інших випадків воно дотримується FetchType.EAGERполітики.
Коротше кажучи, @OneToManyі @ManyToManyвідносини не отримують пов'язаних об'єктів (колекція та карта) неявно, але операція пошуку каскадується через поле в @OneToOneі@ManyToOne .

(люб’язно: - objectdbcom)


9

Обидва FetchType.LAZYі FetchType.EAGERвикористовуються для визначення плану вибору за замовчуванням .

На жаль, ви можете змінити план вибору за замовчуванням для вилучення LAZY. Вибір EAGER є менш гнучким і може призвести до багатьох проблем з продуктивністю .

Моя порада - стримати прагнення зробити ваші асоціації EAGER, оскільки отримання даних - це відповідальність за час запитів. Тому всі ваші запити повинні використовувати вибірку директиви тільки отримати те , що необхідно для поточного бізнес - кейса.


2
"Вибір EAGER менш гнучкий і може призвести до багатьох проблем із продуктивністю." ... Вірніше твердження: "Використання чи не використання вилучення EAGER може призвести до проблем з продуктивністю". У тому конкретному випадку, коли ліниво ініціалізоване поле є дорогим для доступу та рідко використовуваним, ледачий вибір отримає користь. Але у випадку, коли змінна часто використовується, лінива ініціалізація може насправді погіршити продуктивність , вимагаючи більше відвідувань бази даних, ніж прагнення до ініціалізації. Я б запропонував застосувати FetchType правильно, а не догматично.
scottb

Ви рекламуєте свою книгу тут !!. Але так, я вважаю, що це залежить від випадку використання та розміру об'єкта, про який йдеться у відносинах кардинальності.
Джон Дої

6

Від Javadoc :

Стратегія EAGER - це вимога, що стосується часу наполегливого виконання постачальника, щоб дані повинні бути охоче отримані. Стратегія LAZY - це натяк на тривалість роботи постачальника наполегливості, що дані слід отримувати ліниво під час першого доступу.

Наприклад, прагнення є більш ініціативним, ніж ледачий. Ледачий трапляється лише при першому використанні (якщо постачальник підкаже), тоді як з нетерплячими речами (можливо) заздалегідь вибирайте.


1
що ви маєте на увазі під першим використанням?
leon

@leon: Скажіть, у вас є сутність з полем і полем. Коли ви отримаєте сутність, поле нетерпіння буде завантажене з БД до того моменту, коли ви отримаєте посилання на сутність, але поле для ледачих можливо не було. Це було б знайдено лише тоді, коли ви спробували отримати доступ до поля через його аксесуар.
TJ Crowder

@ TJ Crowder, що за замовчуванням, коли не визначено феттип?
Махмуд Салех

@MahmoudSaleh: Я поняття не маю. Мабуть, це залежить від чогось. Я не використовував JPA в реальному проекті, тому я не потрапив у кишки цього.
TJ Crowder

2
@MahmoudS: Феттітипи за замовчуванням: OneToMany: LAZY, ManyToOne: EAGER, ManyToMany: LAZY, OneToOne: EAGER, Стовпці: EAGER
Markus Pscheidt

5

Тип LazyFetch за замовчуванням вибирається Hibernate, якщо явно не позначеноEager тип Fetch. Щоб бути більш точним і стислим, різницю можна вказати нижче.

FetchType.LAZY = Це не завантажує відносини, якщо ви не викликаєте їх методом getter.

FetchType.EAGER = Це завантажує всі відносини.

Плюси і мінуси цих двох типів отримання.

Lazy initialization покращує продуктивність, уникаючи зайвих обчислень та зменшуючи потреби в пам'яті.

Eager initialization займає більше споживання пам'яті, а швидкість обробки - повільна.

Сказавши це, залежить від ситуації, чи може бути використана одна з цих ініціалізацій.


1
Зауваження, що воно "не завантажує відносини, якщо ви не посилаєтесь на нього методом getter", важливо відзначити, а також, на мій погляд, досить відстале дизайнерське рішення ... Я щойно стикався з випадком, коли я припускав, що це отримає його під час доступу та цього не сталося, тому що я явно не викликав функцію геттера для цього. До речі, що являє собою функцію "геттер"? Чи відкладе JPA завантаження властивості до виклику функції getMember, яка точно відповідає шаблону імен учасника?
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;
        }

        }

Тема.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();
        }
    }

    }

Перевірте метод retrieve () Main.java. Коли ми отримаємо Subject, то його колекції списку колекцій , анотовані з ними @OneToMany, будуть ліниво завантажуватися. Але, з іншого боку, Книги, пов’язані з асоціацією тематики колекції , з анотацією @ManyToOne, завантажуються ретельно (від [default][1]за @ManyToOne, fetchType=EAGER). Ми можемо змінити поведінку, розмістивши fetchType.EAGER на @OneToManySubject.java або fetchType.LAZY на @ManyToOneBooks.java.


1

public enum FetchType розширює java.lang.Enum Визначає стратегії для отримання даних із бази даних. Стратегія EAGER - це вимога, що стосується часу наполегливого виконання постачальника, щоб дані повинні бути охоче отримані. Стратегія LAZY - це натяк на тривалість роботи постачальника наполегливості, що дані слід отримувати ліниво під час першого доступу. Реалізація дозволена з нетерпінням отримувати дані, для яких було вказано підказку стратегії LAZY. Приклад: @Basic (fetch = LAZY) захищає String getName () {return name; }

Джерело


1

Я хочу додати цю замітку до того, що "Кюн Хван Мін" сказав вище.

Припустимо, ви використовуєте Spring Rest з цим простим архітектором:

Контролер <-> Сервіс <-> сховище

І ви хочете повернути деякі дані на передній план, якщо ви використовуєте FetchType.LAZY, ви отримаєте виняток після повернення даних до методу контролера, оскільки сесія закрита в Сервісі, томуJSON Mapper Object не зможуть отримати.

Існує три загальних варіанти вирішення цієї проблеми, залежить від дизайну, продуктивності та розробника:

  1. Найпростіший - використовувати FetchType.EAGER, щоб сеанс залишився живим при контролері.
  2. Анти-візерункиРішення , щоб зробити сеанс активним до завершення виконання, це зробить величезну проблему продуктивності в системі.
  3. Найкращою практикою є використання FetchType.LAZYметоду перетворювача для передачі даних з Entityіншого об'єкта даних DTOта надсилання їх контролеру, тому виняток не буде, якщо сеанс закритий.


0

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

0

ЛЕЗИ: Він отримує дочірні сутності ліниво, тобто під час отримання материнської сутності він просто отримує проксі (створений cglib або будь-якою іншою утилітою) дочірніх сутностей, і коли ви отримуєте доступ до будь-якого майна дочірньої сутності, то він фактично дістається в сплячому режимі.

EAGER: він отримує дочірні сутності разом з батьками.

Для кращого розуміння перейдіть до документації Jboss або ви можете використовувати hibernate.show_sql=trueдля свого додатка та перевірити запити, видані в сплячку.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.