Blog-v3(리팩토링),양방향 매핑, 인라인 뷰, 테이블 쪼개기
양방향 매핑(Bidirectional Mapping), 인라인 뷰(Inline View)
Sep 04, 2024
1. 양방향 매핑(Bidirectional Mapping)
양방향 매핑(Bidirectional Mapping)은 객체-관계 매핑(Object-Relational Mapping, ORM) 프레임워크(예: JPA, Hibernate)에서 두 엔티티 간의 관계를 양방향으로 설정하는 것을 의미합니다. 즉, 두 엔티티가 서로를 참조하도록 하는 것입니다. 이렇게 하면 엔티티 A에서 엔티티 B를 참조할 수 있고, 반대로 엔티티 B에서도 엔티티 A를 참조할 수 있습니다.
양방향 매핑의 필요성
양방향 매핑은 데이터베이스에서 외래 키(Foreign Key) 관계를 자바 객체의 관계로 변환할 때 유용합니다. 이를 통해 두 엔티티 간의 관계를 더 명확하게 만들고, 더 직관적으로 데이터를 다룰 수 있습니다. 예를 들어,
User
와 Order
엔티티 간의 관계를 양방향으로 설정하면, 특정 사용자의 모든 주문을 가져올 수 있고, 특정 주문에 연결된 사용자를 쉽게 참조할 수 있습니다.1. 엔티티 클래스 정의
Reply.java
jpackage org.example.springv3.reply;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.example.springv3.board.Board;
import org.example.springv3.user.User;
import org.hibernate.annotations.CreationTimestamp;
import java.sql.Timestamp;
@Setter
@Getter
@Table(name = "reply_tb")
@NoArgsConstructor
@Entity
public class Reply {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String comment; // 댓글 내용
@ManyToOne(fetch = FetchType.LAZY)
private User user; //N포드 생각
@ManyToOne(fetch = FetchType.LAZY)
private Board board;
@CreationTimestamp // em.persist 할때 발동
private Timestamp createdAt;
}
Board.java
//양방향 매핑
@OneToMany(mappedBy = "board")
private List<Reply> replies;
2. 양방향 매핑 설정
@OneToMany
어노테이션:- 이 어노테이션은
Board
엔티티와Reply
엔티티 간의 "1대 다" 관계(One-to-Many)를 정의합니다. - 여기서 "1대 다" 관계는 하나의
Board
(게시글)가 여러 개의Reply
(댓글)을 가질 수 있음을 의미합니다.
mappedBy = "board"
:mappedBy
속성은 양방향 관계에서 연관 관계의 주인이 아닌 쪽에서 사용됩니다.mappedBy = "board"
는Reply
엔티티에서board
필드가 이 관계의 주인임을 나타냅니다. 즉,Reply
엔티티에board
라는 필드가 있으며, 이 필드가Board
와의 관계를 정의하는 주체가 됩니다.mappedBy
가 설정된 엔티티는 주도권이 없는 엔티티이며, 실제로 관계를 관리하지 않습니다. 관계를 관리하는 것은 주인 쪽 엔티티(여기서는Reply
엔티티의board
필드)가 됩니다.
private List<Reply> replies;
:List<Reply>
타입의replies
필드는Board
엔티티와 관련된 모든Reply
엔티티의 목록을 나타냅니다.replies
필드는 해당Board
와 연결된 여러 개의Reply
를 나타내는 컬렉션입니다.- 이 컬렉션은
Board
엔티티를 기준으로 한Reply
엔티티들을 가져오거나 설정하는 데 사용됩니다.
양방향 매핑의 장단점
장점
- 직관적인 데이터 탐색: 엔티티 간의 관계를 양방향으로 설정하면, 양쪽에서 데이터를 탐색하는 것이 더 직관적이고 유연해집니다.
- 상태 일관성 유지: 관계를 올바르게 설정하고 관리하면, 양쪽 엔티티의 상태가 항상 일관성을 유지합니다.
단점
- 복잡성 증가: 양방향 매핑을 설정하면 관계를 설정하고 관리하는 로직이 복잡해질 수 있습니다.
- 무한 루프 문제: 양방향 매핑 시, 직렬화(예: JSON 변환) 과정에서 양쪽 엔티티가 서로를 참조하여 무한 루프가 발생할 수 있습니다. 이를 방지하기 위해
@JsonIgnore
같은 어노테이션을 사용하여 직렬화를 피해야 하는 필드를 지정할 수 있습니다.
결론
양방향 매핑은 엔티티 간의 관계를 더 명확하게 하고, 양쪽에서 데이터를 쉽게 탐색할 수 있게 해줍니다. 그러나 이를 사용할 때는 관계의 복잡성 증가와 상태 관리에 주의해야 합니다. ORM을 사용하는 개발에서는 상황에 맞는 매핑 방식을 선택하는 것이 중요합니다.
2. 인라인 뷰(Inline View)
- 인라인 뷰(Inline View)는 SQL에서 사용되는 개념으로, 쿼리 내에 작성된 **서브쿼리(Subquery)**가 마치 테이블처럼 동작하도록 만드는 것을 말합니다. 즉, 인라인 뷰는 쿼리의
FROM
절에 포함된 서브쿼리를 통해 생성된 임시 테이블을 의미합니다.
인라인 뷰의 사용 목적
- 복잡한 쿼리 간소화:
- 인라인 뷰를 사용하면 복잡한 SQL 쿼리를 작은 단계로 분리하여 간소화할 수 있습니다.
- 여러 개의 서브쿼리나 복잡한 집계 쿼리를 단일 쿼리로 결합할 때 유용합니다.
- 일시적 데이터 집합 생성:
- 특정 조건을 만족하는 일시적인 데이터 집합을 생성하여, 그 데이터를 기반으로 추가적인 필터링, 그룹핑, 또는 집계를 수행할 수 있습니다.
- 데이터 중복 제거:
- 인라인 뷰를 사용하여 데이터 중복을 제거하거나 특정 계산 결과를 임시로 저장한 다음, 그 결과를 사용하여 메인 쿼리에서 추가적인 처리를 수행할 수 있습니다.
인라인 뷰의 예제
1.
SELECT *
FROM
(
SELECT bt.id, bt.title, bt.content, ut.username FROM board_tb bt
INNER JOIN user_tb ut ON bt.user_id = ut.id
where bt.id = 5
);

2.
select *
from
(
SELECT bt.id, bt.title, bt.content, ut.id u_id, ut.username FROM board_tb bt
INNER JOIN user_tb ut ON bt.user_id = ut.id
)
where u_id = 2;

- id와 보드 id 를 조인해서 찾아내기
SELECT * FROM
(
SELECT bt.id, bt.title, bt.content, ut.username FROM board_tb bt
INNER JOIN user_tb ut ON bt.user_id = ut.id where bt.id = 5
) t1
inner join reply_tb t2 on t1.id = t2.board_id;

- 2번을 찾는 결과
SELECT * FROM board_tb bt
INNER JOIN user_tb ut ON bt.user_id = ut.id
LEFT OUTER JOIN reply_tb rt on bt.id = rt.board_id
LEFT OUTER JOIN user_tb rut on rut.id = rt.user_id
where bt.id = 2;

- 결과(인라인 뷰로 할 거면 하나 하나씩 잡고 가야한다.)
SELECT * FROM board_tb bt
INNER JOIN user_tb ut ON bt.user_id = ut.id
INNER JOIN reply_tb rt on bt.id = rt.board_id
INNER JOIN user_tb rut on rut.id = rt.user_id
where bt.id = 5;

인라인 뷰의 장점
- 코드 재사용 및 가독성 향상:
- 인라인 뷰를 사용하면 서브쿼리의 결과를 재사용할 수 있으며, 복잡한 쿼리를 이해하기 쉽게 분할할 수 있습니다.
- 성능 향상:
- 특정 조건에 맞는 데이터를 미리 필터링하거나 그룹화하여 메인 쿼리에서 처리할 데이터 양을 줄일 수 있습니다.
- 복잡한 논리적 작업 수행:
- 하나의 쿼리에서 수행하기 어려운 복잡한 논리적 작업을 여러 단계로 나눠 수행할 수 있습니다.
인라인 뷰의 단점
- 성능 저하 가능성:
- 복잡한 인라인 뷰를 사용할 경우, 특히 큰 데이터셋에 대해 다중 레벨의 서브쿼리를 사용하면 성능이 저하될 수 있습니다.
- 읽기 어려운 코드:
- 지나치게 많은 인라인 뷰를 사용하면 쿼리가 복잡해져 가독성이 떨어질 수 있습니다.
결론
인라인 뷰는 SQL 쿼리를 더욱 강력하고 유연하게 만들어 주는 유용한 도구입니다. 이를 적절하게 사용하면 복잡한 데이터를 간단히 처리할 수 있고, 다양한 조건을 쉽게 적용할 수 있습니다. 그러나 성능과 가독성을 항상 고려하여 사용하는 것이 중요합니다.
오류나는 이유는 SELECT보다 WHERE, FROM 이 먼저 실행되기 때문에
SELECT bt.id, bt.title, bt.content, ut.id u_id, ut.username FROM board_tb bt
INNER JOIN user_tb ut ON bt.user_id = ut.id
where u_id = 2;
3.테이블 쪼개는 근거
1. 테이블 쪼개는 근거
(1) 오브젝트로 표현해야 할 때 (테이블 쪼개기 - 정규화)
- 새로운 필드를 추가하고 싶지만, 이를 하나의 오브젝트(객체)로 표현하는 것이 더 적합할 때 테이블을 쪼갭니다.
- 예: 댓글 시스템에서 댓글 정보를 관리할 때, 하나의 테이블에 모든 댓글 정보를 넣는 대신, 댓글 테이블을 별도로 만듭니다.
- 댓글 테이블:
댓글번호(PK)
,댓글내용
,댓글시간
,댓글주인(user_id)
,댓글 게시글 번호(board_id)
(2) 컬렉션으로 표현해야 할 때 (테이블 쪼개기)
- 필드를 추가하려는 경우, 이 필드가 다수의 값을 가질 수 있는 컬렉션 형태라면, 테이블을 쪼개서 별도의 테이블로 관리합니다.
- 예: 게시글에 여러 개의 댓글이 달릴 수 있기 때문에, 댓글을 게시글 테이블에 직접 넣지 않고 별도의 댓글 테이블로 관리합니다.
2. 연관관계
유저, 게시글, 댓글 간의 관계 설정:
- 유저(1) - 게시글(N): 한 명의 유저가 여러 개의 게시글을 작성할 수 있으므로 1대N 관계입니다.
- 유저(1) - 댓글(N): 한 명의 유저가 여러 개의 댓글을 작성할 수 있으므로 1대N 관계입니다.
- 게시글(1) - 댓글(N): 한 개의 게시글에 여러 개의 댓글이 달릴 수 있으므로 1대N 관계입니다.
댓글 테이블의 필수 컬럼
PK
: 댓글의 고유 식별자
user_id
: 댓글을 작성한 유저의 ID (외래 키)
board_id
: 댓글이 달린 게시글의 ID (외래 키)
comment
: 댓글 내용
created_at
: 댓글 작성 시간
컬렉션과 빌더 패턴
- 컬렉션을 빌더 패턴에서 제외하는 이유:
- 양방향 매핑에서 컬렉션을 관리하려면 관계의 일관성을 유지하기 위해 컬렉션 관리 메서드를 별도로 작성하는 것이 좋습니다.
- 빌더 패턴을 사용해 엔티티를 생성할 때, 컬렉션을 초기화하거나 관리하는 로직은 별도로 처리하여 양방향 관계를 정확하게 설정합니다.
- 이는 코드의 가독성을 높이고, 데이터베이스의 무결성을 유지하는 데 도움이 됩니다.
결론
테이블을 쪼개는 것은 데이터의 정규화와 관계형 데이터베이스의 구조를 최적화하는 중요한 과정입니다. 이를 통해 데이터 중복을 줄이고, 유지보수성과 성능을 향상시킬 수 있습니다. 유저, 게시글, 댓글 간의 1대N 관계를 설정하고, 양방향 매핑을 정확히 관리하기 위해서는 빌더 패턴에서 컬렉션을 별도로 관리하는 것이 좋습니다.
Share article