블로그 프로젝트 v1 - 게시글 화면에 표시

Feb 21, 2024
블로그 프로젝트 v1 - 게시글 화면에 표시

Board 클래스 생성

notion image
notion image
왜 Board 클래스를 User 클래스(DTO)에 함께 담지 않았을까? 두 개의 클래스가 서로 다른 개체를 나타내기 때문. User 클래스는 사용자에 대한 정보를, Board 클래스는 게시물에 대한 정보를 담고 있다. 이 두 클래스는 각각 독립적인 역할과 데이터를 가지고 있으며, 서로 다른 개체를 나타내기 때문에 붙일 수 없다. 데이터베이스의 컬럼은 스칼라 값으로 구성되어야 한다. 각 컬럼은 단일 값만을 가지고 있어야 하며, 여러 개의 값을 가지면 데이터의 일관성과 정규화를 유지하기 어렵다. 따라서, User 클래스와 Board 클래스는 각각 독립적인 엔티티로서 설계되어야 하는 것!
💡
User 클래스에서 게시물에 대한 정보까지 담고 있으면 데이터의 정규과, 일관성 오류!
💡
컬렉션과 오브젝트는 다 쪼갠다. 무조건! 테이블로 다 쪼개
💡
user랑 board의 관계 = 1 : N 유저 1명은 게시글을 여러 개 쓸 수 있지만, 게시글 하나를 유저 여러 명이 쓸 수는 없다
N쪽인 board에 외래키를 설정해준다.

[ 스칼라여도 쪼갤 수 있다 ]

notion image
만약 "hobbys"라는 복수형으로 표현된다면, 일반적으로는 별도의 "hobby" 테이블을 생성하여 관계를 표현해야 한다. "hobby"라는 테이블을 생성하여 각 취미(Hobby)를 개별적인 행으로 표현하고, 이 테이블과 "user" 테이블 간의 관계를 설정해야 한다. 이를 위해 "user" 테이블에는 "user_id"와 같이 사용자를 식별할 수 있는 기본 키(Primary Key)를 가지고 있어야 한다. 그리고 "hobby" 테이블에는 "hobby_id"와 같은 기본 키(Primary Key)와 "user_id"와 같은 외래 키(Foreign Key)를 가지고 있어서 User 테이블과와 Hobby 테이블 간의 관계를 맺을 수 있다. 이렇게 "hobby" 테이블을 추가하고 "user" 테이블과의 관계를 설정함으로써, 각 사용자(User)와 그에 해당하는 취미(Hobby)들 간의 관계를 명확히 표현할 수 있다. 각 사용자는 여러 개의 취미를 가질 수 있으며, 각 취미는 해당 사용자의 외래 키를 가지게 된다.
💡
모든 데이터에는 날짜가 들어가야 한다. 그래야지 정렬이 되니까.
private Timestamp createdAt; 이런거 넣어주자!
 

[ @Entity 꼭 붙이기! ]

notion image
@Entity를 안붙이면 테이블도 안 만들어지고...
notion image
이거 파싱도 안되고... 무조건 꼭 붙여주자!
 
 

 
notion image
아무튼... 서버 실행하니 테이블 만들어졌죠? 더미 데이터 넣으러 가보자!
 

번호 증가 전략 (AUTO_INCREMENT) +외래키엔 제약 조건을 안 넣는게?

notion image
SEQUENCE는 오라클 데이터베이스에서 사용되는 번호 증가 전략 (AUTO_INCREMENT와 비슷한 개념) TABLE = 테이블 전략이나 크게 중요하진 않다 우린 IDENTITY = AUTO_INCREMENT를 사용할 것!!
 

[ 외래키에 제약 조건을 적으면 delete할 때 문제가… ]

선생님은 외래키엔 제약조건을 안넣는게 좋다고 생각한다. 만약 ssar이라는 1번 유저가 있고, 애가 게시글을 2개 적었다고 가정하자. 이후 ssar이 회원탈퇴를 했는데, 탈퇴할 때 row를 삭제하진 않고
notion image
이런 식으로... 필드값을 만들어서 true = 활동회원 / false = 삭제회원 이라고 만든다 ssar은 탈퇴했으니 현재 false 이후, 개인정보법으로 데이터를 다 날려야할 때, ssar을 지워야하는데... userid가 ssar에 의해 다른 테이블의 외래 키로 사용되고 있다면, 해당 외래 키를 참조하는 데이터를 먼저 처리해야만 userid가 ssar인 데이터를 삭제 가능하다 본 테이블에 데이터 [ 1 / ssar / 남 ] 이렇게 설정되어 있다면 참조 테이블(외래키)는 본 테이블 데이터에 자료가 있어야지만 적을 수 있었던 듯...? 참조 테이블 [ ssar / 야구 / 서울 ] 이런 식으로는 추가가 가능한데 [ cos / 배구 / 부산 ] 이렇게... 본 테이블에 없는 데이터는 추가가 안됐었던 걸로 기억 즉, [ 1 / ssar / 남 ] 데이터를 삭제하려면 참조 테이블의 [ ssar / 야구 / 서울 ] 부터 삭제한 후에야 가능하다. 그 말인 것 같음. 따라서 CASECADE를 설정하거나, 삭제할 때 외래키를 NULL로 바꾸는 설정 이런걸 해줘야 삭제가 된다. = 이런걸 안하면 DELETE에서 문제가 발생할 수 있다 이말인듯 승호쌤이랑 한 거 복습해보자!
 

더미 데이터 넣기 (data.sql)

insert into user_tb(username, password, email, created_at) values('ssar', '1234', 'ssar@nate.com', now()); insert into user_tb(username, password, email, created_at) values('cos', '1234', 'cos@nate.com', now()); insert into board_tb(title, content, user_id, created_at) values ('제목1', '내용1', 1, now()); insert into board_tb(title, content, user_id, created_at) values ('제목2', '내용2', 1, now()); insert into board_tb(title, content, user_id, created_at) values ('제목3', '내용3', 1, now()); insert into board_tb(title, content, user_id, created_at) values ('제목4', '내용4', 2, now());
3건은 ssar이 적었고, 1건은 cos가 적었구나… 라는걸 볼 수 있어야한다. (userId 1 = ssar / userId 2 = cos) 더미 데이터 쿼리문 다 작성했으면 서버 실행!
notion image
http://localhost:8080/h2-console/ 들어가서 h2 조회해보니까 데이터가 들어가 있음!
 

뭘 들고 와야 할지 살펴보자

notion image
게시글이 이렇게 되어있으니, 우린 타이틀만 화면에 나오게 하면 되겠죠? 작성자나 좋아요 수 같은건 안 보이니까! 그러나 화면에 안보여도 id는 반드시!!! 들고와야 한다!!! 그러니 title, id 들고 오되, 화면에는 타이틀만 표시하면 될 것 같음
💡
외우자! 화면에 안보여도 id는 반드시!!!!! 들고 와야 한다 id는 왜 눈에 안보여도 무조건 들고 와야하냐? 프라이머리 키라서! DB에서 가져와야 하니까 !! 화면에 안보여도 무조건 들고 와야 한다!!
notion image
상세 보기 누르면 이렇게 됨
 

[ 조회 된 데이터를 화면에 표시하는 과정 ] ★★★

1. 서버에서 데이터 조회 (select 요청을 받았으니 request인가?) 서버는 데이터베이스로부터 데이터를 조회한다. 이 데이터는 게시글의 제목(title)과 고유 식별자(id)로 구성 됨! (게시글 정보는 보통 데이터베이스(DB)에 저장되어 있기 때문에 서버에서 데이터 조회를 통해 해당 정보를 가져오는 것!) 2. 리퀘스트에 데이터 추가 -> 이걸 알아야!! 서버는 조회된 데이터를 리퀘스트(Request)에 추가한다. 이를 통해 데이터를 화면으로 전달할 수 있는 것!! 3. 머스태치 페이지로 이동 서버는 리퀘스트를 들고 머스태치 페이지로 이동한다. (여기선 index.mustache) 4. 데이터 출력 머스태치 페이지에서는 중괄호({{ }})를 사용하여 데이터를 출력한다. (=가방(리퀘스트)에 있는 데이터를 꺼내서 화면에 담을 것이다) 머스태치 페이지에서 중괄호({{ }})로 둘러싸인 표현식을 사용하면, 해당 표현식 내에 있는 데이터를 출력할 수 있다. 이때, 데이터는 리퀘스트(Request)에 담겨있는 가방에서 꺼내서 사용한다 ex) {{data.title}}과 같이 작성하면 데이터의 제목이 해당 위치에 출력 됨 ex) 리퀘스트에 "data"라는 이름으로 게시글의 정보가 저장되어 있고, 해당 게시글의 제목은 "데이터 출력 예시"라고 하자. 이때, 머스태치 페이지에서 {{data.title}}을 사용하면, "데이터 출력 예시"라는 제목이 해당 위치에 출력된다 5. 데이터 반복 여러 개의 게시글이 있는 경우, 머스태치에서는 반복문을 사용하여 데이터를 순회하면서 반복적으로 출력할 수 있다. 이렇게 하면 조회된 데이터가 리퀘스트를 통해 머스태치 페이지로 전달되고, 머스태치 페이지에서 데이터를 화면에 표시할 수 있다. 중괄호({{ }})를 사용하여 데이터를 출력하고, 필요한 경우 반복문을 사용하여 여러 개의 데이터를 출력할 수도 있다.
💡
이 페이지는 이 과정들을 설명하는 페이지다. 우린 오늘… 이걸 했다
 

BoardRepository (DAO) - 1. 서버에서 데이터를 조회하는 과정

notion image
BoardRepository 클래스 생성
notion image
DAO 잖아. EntityManager를 주입받아서 데이터베이스와 소통할 수 있게 하자 EntityManager와 BoardRepository 역시 EntityManager와 UserRepository처럼 의존 관계! 이제 EntityManager의 메소드나 다른 기능들을 UserRepository에서 사용할 수 있겠지...
notion image

[ DAO니까 쿼리문 작성해주자 ]

notion image
서버에서 board_tb 테이블에서 지정된 페이지의 게시글 데이터를 조회하는 과정이다 이제 이걸 리퀘스트에 담으면 된다!!
💡
반환 타입이 List<Board>이므로 다운 캐스팅 필요 X
💡
getSingleResult(); = 단일 결과 반환. 반환 타입이 Object getResultList(); = 여러 개의 결과 반환. 반환 타입이 List
 

BoardController - 2. 조회된 데이터를 리퀘스트에 추가한다

조회된 데이터를 요청(Request)에 추가하는 과정은 일반적으로 Controller에서 처리된다 Controller는 클라이언트로부터의 요청을 받아들이고, 그 요청에 필요한 비즈니스 로직을 실행하여 데이터를 조회하거나 가공한 후, 클라이언트에게 응답을 생성한다. 즉, 조회된 데이터를 리퀘스트에 추가하는 작업도 Controller에서 수행!!
notion image
BoardController에서 HttpSession을 의존하는 이유는 세션 관리를 위해서! 글쓰기할 때는 세션이 필요하다. 로그인 안 됐는데 글을 쓸 수는 없으니까... Controller는 DAO한테 의존하니까 final로 받았다. 이제 조회해볼까?
notion image
조회문 끝! 서버 실행해보면 ("select * from board_tb order by id desc limit ?,?", Board.class); 이 쿼리문 결과가 Board.class로 매핑되어서 boardList에 들어왔을거다. sout(boardList);로 콘솔창에서 확인해보자

[ boardList는 Board의 모임 ]

notion image
 

[ 데이터 들어왔는지 확인 ]

notion image
http://localhost:8080/ 해서 조회해보면 콘솔창에 데이터가 4건 들어와있는 것 확인 이제 이걸 화면에 뿌리기만 하면 된다!
 

[ 확인했으면 request에 담자 ]

notion image
request.setAttribute("boardList", boardList)는 HttpServletRequest 객체에 "boardList"라는 이름으로 boardList를 저장하는 역할 첫번째 매개변수에는 "boardList" (데이터를 식별할 이름) 두번째 매개변수에는 boardList를 (실제 데이터)를 저장하는 역할! 즉, 이 코드는 조회된 데이터를 request에 추가하는 코드! 위에서 조회된 4건의 데이터를 boardList라는 변수에 담고, boardList(캡쳐에 오타가?)라는 이름으로 저장한 후, request에 조회된 4건의 데이터를 담았다!
💡
데이터베이스와 상호 작용 했죠? 쿼리문 받아와서 조회한 결과로 어케저케 했잖아요. MVC 패턴이다
notion image
💡
이건 VC 패턴. DB와 상호작용해서 꺼내지 않으니까…
자, request에 담았죠? 그럼 이제 boardList에 담겨있는 4가지 조회 결과를 꺼내보자! -> 머스태치 이동
 

[ 리퀘스트에 담는 이유? ]

request는 사용자가 특정 페이지나 기능을 요청할 때 생성되는 객체. request에 데이터를 담으면, 해당 request과 관련된 처리 과정에서만 그 데이터를 사용할 수 있다 예를 들어, 사용자가 게시글을 작성한 후 게시글의 내용을 request에 담아 서버로 전송하면, 서버는 그 request을 받아 게시글을 등록하고 응답을 반환한다. 이때 request에 담긴 게시글의 내용은 그 request 처리 동안에만 사용되며, 다른 request와는 독립적으로 처리된다. 세션에 담으면 응답을 해도 계속 남아서 데이터가 유지되니 request에 담는다!
 

index.mustache - 3~4번 과정

[ 데이터 출력과 데이터 반복 ]

notion image
리퀘스트(가방)에 있는 걸 꺼내오자
💡
머스태치 # 의 2가지 문법 [ if와 for ] 컬렉션일 때는 #이 for문으로 바뀐다. 지금 boardList는 컬렉션 타입이니 for문
💡
User 할 때 (로그인 할 때?)는 세션에서 값을 가져오려고 {{sessionUser}}와 같은 변수명을 사용했다
notion image
제목1과 상세보기를 for문 돌렸다! 데이터가 4개니까 for문도 4번 돌겠지!
notion image
for문 4바퀴 돈 모습~
 

 
notion image
지금 {{#boardList}} 가 자바의 ArrayList 객체잖아 자바 코드를 섞을 수 있다는 말인 것 같음. 지금 [ 제목 1, 제목 1, 제목 1, 제목 1 ] 로만 뜨니까 이걸 바꿔주자 필드에 변수명 적기!
notion image
* {{id}}은 게시물의 고유한 ID 값을 받아와서 URL을 생성하는 부분 (href 걸려있으니...) * {{title}} 로 받으면 동적으로 제목을 바꿔줄 수 있겠지 {{#boardList}} 템플릿 엔진이니까… 실행될때 자바 코드로 바뀐다
notion image
게시글 나왔죠?
💡
* 데이터를 바로 화면에 전달한 게 아니라, 데이터를 request에 담아서, request에 있는 걸 꺼내 화면에 표시한거다 * 클라이언트는 데이터를 리퀘스트에 포함시켜 서버로 보내고, 서버는 리퀘스트에서 해당 데이터를 추출하여 화면에 표시하는 작업을 수행
 
 

여기까지 전체 코드

Board

package shop.mtcoding.blog.board; import lombok.Data; import org.hibernate.annotations.CreationTimestamp; import javax.persistence.*; import java.time.LocalDateTime; @Data //getter, setter, toString @Entity @Table(name = "board_tb") public class Board { @Id //프라이머리 키 @GeneratedValue(strategy = GenerationType.IDENTITY) //auto_incrediment private int id; private String title; private String content; private int userId; @CreationTimestamp private LocalDateTime createdAt; }
 

data.sql

insert into user_tb(username, password, email, created_at) values('ssar', '1234', 'ssar@nate.com', now()); insert into user_tb(username, password, email, created_at) values('cos', '1234', 'cos@nate.com', now()); insert into board_tb(title, content, user_id, created_at) values ('제목1', '내용1', 1, now()); insert into board_tb(title, content, user_id, created_at) values ('제목2', '내용2', 1, now()); insert into board_tb(title, content, user_id, created_at) values ('제목3', '내용3', 1, now()); insert into board_tb(title, content, user_id, created_at) values ('제목4', '내용4', 2, now());

BoardController

package shop.mtcoding.blog.board; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import shop.mtcoding.blog.user.User; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.io.PrintWriter; import java.util.List; @RequiredArgsConstructor @Controller public class BoardController { private final HttpSession session; private final BoardRepository boardRepository; @GetMapping({ "/", "/board" }) public String index(HttpServletRequest request) { List<Board> boardList = boardRepository.findAll(); request.setAttribute("boardList", boardList); return "index"; } @GetMapping("/board/saveForm") public String saveForm() { return "board/saveForm"; } @GetMapping("/board/1") public String detail() { return "board/detail"; } }
 

BoardRepository

package shop.mtcoding.blog.board; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; import javax.persistence.EntityManager; import javax.persistence.Query; import java.util.List; @RequiredArgsConstructor @Repository public class BoardRepository { private final EntityManager em; public List<Board> findAll() { Query query = em.createNativeQuery("select * from board_tb order by id desc", Board.class); List<Board> boardList = query.getResultList(); //List를 리턴할땐 다운캐스팅 x return boardList; //1건일 때만 다운캐스팅 } }
 

index.mustache

{{> layout/header}} <div class="container p-5"> {{#boardList}} <div class="card mb-3"> <div class="card-body"> <h4 class="card-title mb-3">{{title}}</h4> <a href="/board/{{id}}" class="btn btn-primary">상세보기</a> </div> </div> {{/boardList}} <ul class="pagination d-flex justify-content-center"> <li class="page-item disabled"><a class="page-link" href="#">Previous</a></li> <li class="page-item"><a class="page-link" href="#">Next</a></li> </ul> </div> {{> layout/footer}}
 

TIP!

notion image
v1은 기본기, v2는 편한거, v3는 엄청 편한거 v3를 쓴다 = v1, v2가 자동으로 이뤄진다
 

 
💡
외우자! 화면에 안보여도 id는 반드시!!!!! 들고 와야 한다 id는 왜 눈에 안보여도 무조건 들고 와야하냐? 프라이머리 키라서! DB에서 가져와야 하니까 !! 화면에 안보여도 무조건 들고 와야 한다!!
💡
데이터를 잘 만드는 방법 = select를 잘 적는다
Share article

codingb