Разница между FetchType LAZY и EAGER в Java Persistence API?


554

Я новичок в Java Persistence API и Hibernate.

В чем разница между FetchType.LAZYи FetchType.EAGERв Java Persistence API?


1
Загрузка коллекций EAGER означает, что они извлекаются полностью во время выборки их родителей. Пока EAGER загружается, то весь мой ребенок забирается. Дочерний элемент выбирается в PersistentSet и PersistentList (или PersistentBag), внутри Persistent Bag, он отображается как список массивов. Это правильно ?? ..
Гита

Ответы:


1066

Иногда у вас есть две сущности, и между ними есть отношения. Например, у вас может быть названная сущность Universityи другая сущность, Studentа в университете может быть много студентов:

Сущность университета может иметь некоторые базовые свойства, такие как идентификатор, имя, адрес и т. Д., А также свойство коллекции, называемое студенты, которое возвращает список студентов для данного университета:

В университете много студентов

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

   // setters and getters
}

Теперь, когда вы загружаете университет из базы данных, JPA загружает его поля id, name и address. Но у вас есть два варианта загрузки студентов:

  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 / сервер JEE), и сущности / DTO передаются клиенту по проводам, и опять же чаще всего в этих сценариях отложенная загрузка не будет работать из-за способа, которым сущности сериализуются по проводу.
TheFooProgrammer

4
Я хотел бы добавить больше информации к этому ответу из моей книги - Чтобы сэкономить память, отложенная загрузка обычно используется для отношений один ко многим и много ко многим. Для одного к одному обычно используется Eager.
Эрран Морад

2
При ленивой загрузке, когда я вызываю getStudents()метод в первый раз, кэшируются ли результаты? чтобы в следующий раз я мог быстрее получить доступ к этим результатам?
JavaTechnical

2
@JavaTechnical зависит, если вы включите кэш второго уровня (включен по умолчанию)
Ced

285

В принципе,

LAZY = fetch when needed
EAGER = fetch immediately

11
Очень ясно, но только после прочтения ответа @ Behang. Спасибо за четкое резюме. :-)
Набин

66

EAGERзагрузка коллекций означает, что они извлекаются полностью во время извлечения их родителя. Так что, если у вас есть Courseи есть List<Student>, все студенты выбираются из базы данных в то время, когда они Courseвыбираются.

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


Я использовал эту концепцию при извлечении деталей дочерней сущности, но я не вижу никакой разницы между ними. Когда я указываю Eager fetch, он выбирает все, и когда я отлаживаю его, я вижу «Bean deferred» в дочернем объекте. Когда я говорю course.getStudents(), он запускает SQL-запрос (видел это на консоли). В типе Lazy fetch тоже самое происходит. Так в чем же разница?
Неха Чоудхари

активные коллекции извлекаются при загрузке объекта-владельца. Ленивые коллекции извлекаются при доступе к ним. Если это не то поведение, которое вы видели, возможно, что-то не так с вашей средой (например, запуск старых версий класса)
Божо

1
@Bozho Вы указали только ленивую загрузку коллекций. Может ли простое строковое поле загружаться лениво?
vikiiii

Нет. Вам нужно использовать запрос или другую сопоставленную сущность, чтобы получить подмножество столбцов
Божо

@ Божо, эй, не могли бы вы ответить на этот вопрос, если он установлен по fetchtype = LAZYумолчанию, даже если попытаться получить коллекцию с помощью getter, hibernete выдает ошибку, сообщающую, что не может оценить
Все Едно

16

Я могу рассмотреть производительность и использование памяти. Одно большое отличие состоит в том, что стратегия извлечения EAGER позволяет использовать извлеченный объект данных без сеанса. Почему?
Все данные извлекаются, когда стремятся отметить данные в объекте, когда сеанс подключен. Однако, в случае стратегии отложенной загрузки, отложенная загрузка помеченного объекта не извлекает данные, если сеанс отключен (после session.close()оператора). Все это можно сделать с помощью Hibernate-прокси. Стремительная стратегия позволяет данным оставаться доступными после закрытия сессии.


11

Насколько мне известно, оба типа выборки зависит от ваших требований.

FetchType.LAZY по требованию (то есть, когда нам потребовались данные).

FetchType.EAGER немедленно (т.е. до того, как наше требование наступит, мы без необходимости извлекаем запись)


11

По умолчанию для всех объектов коллекции и карты применяется правило выборки, FetchType.LAZYа для других экземпляров оно соответствует FetchType.EAGERполитике.
Вкратце, @OneToManyи @ManyToManyотношения не извлекают связанные объекты (сбор и отображение) неявным образом, но операция поиска осуществляется каскадно через поле в @OneToOneи @ManyToOneединицах.

(любезно предоставлено: - objectdbcom)


9

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

К сожалению, вы можете переопределить план выборки по умолчанию только для LAZY. Извлечение EAGER менее гибко и может привести ко многим проблемам с производительностью .

Мой совет заключается в том, чтобы ограничить желание сделать ваши ассоциации EAGER, потому что выборка - это вопрос времени. Поэтому все ваши запросы должны использовать директиву fetch, чтобы получить только то, что необходимо для текущего бизнес-кейса.


2
«Извлечение EAGER менее гибко и может привести ко многим проблемам с производительностью». ... Более правдоподобным утверждением является "Использование или отсутствие выборки EAGER может привести к проблемам с производительностью". В этом конкретном случае, когда лениво инициализированное поле является дорогостоящим для доступа и используется редко, ленивая выборка будет способствовать повышению производительности. Но в случае, когда переменная часто используется, ленивая инициализация может фактически снизить производительность , требуя большего количества поездок в базу данных, чем энергичная инициализация. Я бы предложил применять FetchType правильно, а не догматически.
Скотт

Вы продвигаете свою книгу здесь! Но да, я чувствую, что это зависит от варианта использования и размера объекта, упоминаемого в кардинальном соотношении.
Джон Доу

6

Из Javadoc :

Стратегия EAGER - это требование времени выполнения провайдера постоянства, что данные должны извлекаться с нетерпением. Стратегия LAZY - это подсказка среде выполнения персистентного поставщика о том, что данные должны извлекаться лениво при первом обращении к ним.

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


1
что вы подразумеваете под "первым использованием"?
Леон

@leon: скажем, у вас есть сущность с нетерпеливым полем и ленивым полем. Когда вы получаете сущность, поле нетерпения будет загружено из БД к тому времени, когда вы получите ссылку на сущность, но ленивое поле, возможно, не было. Он будет получен только при попытке доступа к полю через его метод доступа.
TJ Crowder

@TJ Crowder, что по умолчанию, если не определен тип fetch?
Махмуд Салех

@MahmoudSaleh: Понятия не имею. Это, вероятно, зависит от чего-то. Я не использовал JPA в реальном проекте, поэтому я не разбирался в этом.
TJ Crowder

2
@MahmoudS: Типы выборок по умолчанию: OneToMany: LAZY, ManyToOne: EAGER, ManyToMany: LAZY, OneToOne: EAGER, Столбцы: EAGER
Маркус Пшеидт,

5

Тип LazyFetch по умолчанию выбирается Hibernate, если вы явно не отметили Eagerтип Fetch. Чтобы быть более точным и кратким, различие может быть заявлено как ниже.

FetchType.LAZY = Это не загружает отношения, если вы не вызываете их через метод getter.

FetchType.EAGER = Это загружает все отношения.

Плюсы и минусы этих двух типов извлечения.

Lazy initialization повышает производительность, избегая ненужных вычислений и уменьшая требования к памяти.

Eager initialization занимает больше памяти и скорость обработки медленная.

Сказав это, в зависимости от ситуации можно использовать любую из этих инициализаций.


1
Важно отметить утверждение, что оно «не загружает отношения, если вы не вызываете его через метод getter», а также, на мой взгляд, довольно запаздывающее дизайнерское решение… Я только что столкнулся со случаем, когда предполагал, что он получит его при доступе и это не так, потому что я не вызывал функцию 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;
        }

        }

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

    }

Проверьте метод retrieve () файла Main.java. Когда мы получим Subject, его коллекция listBooks , снабженная комментариями @OneToMany, будет загружаться лениво. Но, с другой стороны, связанное с Книгой объединение предмета коллекции , снабженное комментариями @ManyToOne, загружается раньше ( [default][1]для @ManyToOne, fetchType=EAGER). Мы можем изменить поведение, поместив fetchType.EAGER в @OneToManySubject.java или fetchType.LAZY @ManyToOneв Books.java.


1

public enum FetchType extends java.lang.Enum Определяет стратегии для извлечения данных из базы данных. Стратегия EAGER - это требование времени выполнения провайдера постоянства, что данные должны извлекаться с нетерпением. Стратегия LAZY - это подсказка среде выполнения персистентного поставщика о том, что данные должны извлекаться лениво при первом обращении к ним. Реализация позволяет быстро получать данные, для которых указана подсказка стратегии LAZY. Пример: @Basic (fetch = LAZY) protected String getName () {возвращаемое имя; }

Источник


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

LAZY: он лениво выбирает дочерние сущности, т.е. во время выборки родительской сущности, он просто выбирает прокси (созданный cglib или любой другой утилитой) дочерних сущностей, и когда вы получаете доступ к любому свойству дочерней сущности, он фактически извлекается hibernate.

EAGER: он выбирает дочерние объекты вместе с родительскими.

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.