게시글에 댓글 적는 거면 댓글은 Board꺼다
var char에 담으면 안됨?
댓글 작성자명, 댓글이 있다, 날짜 그러면 스칼라 아님 오브젝트임 그래서 테이블 따로 나눠야 한다!
만약 댓글 내용만 있으면 되면 상관 없음
테이블 쪼개는 근거
- 필드 추가하고 싶은데, 오브젝트로 표현해야 될 때(테이블 쪼개기) (댓글 번호, 댓글 내용, 댓글 시간, 댓글 주인, 댓글 게시글 번호)
- 필드 추가하고 싶은데, 컬렉션으로 표현해야 할 때(테이블 쪼개기)(댓글 하나 말고 더 적어야 할 때 → 데이터 타입은 같은데 여러개 있을 때)
연관관계
유저, 게시글, 댓글
유저와 게시글 관계
유저(1)(1) 게시글(N)(1) → 1대 N
유저 댓글
유저 (1)(1) 댓글(N)(1) → 1대 N
게시글 댓글
게시글(1)(1) 댓글(N)(1)
댓글 1개는 게시글 1곳에
게시글 1개는 댓글 여러개
팁 1대 n관계는 행을 본다
행 여러개를 만들 수 있다
댓글 하나에 유저는 하나의 행만 만들 수 있다
질문할 때는
물어보려면 전체 히스토리를 알려줘야 한다(어디 들어가서 어디로 순서를 말해줘야 한다)
테이블
댓글
PK, user_id, board_id, comment, created_at
영화관을 예로 하자
영화
영화관(CGV_부산대연, MEGABOX_부산서면)
상영관(1관,2관,3관)
상영시간(10시, 11시, 12시)
고객
영화(1)(N) 영화관(N)(1) → N,N은 배급이라는 행위 테이블이 나온다!(관계 테이블)
예로 1. 타이타닉 2. 아이언맨이 있고 영화관 1. CGV_부산대연 2. MEGABOX_부산서면
어떤 영화관에 어떤 영화가 있는지 어떻게 표현할 거냐?
모든 영화가 모든 영화관에 있지는 않으니까!!
- 타이타닉 1. CGV
- 아이맨 2. MEGABOX
배급 테이블
- 영화_ID, 영화관_ID
영화관(1)(1) 상영관(N)(1)
- CGV 1. 1관 영화관_ID
중간 중간 필드들은 화면에 다 있다!
시작
좋은것
댓글은 컨트롤러도 안 만든다 (board패키지 안에 reply패키지만 만들고) 댓글을 따로 찾지 않는다 보드 조회하면 댓글은 알아서 가지고 온다
댓글 삭제할 때 /reply/5 하면 댓글 5번 삭제
게시글 100번에 6번 삭제 이게 더 이해 잘 가는 주소다 (안에 있으니까)
하지만 처음인 우리는 다 만든다

1. reply클래스

package org.example.springv3.reply;
import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.example.springv3.board.Board;
import org.example.springv3.user.User;
import org.hibernate.annotations.CreationTimestamp;
import java.sql.Timestamp;
@Table(name = "reply_tb")
@Data
@NoArgsConstructor
@Entity
public class Reply {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Integer id;
@ManyToOne(fetch = FetchType.LAZY)
private User user;
@ManyToOne(fetch = FetchType.LAZY)
private Board board;
private String comment; // 댓글 내용
@CreationTimestamp//em.persist 할 때 발동
private Timestamp createdAt;
}
잘 만들어짐

1개 더 만들어 줘야함
→ 상세보기할 때 mFindById로 join해서 가지고 왔어
이제 보드에 유저, 댓글 join해야 한다!
그러면 보드, 댓글 조인해서 들고올건데
mFindById할 때 join reply를 해야 한다 아니면 한번 때리고 boardId로 조회 2번해도 된다
댓글 테이블을 위한 것 만들어서 where절에 넣어서 찾기
만약 join한다 하면 b.user로 join하고 b.reply할 수 있나? 못한다
reply가 없다
native쿼리로 한다고 해도 보드에 넣을 수 있나?
댓글들이 있다.
즉 같이 담을게 없다
유저한테 댓글 여러개 있는게 아니고 게시글에 댓글 여러개가 있다
2. 보드 이동
package org.example.springv3.board;
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.example.springv3.reply.Reply;
import org.example.springv3.user.User;
import org.hibernate.annotations.CreationTimestamp;
import java.sql.Timestamp;
import java.util.List;
@NoArgsConstructor // 빈 생성자 (하이버네이트가 om 할때 필요)
@Setter
@Getter
@Table(name = "board_tb")
@Entity // DB에서 조회하면 자동 매핑이됨
public class Board {
@GeneratedValue(strategy = GenerationType.IDENTITY) // auto_incremnt 설정, 시퀀스 설정
@Id // PK 설정
private Integer id;
@Column(nullable = false)
private String title;
@Column(nullable = false)
private String content;
@CreationTimestamp
private Timestamp createdAt;
// fk
@ManyToOne(fetch = FetchType.LAZY)
private User user;
@OneToMany(mappedBy = "board") //게시글 조회할 때 댓글여러개 필요해서 적어준 것이다, 포린키의 주인이 누군지 알려줘야 한다! 나는 아니야(안 적으면 컬럼 생성한다)
private List<Reply> replies;
@Builder
public Board(Integer id, String title, String content, Timestamp createdAt, User user) {
this.id = id;
this.title = title;
this.content = content;
this.createdAt = createdAt;
this.user = user;
}
}
비지니스적으로 필요한게 있을 때 한번 더 걸어줘야 한다
private List<Reply> replies;
테이블이 만들어 지면 안되고 조회만 돼야한다!
//게시글 조회할 때 댓글여러개 필요해서 적어준 것이다

정리할 게 있다!
클래스 위에
@Builer
@AllArgsConstructor // 풀 생성자
이러면 생성자 안 적어도 괜찮지 않나요?
빌터패턴은 컬랙션을 포함 못한다 문법상!
만들면 돌다가 터진다
그래서
alt + insert하고 컬랙션 빼고 만들어야 한다!
그래서 생성자를 직접 만들어!!
다시 시작
디테일 머스테치로 이동
<!-- 댓글 -->
<div class="card mt-3">
<!-- 댓글등록 -->
<div class="card-body">
<form action="/reply/save" method="post">
<input type="hidden" name="boardId" value="{{board.id}}">
<textarea class="form-control" rows="2" name="comment"></textarea>
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-outline-primary mt-1">댓글등록</button>
</div>
</form>
</div>
<!-- 댓글목록 -->
<div class="card-footer">
<b>댓글리스트</b>
</div>
<div class="list-group">
{{#board.replies}}
<!-- 댓글아이템 -->
<div class="list-group-item d-flex justify-content-between align-items-center">
<div class="d-flex">
<div class="px-1 me-1 bg-primary text-white rounded">{{user.username}}</div>
<div>{{comment}}</div>
</div>
{{#replyOwner}}
<form action="/board/{{board.id}}/reply/{{id}}/delete" method="post">
<button class="btn">🗑</button>
</form>
{{/replyOwner}}
</div>
{{/board.replies}}
</div>
</div>

이렇게 만들기

<!-- 댓글 -->
<div class="card mt-3">
<!-- 댓글등록 -->
<div class="card-body">
<form action="/reply/save" method="post">
<input type="hidden" name="boardId" value="{{board.id}}">
<textarea class="form-control" rows="2" name="comment"></textarea>
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-outline-primary mt-1">댓글등록</button>
</div>
</form>
</div>
<!-- 댓글목록 -->
<div class="card-footer">
<b>댓글리스트</b>
</div>
<div class="list-group">
<!-- 댓글아이템 -->
<div class="list-group-item d-flex justify-content-between align-items-center">
<div class="d-flex">
<div class="px-1 me-1 bg-primary text-white rounded">{{user.username}}</div>
<div>댓글1</div>
</div>
<form action="#" method="post">
<button class="btn">🗑</button>
</form>
</div>
</div>
</div>
삭제

{{>layout/header}}
<div class="container p-5">
<!-- 수정삭제버튼 -->
{{#model.isOwner}}
<div class="d-flex justify-content-end">
<a href="/api/board/{{model.id}}/update-form" class="btn btn-warning me-1">수정</a>
<form action="/api/board/{{model.id}}/delete" method="post">
<button class="btn btn-danger">삭제</button>
</form>
</div>
{{/model.isOwner}}
<div class="d-flex justify-content-end">
<b>작성자</b> : {{model.username}}
</div>
<!-- 게시글내용 -->
<div>
<h2><b>{{model.title}}</b></h2>
<hr/>
<div class="m-4 p-2">
{{{model.content}}}
</div>
</div>
<!-- 댓글 -->
<div class="card mt-3">
<!-- 댓글등록 -->
<div class="card-body">
<form action="/reply/save" method="post">
<textarea class="form-control" rows="2" name="comment"></textarea>
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-outline-primary mt-1">댓글등록</button>
</div>
</form>
</div>
<!-- 댓글목록 -->
<div class="card-footer">
<b>댓글리스트</b>
</div>
<div class="list-group">
<!-- 댓글아이템 -->
<div class="list-group-item d-flex justify-content-between align-items-center">
<div class="d-flex">
<div class="px-1 me-1 bg-primary text-white rounded">ssar</div>
<div>댓글1</div>
</div>
<form action="#" method="post">
<button class="btn">🗑</button>
</form>
</div>
</div>
</div>
</div>
{{>layout/footer}}
더미 만들기
추가
insert into reply_tb(comment, board_id, user_id, created_at) values('댓글1', 5, 1, now());
insert into reply_tb(comment, board_id, user_id, created_at) values('댓글2', 5, 1, now());
insert into reply_tb(comment, board_id, user_id, created_at) values('댓글3', 5, 2, now());
insert into reply_tb(comment, board_id, user_id, created_at) values('댓글4', 4, 2, now());
insert into reply_tb(comment, board_id, user_id, created_at) values('댓글5', 3, 2, now());
h2
보드 유저 join
select * from board_tb bt
INNER JOIN user_tb ut ON bt.user_id = ut.id
where bt.id = 5;


inline view 쿼리한다
SELECT *
FROM
(
select * from board_tb bt
INNER JOIN user_tb ut ON bt.user_id = ut.id
where bt.id = 5
);

컬럼 골라내는 것만 한 것
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
);

인라인 뷰는 프롬절에 결과 집어넣어서 테이블로 만든다!
이거를 왜 쓰냐?
SELECT bt.id, bt.title, bt.content, ut.id user_id, ut.username FROM board_tb bt
INNER JOIN user_tb ut ON bt.user_id = ut.id
where bt.id = 5

where 절 지우고 실행

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;

select 가 where보다 먼저 실행되서!
오류남
마트 바구니 안에 초코, 과자 100개 쌓여있어
사장님이 50번째 박스에서 우유 하나 꺼내함
첫번째 박스 우유꺼내고 2번째 박스 우유 꺼내고 50번째까지 다 꺼냄
아니면 50번째 박스까지 가서 우유 꺼내기
그래서 where먼저 실행된다
하지만 where먼저 실행되면 u_id를 못 찾는다!
쓸 수 있는 유일한 게 인라인 뷰다!!
왜 그렇냐?
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 )

같은데 개념이 확 달라진다
인라인 뷰는 메모리에 있는것 바로 가지고 온다! 그럼 u_id는 이미 실행됨
프로잭션은 위의 select*가 할꺼다

다시 3번째 코드로 돌아가서
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 );

여기다 댓글 추가할 건데
3개 있다면 3줄 추가되야 한다
이러면 중복됨
하지만 join하면 하드디스크 낭비가 없다!
보드id replyid 같은 것
즉
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;

이게 게시글 상세보기 쿼리다!!
또 댓글 적은 유저도 join해야 한다
복잡하게 생각하지 말고 결과 자체를 테이블로 보고 join하자
user_id 는 댓글 유저 id다
하나씩 조인하면서 결과 보고 또 이너조인해서 찾아보고
인라인뷰 적은 거는 테이블이라 보고 편하라고 한 것
결과
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;

화면에 필요한 것만 고르자
게시글

유저부분

댓글

마지막으로 문제
2로 바꾸면?
결과 안 나옴

그래서 outer join 해야함
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;

내일 JPQL로 배울 거다!
보드 리포지토리
@Query("select b from Board b join fetch b.user left join fetch b.replies r left join fetch r.user where b.id=:id")
Optional<Board> mFindByIdWithReply();

Share article