Разница между FetchType LAZY и EAGER в Java Persistence API?
Я новичок в Java Persistence API и Hibernate.
В чем разница между FetchType.LAZY
а также FetchType.EAGER
в Java персистентности API?
18 ответов
Иногда у вас есть две сущности, и между ними есть отношения. Например, у вас может быть сущность с именем 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. Но у вас есть два варианта для студентов: загрузить его вместе с остальными полями (т. Е. С нетерпением) или загрузить его по требованию (т. Е. Лениво), когда вы вызываете университетский метод getStudents().
Когда в университете много студентов, неэффективно загружать его всеми студентами, когда они не нужны. Таким образом, в подобных случаях вы можете заявить, что хотите, чтобы студенты загружались, когда они действительно необходимы. Это называется отложенной загрузкой.
В принципе,
LAZY = fetch when needed
EAGER = fetch immediately
EAGER
загрузка коллекций означает, что они извлекаются полностью во время выборки их родителя. Так что если у вас есть Course
и это имеет List<Student>
все студенты выбираются из базы данных во время Course
взято.
LAZY
с другой стороны, означает, что содержание List
выбираются только когда вы пытаетесь получить к ним доступ. Например, позвонив course.getStudents().iterator()
, Вызов любого метода доступа на List
инициирует вызов в базу данных для получения элементов. Это реализуется путем создания прокси вокруг List
(или же Set
). Так что для ваших ленивых коллекций конкретные типы не ArrayList
а также HashSet
, но PersistentSet
а также PersistentList
(или же PersistentBag
)
Основное различие между двумя типами выборки - момент, когда данные загружаются в память.
Я приложил 2 фотографии, чтобы помочь вам понять это.
Нетерпеливый выбор
Я могу рассмотреть производительность и использование памяти. Одно большое отличие состоит в том, что стратегия извлечения EAGER позволяет использовать извлеченный объект данных без сеанса. Зачем?
Все данные извлекаются, когда стремятся отметить данные в объекте, когда сеанс подключен. Однако в случае стратегии отложенной загрузки помеченный объект с отложенной загрузкой не извлекает данные, если сеанс отключен (после session.close()
заявление). Все это можно сделать с помощью Hibernate-прокси. Стремительная стратегия позволяет данным оставаться доступными после закрытия сессии.
Lazy
Тип извлечения по умолчанию выбирается Hibernate, если вы явно не пометите Eager
Тип выборки. Чтобы быть более точным и кратким, различие может быть заявлено как ниже.
FetchType.LAZY
= Это не загружает отношения, если вы не вызываете их через метод getter.
FetchType.EAGER
= Это загружает все отношения.
Плюсы и минусы этих двух типов извлечения.
Lazy initialization
повышает производительность, избегая ненужных вычислений и снижая требования к памяти.
Eager initialization
занимает больше памяти и скорость обработки медленная.
Сказав это, в зависимости от ситуации можно использовать любую из этих инициализаций.
По умолчанию для всех объектов коллекции и карты правило выборки FetchType.LAZY
а для других случаев следует FetchType.EAGER
политика.
Вкратце, @OneToMany
а также @ManyToMany
Связи не извлекают связанные объекты (коллекция и карта) неявным образом, но операция поиска каскадно проходит через поле в @OneToOne
а также @ManyToOne
из них.
И то и другое FetchType.LAZY
а также FetchType.EAGER
используются для определения плана выборки по умолчанию.
К сожалению, вы можете переопределить план выборки по умолчанию только для LAZY. Извлечение EAGER менее гибко и может привести ко многим проблемам с производительностью.
Мой совет - ограничить желание сделать ваши ассоциации EAGER, потому что выборка является обязанностью времени запроса. Поэтому все ваши запросы должны использовать директиву fetch, чтобы получить только то, что необходимо для текущего бизнес-кейса.
Насколько мне известно, оба типа выборки зависит от ваших требований.
FetchType.LAZY
по требованию (то есть, когда нам потребовались данные).
FetchType.EAGER
немедленно (т.е. до того, как наше требование наступит, мы без необходимости извлекаем запись)
Я хочу добавить эту заметку к тому, что сказал "Кён Хван Мин" выше.
Предположим, вы используете Spring Rest с этим простым архитектором:
Контроллер <-> Сервис <-> Репозиторий
И вы хотите вернуть некоторые данные в интерфейс, если вы используете FetchType.LAZY
, вы получите исключение после того, как вернете данные в метод контроллера, так как сеанс закрыт в Сервисе, поэтому JSON Mapper Object
не могу получить данные.
Существует три распространенных варианта решения этой проблемы, в зависимости от дизайна, производительности и разработчика:
- Самый простой - это использовать
FetchType.EAGER
, Так что сеанс будет все еще жив при методе контроллера. - Решения по борьбе с шаблонами, позволяющие запустить сеанс до окончания его выполнения, создают огромную проблему с производительностью в системе.
- Лучшая практика заключается в использовании
FetchType.LAZY
с конвертером метода для передачи данных изEntity
в другой объект данныхDTO
и отправьте его контроллеру, чтобы не было исключений, если сеанс закрыт.
Из Javadoc:
Стратегия EAGER - это требование времени выполнения провайдера постоянства, что данные должны извлекаться с нетерпением. Стратегия LAZY - это подсказка среде выполнения персистентного поставщика о том, что данные должны извлекаться лениво при первом обращении к ним.
Например, жаждущий более активен, чем ленив. Ленивый происходит только при первом использовании (если провайдер берет подсказку), тогда как с нетерпеливыми вещами (может) получить предварительную выборку.
JOIN
это большое дело
Воспринимайте это легко:
предположим, что у нас есть класс с именем
User
и другой класс, называемый
Address
и предположим, что у каждого пользователя есть один или несколько адресов, которые означают здесь отношение (один ко многим), если вы выполните:
FetchType.LAZY
выполнить команду sql, например, без:
SELECT * FROM users
FetchType.EAGER
выполнить команду sql, как внутри
join
:
SELECT * FROM users u join address a on a.user_id = u.user_id
Примечание : приведенные выше запросы только для пояснения изображения для вас, но структура Hibernate на самом деле выполняет аналогичные запросы, указанные выше.
Какие типы выборки лучше?
- Поскольку Eager fetching загружает ВСЕ отношения автоматически, это сильно снижает производительность.
- Ленивая выборка не загружает никаких отношений, если не указано иное, что приводит к повышению производительности.
- Активная выборка упрощает программирование, так как требуется меньше кода
- Ленивая загрузка может привести к ошибкам (исключениям), если вся система не будет должным образом протестирована
- Учитывая все обстоятельства, вы все равно должны предпочесть ленивую загрузку, а не нетерпеливую, поскольку она более производительна.
Если вы используете Spring Boot Framework, перейдите к
application.properties
файл и добавьте команду ниже, чтобы точно знать, что происходит.
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
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 на @OneToMany
Subject.java или fetchType.LAZY в @ManyToOne
в Books.java.
Лучший способ понять разницу между ними, если вы понимаете lazy-okay.FetchType.LAZY указывает hibernate извлекать только связанные объекты из базы данных, когда вы используете отношение.
PS: Во многих проектах, над которыми я работал, я видел, что многие разработчики программного обеспечения не обращают на них внимания, и есть даже те, кто называет себя старшими. Если проект, над которым вы работаете, не является обменом данными на больших объемы данных, это нормально иметь EAGER здесь. Однако, учитывая проблемы, в которых может возникнуть n+1 проблем, вам нужно обратить на них внимание после того, как вы узнаете типы выборки отношений по умолчанию.
Здесь вы можете увидеть значения по умолчанию:Тип выборки по умолчанию для «один к одному», «многие к одному» и «один ко многим» в спящем режиме.
Кроме того, на этом все не заканчивается, даже после понимания типов выборки. Чтобы понять, когда использовать LAZY, а когда EAGER, вам также необходимо понимать концепции однонаправленности и двунаправленности. Кроме того, репозиторий весенней загрузки имеет несколько методов, которые позволяют ему считывать данные для вас, ленивых или нетерпеливых. Например, метод
getOne()
или же
getById()
методы позволяют вам лениво извлекать данные из сущностей. Короче говоря, что вы используете и когда, зависит от того, что хочет от вас другая сторона.
public enum FetchType extends java.lang.Enum Определяет стратегии для извлечения данных из базы данных. Стратегия EAGER - это требование времени выполнения провайдера постоянства, что данные должны извлекаться с нетерпением. Стратегия LAZY - это подсказка среде выполнения персистентного поставщика о том, что данные должны извлекаться лениво при первом обращении к ним. Реализация позволяет охотно получать данные, для которых указана подсказка стратегии LAZY. Пример: @Basic(fetch=LAZY) protected String getName() {возвращаемое имя; }
@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;
}
//...
}
Ленивый: он извлекает дочерние сущности лениво, т.е. во время извлечения родительской сущности он просто извлекает прокси (созданный cglib или любой другой утилитой) дочерних сущностей, и когда вы получаете доступ к любому свойству дочерней сущности, он фактически извлекается hibernate.
EAGER: он выбирает дочерние объекты вместе с родительским.
Для лучшего понимания перейдите к документации Jboss или вы можете использовать hibernate.show_sql=true
для вашего приложения и проверьте запросы, выданные в спящем режиме.
Есть один крошечный комментарий: если вы используете ленивый тип, если вы закроете сеанс, вы не сможете получить данные из базы данных позже (см. Вывод ниже).
Но с типом Eager вы извлекаете данные во время извлечения Instructor, поэтому после session.close() вы сможете использовать/показывать эти данные CourseList.
@OneToMany(//fetch = FetchType.EAGER,
fetch = FetchType.LAZY,
mappedBy = "instructor",
cascade = {CascadeType.DETACH, CascadeType.MERGE,
CascadeType.PERSIST, CascadeType.REFRESH})
private List<Course> courseList;
Я бы предложил попробовать оба из них в режиме отладки. В данном случае я использую ленивый тип, как видите.
try {
//start the transaction
session.beginTransaction();
//Get instructor from database
int instructorId = 7;
Instructor tempInstructor = session.get(Instructor.class,instructorId);
System.out.println("Instructor: "+tempInstructor);
//commit transaction
session.getTransaction().commit();
//close session
session.close();
//since courselist is lazy loaded... this should fail
//so in here we are not able to fetch courselist data
//get courses
System.out.println("Courses "+tempInstructor.getCourseList() );
System.out.println("Done!");
} finally {
session.close();
factory.close();
}
}
Выходное исключение:
Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.exercise.hibernate.entity.Instructor.courseList, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:606)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:218)
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:585)
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:149)
at org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:621)
at java.base/java.lang.StringConcatHelper.stringOf(StringConcatHelper.java:453)
at java.base/java.lang.StringConcatHelper.simpleConcat(StringConcatHelper.java:408)
at com.exercise.hibernate.main.EagerLazyLoading.main(EagerLazyLoading.java:56)