참조한 강의 : 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
'Spring' 카테고리의 다른 글
Spring Framework - Layered Architecture (0) | 2021.02.16 |
---|---|
Spring Framework - 연관 관계 매핑 (0) | 2021.02.13 |
Spring Framework - Persistence Context (0) | 2021.02.06 |
Spring Framework - CRUD Using Hibernate ORM (0) | 2021.02.04 |
Spring Framework - Hibernate ORM (0) | 2021.02.02 |