Я новичок в Java Persistence API и Hibernate.
В чем разница между FetchType.LAZY
и FetchType.EAGER
в Java Persistence API?
Я новичок в Java Persistence API и Hibernate.
В чем разница между FetchType.LAZY
и FetchType.EAGER
в Java Persistence API?
Иногда у вас есть две сущности, и между ними есть отношения. Например, у вас может быть сущность с именем 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()
.Когда в университете много студентов, неэффективно загружать всех своих студентов вместе с ним, особенно когда они не нужны, и в подобных случаях вы можете заявить, что хотите, чтобы студенты загружались, когда они действительно необходимы. Это называется отложенной загрузкой.
Вот пример, где 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.
}
В принципе,
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
)
Я могу рассмотреть производительность и использование памяти. Одно большое различие заключается в том, что стратегия EAGER fetch позволяет использовать извлеченный объект данных без сеанса. Почему?
Все данные извлекаются, когда требуются помеченные данные в объекте, когда сеанс подключен. Однако в случае ленивой стратегии загрузки ленивая загрузка отмеченного объекта не извлекает данные, если сеанс отключен (после оператора session.close()
). Все, что может быть сделано с помощью hibernate proxy. Стремительная стратегия позволяет сохранять данные после закрытия сессии.
По умолчанию для всех объектов коллекции и карты правило выборки FetchType.LAZY
, а для других экземпляров - политика FetchType.EAGER
.
Короче говоря, отношения @OneToMany
и @ManyToMany
не выводят связанные объекты (сбор и карту) неявно, но операция поиска каскадируется через поле в @OneToOne
и @ManyToOne
.
Как мне известно, оба типа выборки зависят от вашего требования.
FetchType.LAZY
предоставляется по требованию (т.е. когда нам нужны данные).
FetchType.EAGER
является непосредственным (т.е. перед тем, как наше требование приходит, мы ненужно извлекаем запись)
Оба FetchType.LAZY
и FetchType.EAGER
используются для определения плана выборки по умолчанию.
К сожалению, вы можете только переопределить план выборки по умолчанию для получения LAZY. Выбор EAGER менее гибок и может привести к многочисленным проблемам производительности.
Мой совет заключается в том, чтобы ограничить желание создавать ассоциации EAGER, потому что выборка - это ответственность за время запроса. Таким образом, все ваши запросы должны использовать директиву fetch, чтобы получить только то, что необходимо для текущего бизнес-кейса.
Из Javadoc:
Стратегия EAGER является обязательным требованием для среды выполнения поставщика непрерывности, которую данные должны получать с нетерпением. Стратегия LAZY - это намек на время выполнения провайдера настойчивости, при котором данные должны извлекаться лениво при первом доступе.
Например, нетерпеливый более активен, чем ленивый. Lazy происходит только при первом использовании (если провайдер принимает подсказку), тогда как с нетерпеливыми вещами (может) получить предварительную выборку.
Тип Lazy
Fetch по умолчанию выбран Hibernate, если вы явно не отметили Eager
Тип Fetch. Чтобы быть более точным и кратким, разница может быть указана ниже.
FetchType.LAZY
= Это не загружает отношения, если вы не вызываете его с помощью метода getter.
FetchType.EAGER
= Здесь загружаются все отношения.
Плюсы и минусы этих двух типов выборки.
Lazy initialization
повышает производительность, избегая ненужных вычислений и уменьшая требования к памяти.
Eager initialization
потребляет больше памяти, а скорость обработки медленная.
Сказав, что зависит от ситуации, можно использовать одну из этих инициализаций.
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.
public enum FetchType расширяет java.lang.Enum Определяет стратегии для извлечения данных из базы данных. Стратегия EAGER является обязательным требованием для среды выполнения поставщика непрерывности, которую данные должны получать с нетерпением. Стратегия LAZY - это намек на время выполнения провайдера настойчивости, при котором данные должны извлекаться лениво при первом доступе. Реализации разрешено охотно извлекать данные, для которых указана стратегия LAZY. Пример: @Basic (выборка = ЛЕНИВЫЙ) protected String getName() {return name; }
Я хочу добавить эту заметку к тому, что сказал "Кён Хван Мин" выше.
Предположим, вы используете Spring Rest с этим простым архитектором:
Контроллер <-> Сервис <-> Репозиторий
И вы хотите вернуть некоторые данные во FetchType.LAZY
интерфейс, если вы используете FetchType.LAZY
, вы получите исключение после того, как FetchType.LAZY
данные в метод контроллера, так как сеанс закрыт в Сервисе, поэтому JSON Mapper Object
не может получить данные.
Существует три распространенных варианта решения этой проблемы, в зависимости от дизайна, производительности и разработчика:
FetchType.EAGER
, чтобы сеанс все еще оставался живым при методе контроллера.FetchType.LAZY
использовать 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
для своего приложения и проверить запросы, выданные спящим режимом.