Blog 게시판 만들기 (4) - 글쓰기 기능 만들기

bethe·2022년 9월 7일
0

Springboot

목록 보기
35/46
post-custom-banner

📝 글쓰기 기능 만들기

로그인이 되면 글쓰기 기능 header에 보이도록 설정했다. 그런데 지금까지 짠 코드로는 클라이언트가 로그인을 하지 않고 글쓰기 주소인 /boards/writeForm으로 가면 글쓰기 페이지가 보인다.

403(인가되지 않은 접근)으로 막거나 or login 페이지로 redirection을 해주는 대응을 해주어야 한다.

후자의 방법을 사용해보겠다.


1. /wirteForm의 메서드 만들기 (Controller)

writeForm 페이지 메서드는 session의 키값을 들고 와 그 키값의 null 여부에 따라 보여주는 페이지를 다르게 해야한다.

1) session DI하기

우션 Controller가 HttpSession에 의존하므로 DI를 해준다.

@RequiredArgsConstructor
@Controller
public class BoardsController {
	
	private final HttpSession session;

2) session key값을 받고 조건문 설정하기

package site.metacoding.red.web;

import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import lombok.RequiredArgsConstructor;
import site.metacoding.red.domain.users.Users;

@RequiredArgsConstructor
@Controller
public class BoardsController {
	
	private final HttpSession session;

	@GetMapping({"/","/boards"})
	public String getBoardList() {
		return "boards/main";
	}
	
	@GetMapping("/boards/{id}")
	public String getBoardList(@PathVariable Integer id) {
		return "boards/detail";
	}
	
	@GetMapping("/boards/writeForm")
	public String writeForm() {
		Users principal = (Users) session.getAttribute("principal");
		//setAttribute는 Object로 값을 받는데 UserPS를 담았음
		//그런데 세션의 데이터를 받는 타입도 Object로 하면
		//Object o = session.getAttribute("principal");
		//o.getUsersId(); 실행 불가
		//getUsersId 메서드는 오브젝트 타입이 아니므로 Users로 받아야 함
		//고로 object로 받은 session도 다운캐스팅
		if (principal == null) {
			return "redirect:/loginForm";
		}
		return "boards/writeForm";
	}
}

💻 인증과 권한
우리가 짠 조건문은 세션에 값이 있는지 없는지만을 검사한 '인증'이다.
관리자의 경우처럼 개개인마다 볼 수 있는 페이지가 다를텐데, 개개인의 권한을 체크하는 로직이 필요하다. (인가)
여기서는 인증만 되면 글을 쓸 수 있도록 연습해보겠다.


2. writeForm.jsp

boards에 writeForm의 값을 (title, content) post하겠다는 jsp를 작성.

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>

<%@ include file="../layout/header.jsp"%>

<div class="container">
	<form action="/boards" method="post">
		<div class="mb-3 mt-3">
			<input
				type="text" class="form-control"
				placeholder="Enter title" name="title">
		</div>
		<div class="mb-3">
			<textarea class="form-control" rows="8" name="content"></textarea>
		</div>
		<button type="submit" class="btn btn-primary">글쓰기완료</button>
	</form>
</div>

<%@ include file="../layout/footer.jsp"%>

writeForm(post, insert) 상태에서는 아직 PK가 만들어지지도 않았으므로 boards 주소 뒤에 PK를 붙일 필요가 없다.

📵 주의할 점 : "boards/write"처럼 주소에 동사를 붙이지 않기!


3. writeForm의 post값을 받을 writeBoards메서드 만들기 (Controller)

1) 메서드 생성

	@PostMapping("/boards")
	public String writeBoards(WriteDto writeDto) {
		// 메서드 이름을 지을 때는 동사를 먼저 적고 명사를 적자
	}

2) post된 값을 담을 DTO 만들기

package site.metacoding.red.web.dto.request.boards;

import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
public class WriteDto {
	private String title;
	private String content;
}

3) BoardsDao DI하기

WriteDto를 받아 저장하려면 BoardsDao가 있어야 하므로 BoardsDao를 DI한다.

@RequiredArgsConstructor
@Controller
public class BoardsController {
	
	private final HttpSession session;
	private final BoardsDao boardsDao;

4) Boards에 writdDto 값을 받기

	@PostMapping("/boards")
	public String writeBoards(WriteDto writeDto) {
		boardsDao.insert(null);
	}

그런데 boardsDao.insert는 Boards 타입으로 받고 있다.
이 때, DAO의 타입을 WriteDto로 수정하는 것이 아닌 Boards 그대로 받아야 한다.

🔎 데이터를 writeDto로 받지 않고 Boards로 받아야 하는 이유
💡 Boards에는 userId값이 필요한데(FK라 없으면 insert가 되지 않는다), writeDto는 userId를 들고 있지 않기 때문이다.
DTO에는 통신으로 받을 데이터만 적어야 하는데, userId는 클라이언트에게서 받을 값이 아니므로 writeDto에 private Integer userId를 추가하면 안 된다. (애초에 사용자는 자신의 userId가 무엇인지도 모를 것이다.)
👉 userId를 어떻게든 받아서 Boards에 닮아줘야 한다.

🔓 userId는 session에 담겨져 있다!!
고로 session 인수를 불러와 Boards의 userId에 담아주면 된다.

	@PostMapping("/boards")
	public String writeBoards(WriteDto writeDto) {
		// 메서드 이름을 지을 때는 동사를 먼저 적고 명사를 적자
		// userId는 FK라 없으면 insert가 되지 않는다
		// 그런데 WriteDto는 userId를 들고 있지 않기 때문에(통신에서는 무조건 받을 데이터만 적음)
		// 받기는 WriteDto로 받되 Boards로 옮겨 받아야 함
		
		// userId는 session의 principal에 있음
		Users principal = (Users) session.getAttribute("principal");
        
		Boards boards = new Boards();
		boards.setTitle(writeDto.getTitle());
		boards.setContent(writeDto.getContent());
		boards.setUsersId(principal.getId());
		boardsDao.insert(boards);
		return "redirect:/";
	}

4. mapper

boardsDao.insert가 실행될 mapper 작성.

	<insert id="insert">
		INSERT INTO boards(id, title, content, usersId, createdAt)
		VALUES(boards_seq.nextval, #{title}, #{content}, #{usersId}, sysdate)
	</insert>

그리고 세팅된 Boards에 userId로 파라매터가 등록되어 있으므로 usersId로 수정해야 쿼리문이 오류없이 작동될 것이다.

@Setter
@Getter
public class Boards {
	private Integer id;
	private String title;
	private String content;
	private Integer usersId;
	private Timestamp createdAt;
}

5. writeBoards메서드(/boards)에 로그인 인증 조건문 걸기

🔥 위의 writeBoards메서드 코드는 문제가 있다.
로그인을 하지 않아도 글쓰기가 되는데, 로그인을 하지 않은 상태이므로 session의 UsersPS가 없고 따라서 usersId가 insert가 되지 않아 서버가 계속 터질 것이다.

따라서 principal이 null일 때 로그인 폼으로 가는 조건문을 걸어주어야 한다.

	@PostMapping("/boards")
	public String writeBoards(WriteDto writeDto) {
		// 메서드 이름을 지을 때는 동사를 먼저 적고 명사를 적자
		// userId는 FK라 없으면 insert가 되지 않는다
		// 그런데 WriteDto는 userId를 들고 있지 않기 때문에(통신에서는 무조건 받을 데이터만 적음)
		// 받기는 WriteDto로 받되 Boards로 옮겨 받아야 함
		
		// userId는 session의 principal에 있음
		Users principal = (Users) session.getAttribute("principal");

		if (principal == null) {
			return "redirect:/loginForm";
		} // return 되면 메서드는 종료되니까 여기서 쳐내
			// if else 조건문 쓰면 문장이 복잡해지니까 if까지만으로 쳐내기

		Boards boards = new Boards();
		boards.setTitle(writeDto.getTitle());
		boards.setContent(writeDto.getContent());
		boards.setUsersId(principal.getId());
		boardsDao.insert(boards);
		return "redirect:/";
	}

6. writeBoards메서드(/boards) 코드 리팩토링

		Boards boards = new Boards();
		boards.setTitle(writeDto.getTitle());
		boards.setContent(writeDto.getContent());
		boards.setUsersId(principal.getId());
		boardsDao.insert(boards);

이 코드를 리팩토링 하여 깔끔하게 정리해보자.

💻 dto를 entity(boards)로 변환해서 인수로 담아준다.

1) WriteDto에 DTO를 entity에 담을 메서드 생성

우선 Boards를 new하고 값을 담는 코드를 WriteDto 메서드로 옮겨보자.

[WriteDto 코드]

package site.metacoding.red.web.dto.request.boards;

import lombok.Getter;
import lombok.Setter;
import site.metacoding.red.domain.boards.Boards;

@Setter
@Getter
public class WriteDto {
	private String title;
	private String content;
	
	//DTO가 Entity로 변하는 메서드
	public Boards toEntity() {
		Boards boards = new Boards();
		boards.setTitle(writeDto.getTitle());
		boards.setContent(writeDto.getContent());
		boards.setUsersId(principal.getId());
        return boards;
	}
}

그런데 현재 class가 wirteDto이므로 public Boards toEntity() 코드를 이렇게 수정하면 된다.

package site.metacoding.red.web.dto.request.boards;

import lombok.Getter;
import lombok.Setter;
import site.metacoding.red.domain.boards.Boards;

@Setter
@Getter
public class WriteDto {
	private String title;
	private String content;
	
	//DTO가 Entity로 변하는 메서드
	public Boards toEntity(Integer usersId) {
		Boards boards = new Boards();
		boards.setTitle(this.title);
		boards.setContent(this.content);
		boards.setUsersId(usersId);
		return boards;
	}
}

2) setter보다 생성자 초깃값으로 값 받기

그런데 setter는 값을 변화시킬 때 사용하므로 이 경우 생성자 초깃값으로 받는 것이 좋다.

	public Boards toEntity(Integer usersId) {
		Boards boards = new Boards(null, this.title, this.content, usersId, null);
		return boards;
	}

Boards(Entity)도 수정해야 한다.

  • @AllArgsConstructor는 값이 들어오지 않을 id, createAt도 받는 생성자라 정확한 생성자가 아님
  • final은 return 된 것이기 때문에 값을 바꿀 수 없음
  • 👉 받을 값만 있는 생성자를 만들어 주는 것이 좋다.
package site.metacoding.red.domain.boards;

import java.sql.Timestamp;

import lombok.Getter;

@Getter
public class Boards {
	private Integer id;
	private String title;
	private String content;
	private Integer usersId;
	private Timestamp createdAt;
	
	//import lombok.AllArgsConstructor; <-정확한 생성자X
	//final도 X : return값을 돌려주는 생성자이기 때문에 값을 바꿀 수 없기 때문
	//값을 받을 직접적인 생성자를 만들어주는게 나음
	
	public Boards(String title, String content, Integer usersId) {
		this.title = title;
		this.content = content;
		this.usersId = usersId;
	}
}

Boards의 생성자에 맞춰 public Boards toEntity()도 수정

package site.metacoding.red.web.dto.request.boards;

import lombok.Getter;
import lombok.Setter;
import site.metacoding.red.domain.boards.Boards;

@Setter
@Getter
public class WriteDto {
	private String title;
	private String content;
	
	//DTO가 Entity로 변하는 메서드
	public Boards toEntity(Integer usersId) {
		Boards boards = new Boards(this.title, this.content, usersId);
		return boards;
	}
}

3) Controller 코드 최종 수정

@PostMapping("/boards")
	public String writeBoards(WriteDto writeDto) {
		// 메서드 이름을 지을 때는 동사를 먼저 적고 명사를 적자
		// userId는 FK라 없으면 insert가 되지 않는다
		// 그런데 WriteDto는 userId를 들고 있지 않기 때문에(통신에서는 무조건 받을 데이터만 적음)
		// 받기는 WriteDto로 받되 Boards로 옮겨 받아야 함
		
		// userId는 session의 principal에 있음
		Users principal = (Users) session.getAttribute("principal");

		if (principal == null) {
			return "redirect:/loginForm";
		} // return 되면 메서드는 종료되니까 여기서 쳐내
			// if else 조건문 쓰면 문장이 복잡해지니까 if까지만으로 쳐내기

		//dto를 entity(boards)로 변환해서 인수로 담아준다.
		boardsDao.insert(writeDto.toEntity(principal.getId()));
		return "redirect:/";
	}


Test

글쓰기를 하면 Boards엔 게시글이 보이지 않더라도
DB에 입력된 것을 볼 수 있다.

profile
코딩을 배우고 기록합니다. 읽는 사람이 이해하기 쉽게 쓰려고 합니다.
post-custom-banner

0개의 댓글