ORM에서 데이터를 fetch 또는 load 하는 방식은 크게 eager와 lazy로 나뉜다.
예시) UserLazy 클래스
@Entity
@Table(name = "USER")
public class UserLazy implements Serializable {
@Id
@GeneratedValue
@Column(name = "USER_ID")
private Long userId;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Set<OrderDetail> orderDetail = new HashSet();
// standard setters and getters
// also override equals and hashcode
}
예시) OrderDetail 클래스
@Entity
@Table (name = "USER_ORDER")
public class OrderDetail implements Serializable {
@Id
@GeneratedValue
@Column(name="ORDER_ID")
private Long orderId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="USER_ID")
private UserLazy user;
// standard setters and getters
// also override equals and hashcode
}
하나의 User는 다수의 OrderDetail을 가질 수 있다. Eager Load 전략에서는 User 데이터를 로드하면, 이와 관련된 모든 order 들을 로드하여 메모리에 적재할 것이다.
하지만 Lazy Loading을 활성화 하는 UserLazy를 가져온다면 명시적 호출을 할 때까지 OrderDetail 데이터가 초기화 되어 메모리에 로드되지 않을 것이다.
Fetch 전략을 Hibernate에서 설정하는 방법을 알아보자
어노테이션 매개변수를 이용하여 Lazy Loading 또는 Eager Fetching을 활성화 할 수 있다.
fetch = FetchType.LAZY
fetch = FetchType.EAGER
List<UserLazy> users = sessionLazy.createQuery("From UserLazy").list();
UserLazy userLazyLoaded = users.get(3);
return (userLazyLoaded.getOrderDetail());
Lazy 초기화 접근법에서 orderDetail은 우리가 getter 또는 다른 메서드로 명시적으로 호출할 때까지 초기화되지 않는다.
UserLazy userLazyLoaded = users.get(3);
하지만 Eager 전략을 사용하는 UserEager에서는 첫번째 줄에서 즉시 초기화 될 것이다.
List<UserEager> user = sessionEager.createQuery("From UserEager").list();
예시) Hibernate 기능 테스트
Hibernate.isInitialized(orderDetailSet);
예시) 생성된 SQL 쿼리를 보여주는 fetching.hbm.xml 설정
<property name="show_sql">true</property>
예시) 콘솔에 출력된 Lazy Loading 쿼리
select user0_.USER_ID as USER_ID1_0_, ... from USER user0_
예시) 콘솔에 출력된 Eager Fetching 쿼리
select orderdetail0_.USER_ID as USER_ID4_0_0_, orderdetail0_.ORDER_ID as ORDER_ID1_1_0_, orderdetail0_ ...
from USER_ORDER orderdetail0_ where orderdetail0_.USER_ID=?
Lazy Loading과는 다르게 Eager Fetching에서는 USER_ORDER로 이루어진 JOIN을 한다. 위 쿼리는 모든 User에 대해 생성되므로 다른 접근법 보다 많은 메모리를 사용하게 된다.
from USER_ORDER orderdetail0_ where orderdetail0_.USER_ID=?
쿼리에서 맨 끝의 ?는 바인딩 변수(parameter)를 나타냅니다.
바인딩 변수는 쿼리 실행 시에 실제 값으로 대체되는 자리 표시자입니다. 이렇게 함으로써 쿼리가 실행되기 전에 어떤 값을 바인딩할지 결정할 수 있습니다. 바인딩 변수는 쿼리의 조건절이나 파라미터화된 쿼리에 사용되어 특정 조건을 만족하는 데이터를 가져오거나 조작하는 데 사용됩니다.예를 들어, orderdetail0.USER_ID=? 쿼리에서 ?는 orderdetail0의 USER_ID 열에 바인딩될 값이 됩니다. 이 쿼리를 실행할 때, ? 자리에 실제로 사용될 값을 설정하여 쿼리를 완성하게 됩니다. 바인딩 변수를 사용하면 동일한 쿼리를 여러 번 실행할 때마다 다른 값으로 실행할 수 있습니다.
장점
단점
장점
단점
기본적으로 @OneToMany, @ManyToMany 연관해서는 FetchType.LAZY전략을 사용하는 반면 @OneToOne, @ManyToOne은 FetchType.EAGER 전략을 사용함
Hibernate에서는 클래스들의 프록시 구현을 제공함으로서 엔터티와 associations(데이터베이스의 relation과 비슷한 의미)에게 Lazy Loading을 적용한다.
Hibernate는 엔터티를 엔터티의 클래스로부터 나온 프록시로 대체함으로서 엔터티를 향한 호출을 가로챈다. 우리의 예시에서는 제어권이 User 클래스 구현으로 양도되기 전에 데이터베이스에서 누락된 request 정보가 로드 된다.
엔터티를 실제 사용하는 코드가 실행되기 전에, 데이터베이스에 있는 추가적인 연관 데이터를 프록시를 통해 로드한다
또한 예시의 Set orderDetailSet 처럼 association이 컬렉션 클래스로 표현될 때, 래퍼클래스가 생성되어 초기의 컬렉션을 대체한다는 점에 주목.
래퍼 클래스(Wrapper Class)는 기본 데이터 타입(primitive data type)을 객체로 감싸주는 클래스를 말합니다. 기본 데이터 타입(int, float, char, boolean 등)은 원시적인 값을 나타내는 데이터 타입인 반면, 래퍼 클래스(Integer, Float, Character, Boolean)는 이러한 기본 데이터 타입을 객체로 감싸서 객체 지향 프로그래밍에서 활용할 수 있도록 도와줍니다. 제네릭(Generic) 프로그래밍, 컬렉션(Collection) 사용 등에서 유용하게 활용할 수 있습니다.
컬렉션 클래스는 기본적으로 래퍼 클래스가 아닙니다. 컬렉션 클래스는 Java의 표준 라이브러리나 다른 라이브러리에서 제공하는 일반적인 데이터 구조를 나타내는 클래스입니다.
예를 들어, Hibernate에서는 PersistentSet, PersistentList, PersistentMap 등의 래퍼 클래스를 제공하여 컬렉션의 엔터티 관계를 관리합니다. 각 래퍼 클래스는 컬렉션 클래스를 확장(상속)하고, 내부적으로 Lazy Loading, 관련된 데이터 로딩, 변경 추적 등의 기능을 추가하여 엔터티 간의 관계를 처리합니다.