[Spring Boot] 04. 간단한 DB 연결 연습 ②

shr·2022년 2월 10일
0

Spring

목록 보기
4/23
post-thumbnail

💡 개념 간단 정리

BCrypt
Bcrypt는 해시 알고리즘을 이용해 패스워드를 저장한다. 랜덤한 솔트를 생성하기 때문에 같은 문자열에 대해 다른 인코드 결과를 반환한다. 매번 길이가 60인 String을 만든다.

우선 수정 및 추가하게 될 파일을 먼저 살펴 보자. (🤗 표시 참고)


1. pom.xml에 BCrypt 의존성 추가하기

<dependency>
  <groupId>org.mindrot</groupId>
  <artifactId>jbcrypt</artifactId>
  <version>0.4</version>
</dependency>


2. src/main/java - com.example.demo.service - BoardService 클래스 생성

package com.example.demo.service;

import java.util.List;
import java.util.stream.Collectors;

import org.mindrot.jbcrypt.BCrypt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.example.demo.dao.BoardDao;
import com.example.demo.entity.Board;

@Service
public class BoardService {
    @Autowired
    private BoardDao dao;

    public Boolean write(Board board) {
        String encodedPassword = BCrypt.hashpw(board.getPassword(), BCrypt.gensalt(10));
        board.setPassword(encodedPassword);
        return dao.save(board)!=0;
    }

    public List<Board> list() {
        List<Board> list = dao.findAll();
        for(Board board:list) 
            board.setPassword(null);
        return list;
    }

    public Board read(Integer bno) {
        dao.increaseReadCnt(bno);
        return dao.findById(bno).setPassword(null);
    }

    public Boolean update(String title, String content, String password, Integer bno) {
        Board board = dao.findById(bno);
        if(board==null)
            return false;
        if(BCrypt.checkpw(password, board.getPassword())==false)
            return false;
        return dao.update(title, content, bno)!=0;
    }

    public Boolean delete(String password, Integer bno) {
        Board board = dao.findById(bno);
        if(board==null)
            return false;
        if(BCrypt.checkpw(password, board.getPassword())==false)
            return false;
        return dao.deleteById(bno)!=0;
    }

}


3. src/test/java - com.example.demo - BoardServiceTest 생성 후 테스트

package com.example.demo;

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;

import com.example.demo.entity.Board;
import com.example.demo.service.BoardService;

@SpringBootTest
public class BoardServiceTest {
    @Autowired
    private BoardService service;

    @Test
    public void writeTest() {
        Board board = new Board("글 제목","글 내용","spring","1234");
        assertEquals(true, service.write(board));
    }

    @Test
    public void readTest() {
        Board board = service.read(1 /* 글 번호 */);
        assertEquals(1, board.getReadCnt());
        System.out.println(board);
    }

    @Transactional
    @Test
    public void updateTest() {
        assertEquals(true, service.update("변경 제목", "변경 내용", "1234", 1 /* 글 번호 */));
        assertEquals(false, service.update("변경 제목", "변경 내용", "1234", 2));
    }

    @Transactional
    @Test
    public void deleteTest() {
        assertEquals(true, service.delete("1234", 1));
        assertEquals(false, service.delete("1234", 2));
    }
}


4. src/main/java - com.example.demo.controller - BoardController 생성

package com.example.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.ModelAndView;

import com.example.demo.entity.Board;
import com.example.demo.service.BoardService;

@Controller
public class BoardController {
    @Autowired
    private BoardService service;

    // 글 목록 get
    @GetMapping({"/", "/board/list"})
    public ModelAndView list() {
        return new ModelAndView("board/list").addObject("list", service.list());
    }

    // 글 읽기 get
    @GetMapping("/board/read")
    public ModelAndView read(Integer bno) {
        return new ModelAndView("board/read").addObject("board", service.read(bno));
    }

    // 글 작성 get, post
    @GetMapping("/board/write")
    public void write() {
    }

    @PostMapping("/board/write")
    public String write(Board board) {
        service.write(board);
        return "redirect:/board/read?bno=" + board.getBno();
    }

    // 글 변경 post
    @PostMapping("/board/update")
    public String update(String title, String content, String password, Integer bno) {
        Boolean result = service.update(title, content, password, bno);
        if(result==false) 
            return "redirect:/board/list?error";
        return "redirect:/board/read?bno=" + bno;
    }

    // 글 삭제 post
    @PostMapping("/board/delete")
    public String delete(String password, Integer bno) {
        Boolean result = service.delete(password, bno);
        if(result==false) 
            return "redirect:/board/list?error";
        return "redirect:/board/list";
    }
}


5. src/main/java - com.example.demo.entity - Board 수정

package com.example.demo.entity;

import java.time.LocalDateTime;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain=true)
public class Board {
    private Integer bno;
    private String title;
    private String content;
    private String nickname;
    private String password;
    private LocalDateTime writeTime = LocalDateTime.now();
    private Integer readCnt = 0;

    // 테스트 정상 동작을 위해 4변수 생성자 추가
    public Board(String title, String content, String nickname, String password) {
        this.title = title;
        this.content = content;
        this.nickname = nickname;
        this.password = password;
        this.writeTime = LocalDateTime.now();
        this.readCnt = 0;
    }
}


6. src/main/resources - templates - board - list.html, read.html, write.html 생성

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" xmlns:th="http://www.thymeleaf.org">
<title>Insert title here</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script>
    $(document).ready(function() {
        const message = location.search.substr(1);
        if(message!="")
            alert("작업이 실패했습니다.");
    })
</script>
</head>
<body>
<div id="page">
    <table class="table table-hover">
        <thead>
            <tr>
                <th>번호</th><th>제목</th><th>글쓴이</th><th>시간</th><th>읽기</th>
            </tr>
        </thead>
        <tbody>
            <tr th:each="board: ${list}">
                <td th:text="${board.bno}"></td>
                <td><a th:href="@{/board/read(bno = ${board.bno})}" th:text="${board.title}"></a></td>
                <td th:text="${board.nickname}"></td>
                <td th:text="${board.writeTime}"></td>
                <td th:text="${board.readCnt}"></td>
            </tr>
        </tbody>
    </table>
    <div>
        <a class="btn btn-info" href="/board/write">글쓰기로</a>
    </div>
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" xmlns:th="http://www.thymeleaf.org">
<title>Insert title here</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script>
    $(document).ready(function() {
        $("#delete").click(function() {
            const $form = $("<form>").attr("action","/board/delete").attr("method","post").appendTo($("body"));
            $("<input>").attr("type","hidden").attr("name","password").val($("#password").val()).appendTo($form);
            $("<input>").attr("type","hidden").attr("name","bno").val($("#bno").val()).appendTo($form);
            $form.submit();
        })
    });
</script>
</head>
<body>
    <h1>글 읽기</h1>
    <form id="read_form" action="/board/update" method="post">
        <div class="form-group">
            <label for="nickname">닉네임 </label>
            <input type="text" class="form-control" id="nickname" th:value="${board.nickname}" name="nickname">
        </div>
        <div class="form-group">
            <label for="password">비밀번호 </label>
            <input type="text" class="form-control" id="password" name="password">
        </div>
        <div class="form-group">
            <label for="title">제목 </label>
            <input type="text" class="form-control" id="title" th:value="${board.title}" name="title">
        </div>
        <div class="form-group">
            <textarea class="form-control" rows="5" id="content" th:text="${board.content}" name="content"></textarea>
        </div>
        <input type="hidden" class="form-control" id="bno" th:value="${board.bno}" name="bno">
        <div>
            <button class="btn btn-default" id="update">변경</button>
            <button type="button" class="btn btn-alert" id="delete">삭제</button>
        </div>
    </form>
    <a type="button" class="btn btn-info" href="/board/list">목록으로</a>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" xmlns:th="http://www.thymeleaf.org">
<title>Insert title here</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
    <h1>글 쓰기</h1>
    <form id="read_form" action="/board/write" method="post">
        <div class="form-group">
            <label for="nickname">닉네임 </label>
            <span id="nickname_msg"></span>
            <input type="text" class="form-control" id="nickname" name="nickname">
        </div>
        <div class="form-group">
            <label for="password">비밀번호 </label>
            <input type="text" class="form-control" id="password" name="password">
        </div>
        <div class="form-group">
            <label for="title">제목 </label>
            <input type="text" class="form-control" id="title" name="title">
        </div>
        <div class="form-group">
            <textarea class="form-control" rows="5" id="content" name="content"></textarea>
        </div>
        <div>
            <button class="btn btn-default" id="write">작성</button>
        </div>
    </form>
</body>
</html>
profile
못하다 보면 잘하게 되는 거야 ・ᴗ・̥̥̥

0개의 댓글

관련 채용 정보