@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "company_id")
private User company;
@ManyToOne
@JoinColumn(name = "consumer_id")
private User consumer;
@ManyToOne
@JoinColumn(name = "template_id")
private Template template;
...
}
현재 개발 중인 프로젝트에서 내가 작성한 코드의 일부분이다. 1:N 연관관계를 표현하기 위해서는 @ManyToOne
를 속성에 붙여줘야 한다고 알고 있었다. 그런데 팀원의 코드리뷰를 통해서 지연로딩을 설정해줘야 한다는 것을 알게 되었다.
지연로딩에 대해 알아보기 전에
@ManyToOne
과@OneToMany
에 대해서 알아보자.
@ManyToOne
& @OneToMany
@OneToMany
과 @ManyToOne
은 Entity 간의 연관관계를 표현하기 위한 어노테이션이다.
사용자User
와 게시글Board
을 예로 들어보자.
사용자는 여러 개의 게시글을 남길 수 있다.
이 때, 사용자와 게시글의 관계는 1(User) : N(Board)이다.
두 객체가 연관관계를 가지기 위해서는 외래키를 어느 한쪽에서 가지고 있어야 한다. 이때, 외래키를 가지고 있는 쪽을 연관관계의 주인이라고 한다.
외래키는 N에 해당하는 객체가 가지고 있도록 한다. 외래키를 가지는 주인만이 DML(등록, 수정, 삭제)가 가능하고, 반대는 조회만 가능하다.
이 때, 외래키에 해당하는 필드에 @ManyToOne
을 붙여준다. 추가로, 객체 간의 연관관계가 양방향 연관관계일 경우 List<>
필드를 생성하고 @OneToMany
를 붙여준다.
주의해야 할 점은, 양방햔 연관관계일 경우, @OneToMany
의 mappedBy
속성을 통해서 연관관계의 주인을 설정해주어야 한다. 이렇지 않을 경우 이는 양방향 연관관계가 아닌, 2개의 단방향 연관관계이다.
@Entity
public class User extends BaseTimeEntity{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "company")
private List<Order> orderedCompanyList = new ArrayList<>();
@OneToMany(mappedBy = "consumer")
private List<Order> orderedConsumerList = new ArrayList<>();
@OneToMany(mappedBy = "user")
private List<Template> templateList = new ArrayList<>();
...
}
이처럼 연관관계를 설정한 후, DB에서 해당 객체를 조회할 때 어떤 동작이 일어날까?
연관관계가 맺어진 객체를 조회할 때 어떤 방식으로 조회할 것인지를 결정하는 속성이 바로 fetch
이다.
FetchType
에는 지연로딩LAZY
와 즉시로딩EAGER
이 있다.
fetch
는 조회 시에만 영향을 미친다. (등록, 수정, 삭제를 수행할 때에는 영향을 없다.)
@ManyToOne
의 fetch
의 기본값은 FetchType.EAGER
이다. 즉, 별도의 설정을 하지 않으면 즉시로딩 방식으로 동작이 수행된다.
즉시로딩 방식은 어떤 식으로 수행이 될까?
위의 코드를 예로 들면 Order는 현재 User(Company, Consumer), Template과 연관관계를 맺고 있다. 이때, Order를 조회할 경우 Company, Consumer, Template과 join을 걸어 쿼리를 날리게 된다. Order를 조회할 때마다 3번의 join이 수행되는 것이다.
이처럼 객체간 연관관계가 복잡해질 수록 쿼리문은 복잡해지게 되고, 이는 성능 저하로 이어질 수 있다.
지연로딩 방식을 사용하면 어떨까?
지연 로딩을 사용할 경우, Order를 조회하면 Company, Consumer, Template는 프록시 객체로 대체되어 불러와진다. 실제로 쿼리도 join 없이 날아간다. 그리고 이후 실제로 객체를 데이터를 사용하는 시점에 쿼리를 날려 실제 값을 조회한다. 그러므로 Order 내에서 연관이 맺어진 객체의 값을 필요로 하지 않을 경우 불필요한 join이 발생하지 않을 수 있으므로 효율적으로 동작을 수행할 수 있도록 한다.
그러므로 @ManyToOne
을 사용할 때는 @ManyToOne(fetch = FetchType.LAZY)
와 같이 지연로딩을 설정해주자. @OneToMany
는 기본값이 지연로딩이므로 별도로 설정해줄 필요는 없다!