Spring

Spring Framework - Eager, Lazy Loading

728x90

참조한 강의 : Spring & Hibernate For Beginners (www.udemy.com/course/spring-hibernate-tutorial/)

 

 

 

- Fetch

: Fetch 는 사전적인 의미로는, 어디에 가서 뭔가를 가져오다 라는 의미인데, 웹 프로그래밍에서는, JS 의 경우, 서버와의 네트워크 통신을 매개체로, 정보를 주고 받기위한 메소드이다.

여기서 언급할 Fetch 도 비슷한 의미로, 자바에서 DB 서버에게 데이터 요청을 위한 수단을 의미한다.

Hibernate ORM 을 이용해서 DB 서버에게 어떤 정보를 요청할때, 두 가지 타입으로 요청을 할수있는데, 

첫번째는 Eager Loading

두번째는 Lazy Loading 이다.

 

 

- Eager Loading

: 이 타입은, Fetch 시에, 모든 관련된 데이터를 전부 불러오게 하는 타입이다.

예를들어서, DB 에 강사 테이블과 강의 테이블이 1:N 의 관계를 가지고 있을때,

강사에 대한 정보를 조회할때, 해당 강사와 연관된 모든 강의 테이블 값들을 전부 가져오는 방식이다.

 

- Instructor.java

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.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
 
@Entity
@Table(name="instructor")
public class Instructor {
    
    @Id
    @Column(name="id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int id;
    
    @Column(name="first_name")
    private String firstName;
    
    @Column(name="last_name")
    private String lastName;
    
    @Column(name="email")
    private String email;
    
    @OneToOne(cascade=CascadeType.ALL)
    @JoinColumn(name="instructor_detail_id")
    private InstructorDetail instructorDetail;
    
    @OneToMany(fetch=FetchType.EAGER,
               mappedBy="instructor",
               cascade={CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    private List<Course> courses;
    
    public Instructor() {}
 
    public Instructor(String firstName, String lastName, String email) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
    }
 
    public int getId() {
        return id;
    }
 
    public void setId(int id) {
        this.id = id;
    }
 
    public String getFirstName() {
        return firstName;
    }
 
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
 
    public String getLastName() {
        return lastName;
    }
 
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
 
    public String getEmail() {
        return email;
    }
 
    public void setEmail(String email) {
        this.email = email;
    }
 
    public InstructorDetail getInstructorDetail() {
        return instructorDetail;
    }
 
    public void setInstructorDetail(InstructorDetail instructorDetail) {
        this.instructorDetail = instructorDetail;
    }
 
    public List<Course> getCourses() {
        return courses;
    }
 
    public void setCourses(List<Course> courses) {
        this.courses = courses;
    }
    
    public void addCourse(Course course) {
        if (courses == null) courses = new ArrayList<>();
        
        courses.add(course);
        
        course.setInstructor(this);
    }
 
    @Override
    public String toString() {
        return "Instructor [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + ", email=" + email
                + ", instructorDetail=" + instructorDetail + "]";
    }
    
}
 
cs

 

- InsturctorDetail.java

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;
 
@Entity
@Table(name="instructor_detail")
public class InstructorDetail {
 
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="id")
    private int id;
    
    @Column(name="youtube_channel")
    private String youtubeChannel;
    
    @Column(name="hobby")
    private String hobby;
 
    @OneToOne(mappedBy="instructorDetail", cascade= {CascadeType.REFRESH, CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST})
    private Instructor instructor;
    
    public InstructorDetail() {}
    
    public InstructorDetail(String youtubeChannel, String hobby) {
        this.youtubeChannel = youtubeChannel;
        this.hobby = hobby;
    }
 
    public int getId() {
        return id;
    }
 
    public void setId(int id) {
        this.id = id;
    }
 
    public String getYoutubeChannel() {
        return youtubeChannel;
    }
 
    public void setYoutubeChannel(String youtubeChannel) {
        this.youtubeChannel = youtubeChannel;
    }
 
    public String getHobby() {
        return hobby;
    }
 
    public void setHobby(String hobby) {
        this.hobby = hobby;
    }
 
    public Instructor getInstructor() {
        return instructor;
    }
 
    public void setInstructor(Instructor instructor) {
        this.instructor = instructor;
    }
 
    @Override
    public String toString() {
        return "InstructorDetail [id=" + id + ", youtubeChannel=" + youtubeChannel + ", hobby=" + hobby + "]";
    }
 
}
 
cs

 

- Course.java

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
 
@Entity
@Table(name="course")
public class Course {
    
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="id")
    private int id;
    
    @Column(name="title")
    private String title;
    
    @ManyToOne(cascade={CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    @JoinColumn(name="instructor_id")
    private Instructor instructor;
 
    public Course() {}
    
    public Course(String title) {
        this.title = title;
    }
 
    public int getId() {
        return id;
    }
 
    public void setId(int id) {
        this.id = id;
    }
 
    public String getTitle() {
        return title;
    }
 
    public void setTitle(String title) {
        this.title = title;
    }
 
    public Instructor getInstructor() {
        return instructor;
    }
 
    public void setInstructor(Instructor instructor) {
        this.instructor = instructor;
    }
 
    @Override
    public String toString() {
        return "Course [id=" + id + ", title=" + title + ", instructor=" + instructor + "]";
    }
    
}
 
cs

 

- GetInstructorCourseDemo.java

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
 
import com.luv2code.hibernate.demo.entity.Course;
import com.luv2code.hibernate.demo.entity.Instructor;
import com.luv2code.hibernate.demo.entity.InstructorDetail;
 
public class GetInstructorCourseDemo {
 
    public static void main(String[] args) throws HibernateException {
        
        SessionFactory factory = new Configuration()
                                    .configure("hibernate.cfg.xml")
                                    .addAnnotatedClass(Instructor.class)
                                    .addAnnotatedClass(InstructorDetail.class)
                                    .addAnnotatedClass(Course.class)
                                    .buildSessionFactory();
        
        Session session = factory.getCurrentSession();
        
        try {
            session.beginTransaction();
            
            int id = 1;
            
            Instructor instructor = session.get(Instructor.class, id);
            
            System.out.println("\n\nGetting Courses Info : " + instructor.getCourses());
        
            session.getTransaction().commit();
        } catch (Exception e) { 
            e.printStackTrace();
        } finally {
            session.close();
            factory.close();
        }
    }
 
}
cs

 

 

위 3개의 테이블들을 기준으로 Instructor 를 조회할때 어떻게 가져오는지 로그를 살펴보면

 

LEFT OUTER JOIN 을 통해서 course 테이블과 JOIN 연산으로 관련 정보를 한번에 싹 긁어오는 것을 볼 수 있다.

 

Eager Loading 을 할 경우, 세션이 닫혀있더라도, 다시 연관 데이터를 조회해도 오류가 나지 않는다

(이미 전부 긁어 모아서 자바 앱 메모리 안에 넣어뒀기 때문임)

 

 

 

- Lazy Loading

: 이 타입은 Fetch 시에 모든 연관 데이터를 전부 불러오는게 아니라, 연관된 데이터를 요청하는 그 시점에만 불러오는 방식이다. 따라서 위와 같이 여러개와 연관된 데이터라면, lazy 타입을 쓰는것이 더 선호된다.

한꺼번에 관련된 모든 데이터를 불러오면, 메모리 낭비가 생길 수 있기 때문이다.

 

위 코드에서 FetchType 만 바꿔서 다시 로그를 출력해보면

 

 

course 테이블에 대해서, LEFT OUTER JOIN 없이, 하이버네이트가 먼저 instructor 정보를 가져오고 나서, 

instructor 인스턴스와 관련된 course 테이블 정보를 요구할때 다시 조회하는것을 볼 수 있다.

 

이 타입의 경우, 세션이 닫혀있을때, 사용하면 오류가 난다

왜냐면, 연결이 끊어졌기 때문에, 더 이상 정보조회를 할수없기 때문이다.

그러므로, lazy loading 을 사용하는 경우, 세션이 열려있는 동안 조회를해서 자바앱 메모리상에 담아두던지

아니면, 처음에 조회할때, session.get 방식이 아니라

HQL 에 JOIN 연산을 넣어서 조회하면 된다

 

예를들면 이런식으로..

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.query.Query;
 
import com.luv2code.hibernate.demo.entity.Course;
import com.luv2code.hibernate.demo.entity.Instructor;
import com.luv2code.hibernate.demo.entity.InstructorDetail;
 
public class FetchJoinDemo {
 
    public static void main(String[] args) {
        
        SessionFactory factory = new Configuration()
                                    .configure("hibernate.cfg.xml")
                                    .addAnnotatedClass(Instructor.class)
                                    .addAnnotatedClass(InstructorDetail.class)
                                    .addAnnotatedClass(Course.class)
                                    .buildSessionFactory();
        
        Session session = factory.getCurrentSession();
        
        try {
            session.beginTransaction();
            
            int id = 1;
            
            Query<Instructor> query =
                    session.createQuery("select i from Instructor i "
                            + "JOIN FETCH i.courses "
                            + "where i.id=:instructorId"
                            Instructor.class);
            
            query.setParameter("instructorId", id);
            
            Instructor instructor = query.getSingleResult();
            
            System.out.println("\n\n Instructor Info : " + instructor + "\n\n");
            
            session.getTransaction().commit();
            
            System.out.println("\n\nWell Done");
        } catch (Exception e) { 
            e.printStackTrace();
        } finally {
            session.close();
            factory.close();
        }
    }
 
}
 
cs

 

 

 

- Default Fetch Type

: 테이블 연관관계 매핑 타입에 따른 기본 FetchType 들이 존재한다

 

Fetch Type Default Loading Type
@OneToOne FetchType.EAGER
@OneToMany FetchType.LAZY
@ManyToOne FetchType.EAGER
@ManyToMany FetchType.LAZY

 

상황에 따라서 기본 타입이 아닌 다른 타입을 가져다 써도 무방하다.

 

 

 

 

 

 

- Reference)

Eager/Lazy Loading : www.baeldung.com/hibernate-lazy-eager-loading

728x90