시나리오 코드
Post, Comment 엔티티 만들어 보기
자바의 객체 지향적인 관점에서 관계 살펴 보기
Post 객체는 여러 개의 Comment 객체를 참조할 수 있습니다. 이는 List<Comment>를 통해 구현되며, 이를 통해 Post 객체는 자신과 연결된 여러 개의 댓글을 관리합니다. 반대로, Comment 객체는 각각 하나의 Post 객체를 참조합니다. 즉, 댓글은 항상 하나의 게시글에 속하게 됩니다.
SQL 관점에서 관계 살펴보기
Post와 Comment의 관계는 SQL 데이터베이스에서 1:N (일대다)관계로 정의됩니다. 즉, 하나의 게시글(Post)은 여러 개의 댓글(Comment)과 연결될 수 있습니다.
1. Post 테이블
Post 테이블은 각 게시글에 대한 정보를 담고 있으며, 각 게시글은 고유한 ID로 식별됩니다. SQL에서 이를 나타내기 위해 기본 키(PK)를 설정합니다.
CREATE TABLE Post (
id BIGINT AUTO_INCREMENT PRIMARY KEY, -- 게시글의 고유 ID (PK)
title VARCHAR(255), -- 게시글 제목
content TEXT -- 게시글 내용
);
2. Comment 테이블
Comment 테이블은 각 댓글에 대한 정보를 담고 있으며, 각 댓글은 고유한 ID로 식별됩니다. 댓글은 어느 게시글에 속하는지를 나타내기 위해 외래 키(FK)를 사용합니다. 이 외래 키(FK)는 Post 테이블의 ID를 참조하며, 이를 통해 댓글이 어느 게시글에 속하는지 데이터베이스에서 관리됩니다.
CREATE TABLE Comment (
id BIGINT AUTO_INCREMENT PRIMARY KEY, -- 댓글의 고유 ID (PK)
text TEXT, -- 댓글 내용
post_id BIGINT, -- 어느 게시글에 속하는지 참조 (FK)
FOREIGN KEY (post_id) REFERENCES Post(id) -- Post 테이블의 id를 참조 (FK)
);
참조의 방향성
Post와 Comment 사이에서 참조의 방향성을 단방향 또는 양방향으로 설정할 수 있으며, 두 방식은 각각의 장단점이 있습니다. 실무에서는 보통 위와 같은 경우 양방향 참조를 많이 선택할 수 있지만 단방향성으로 만들더라도 잘못된 부분은 없습니다.
1. 단방향 참조
장점:
- 구현이 단순:
- 한쪽에서만 참조하기 때문에 설계가 간단합니다. 필요하지 않은 방향으로의 참조를 구현할 필요가 없어서 코드가 더 단순해집니다.
- 성능 최적화:
- 불필요한 참조를 하지 않으므로 메모리 사용을 줄일 수 있으며, 필요 없는 객체를 로드하지 않아도 됩니다. 특히 지연 로딩(Lazy Loading) 전략과 결합하면 성능이 최적화될 수 있습니다.
- 메모리 사용 절약:
- 단방향으로만 참조할 경우, 반대 방향으로의 객체를 유지할 필요가 없기 때문에 메모리 사용을 줄일 수 있습니다.
단점:
- 양방향 탐색 불가:
- 한쪽에서만 참조하기 때문에, 참조되지 않는 방향에서는 해당 객체를 참조하거나 조회할 수 없습니다. 예를 들어, Comment에서 Post를 참조할 수 없으므로, Comment에서 속한 Post에 접근할 수 없습니다.
- 관계 복잡성이 증가:
- 만약 반대쪽에서 객체를 참조해야 할 경우, 별도의 쿼리를 작성해야 하며, 이로 인해 코드 복잡성이 증가할 수 있습니다.
2. 양방향 참조 (Bidirectional Relationship)
장점:
- 양방향 탐색 가능:
- 양방향 참조를 통해, Post와 Comment 모두 서로를 참조할 수 있습니다. 예를 들어, Post에서 Comment 리스트를 참조할 수 있고, Comment에서 자신이 속한 Post를 참조할 수 있어 관계 탐색이 매우 용이합니다.
- 더 직관적인 객체 탐색:
- 양쪽에서 데이터를 쉽게 탐색할 수 있기 때문에, 개발자가 두 엔티티 간의 관계를 더 쉽게 관리하고 탐색할 수 있습니다. 이로 인해 코드 가독성이 좋아지고 유지보수가 쉬워집니다.
- 더 적은 쿼리:
- 데이터베이스에서 한쪽을 조회할 때 반대쪽 객체도 쉽게 접근할 수 있어, 필요에 따라 추가 쿼리 작성이 줄어들 수 있습니다.
단점:
- 설계의 복잡성 증가:
- 양방향 참조에서는 어느 엔티티가 연관관계의 주인인지를 명확히 설정해야 하고, 이로 인해 설계가 복잡해질 수 있습니다. 특히 mappedBy와 같은 설정을 올바르게 사용해야 합니다.
- 메모리 사용 증가:
- 양방향으로 참조를 유지하기 때문에, 두 엔티티가 서로를 참조할 경우 메모리 사용량이 늘어날 수 있습니다.
- 순환 참조의 위험:
- 양쪽에서 서로를 참조하면서 순환 참조(Circular Reference)가 발생할 수 있습니다. 이는 직렬화(serialization)나 JSON 변환에서 무한 루프를 일으킬 수 있습니다. 이런 문제를 해결하기 위해 @JsonIgnore와 같은 어노테이션을 사용해야 할 때도 있습니다.
@Entity(name = "tb_comment")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Comment {
@Id // PK 지정
@GeneratedValue(strategy = GenerationType.IDENTITY) // 코드 --> db 위임
private Long id;
private String text;
@ManyToOne
@JoinColumn(name = "post_id")
private Post post;
}
@Entity(name = "tb_post")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
// mappedBy : post - 연관 관계의 주인이 Comment 엔티티에 post(속성) 필드 임을 나타냅니다.
// 객체 필드 기준으로 생각해야 합니다.
// CascadeType.ALL - 제약을 설정하게 되면 Post 엔티티에 대한 모든 상태 변경(저장, 삭제 등)이
// 관련된 Commnet 엔티티에 전파 된다.
@OneToMany(mappedBy = "post" , cascade = CascadeType.ALL)
private List<Comment> comments;
}
시나리오 코드 3
Order 엔티티 만들어 보기
주문 서비스에서는 사용자(User)가 배송지(Address)로 물품을 주문(Order)하는 과정이 필요합니다
프로젝트 요구 조건에 따라서 참조의 방향성을 선택할 수 있지만 여기서는 User와 Address 엔티티와의
단방향 참조 관계를 설정해서 코드를 작성해 봅시다.
User
@Entity(name = "tb_user")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// @OneToOne(fetch = FetchType.LAZY)
// @OneToOne - User, Address 엔티티 간의 1:1 관계를 매핑합니다.
// FetchType.LAZY - 지연 로딩 전략을 사용합니다. (getAddress())
@OneToOne(fetch = FetchType.LAZY)
// @JoinColumn - 외래키(FK) 를 설정하는 어노테이션 이다.
// address_id 라는 이름 외래 키 컬럼이 User 테이블에 추가 됩니다.
@JoinColumn(name = "address_id")
private Address address;
@OneToMany
@JoinColumn(name = "order_id")
private List<Order> orders;
}
Address
@Entity(name = "tb_address")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String city;
private String street;
}
Order
@Entity(name = "tb_order")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
@OneToOne
@JoinColumn(name = "address_id")
private Address address;
}
'Spring boot' 카테고리의 다른 글
[JPA] 블로그 프로젝트 만들기 - 1 (4) | 2024.10.02 |
---|---|
[Spring boot] 단방향, 양방향 매핑에 대한 이해 (1) (1) | 2024.10.01 |
[Spring boot] DELETE 방식에 이해 및 실습 (0) | 2024.10.01 |
[Spring boot] 영속성 컨텍스트란? (0) | 2024.10.01 |
[Spring boot] 엔티티 매니저에 대해 알아보자. (0) | 2024.10.01 |