블로그 백엔드 서버 만들기!

yuns·2022년 10월 3일
1

Spring

목록 보기
10/13

나만의 블로그 백엔드 서버 만들기

로그인 기능이 없는 기초 CRUD 기능을 포함한 서버 만들기

API 명세서


프로젝트 준비

  1. Spring Initializer로 프로젝트 생성

  2. settings - gradle / project structure 에서 java11버전으로 설정

  3. com.sparta.board 안에 controller, service, dto, domain 패키지 만들기


BoardEntity 만들기

  1. Board.java에 필요한 필드를 생성

의문 : 날짜를 어떻게 가져올것인가?

  • Data 타입으로 받는다
    Date today = new Date();
    private Date boardTime = today;
  • Timestamped 클래스를 이용한다
  1. annotation 붙이기
package com.sparta.board.domain;

import com.sparta.board.dto.BoardRequestDto;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.text.SimpleDateFormat;
import java.util.Date;

@Getter
@NoArgsConstructor
@Entity
public class Board {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    // 제목
    @Column(nullable = false)
    private String title;

    // 작성자명
    @Column(nullable = false)
    private String username;

    // 작성 날짜
    Date today = new Date();
    SimpleDateFormat myDate = new SimpleDateFormat("yyyy/MM/dd");
    private String boardTime = myDate.format(today);

    // 작성 내용
    @Column(nullable = false)
    private String content;

    // 비밀번호
    @Column(nullable = false)
    private String password;
}


BoardRequestDto 만들기

  1. annotation 붙이기
  2. Board의 필드 내용 가져오기
package com.sparta.board.dto;

import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.Column;
import java.text.SimpleDateFormat;
import java.util.Date;

@NoArgsConstructor
@Getter
public class BoardRequestDto {
    // 제목
    private String title;

    // 작성자명
    private String username;

    // 작성 날짜
    Date today = new Date();
    SimpleDateFormat myDate = new SimpleDateFormat("yyyy/MM/dd");
    private String boardTime = myDate.format(today);

    // 작성 내용
    private String content;

    // 비밀번호
    private String password;
}

다시 Board.java로 가서, requestDto를 이용한 생성자 추가하기

    public Board(BoardRequestDto requestDto) {
        this.title = requestDto.getTitle();
        this.username = requestDto.getUsername();
        this.boardTime = requestDto.getBoardTime();
        this.content = requestDto.getContent();
        this.password = requestDto.getPassword();
    }

BoardRepository 만들기

repository는 DB와 직접 닿는 부분, 가장 안쪽

  1. class 를 interface키워드로 바꾸기
  2. 상속으로 JpaRepository 불러오기
  3. 제네릭<>의 첫번째는 가져올 객체, 두번째는 id의 타입 Long
package com.sparta.board.domain;

import org.springframework.data.jpa.repository.JpaRepository;

public interface BoardRepository extends JpaRepository<Board, Long> {
}

BoardService.java 만들기

  1. annotation 붙이기
  2. repository를 필드로 만들고, update 메소드 추가
package com.sparta.board.service;

import com.sparta.board.domain.Board;
import com.sparta.board.domain.BoardRepository;
import com.sparta.board.dto.BoardRequestDto;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;

@RequiredArgsConstructor
@Service
public class BoardService {
    private final BoardRepository boardRepository;

    // 업데이트
    @Transactional
    public Long update(Long id, BoardRequestDto requestDto) {
        Board board = boardRepository.findById(id).orElseThrow(
                () -> new IllegalArgumentException("해당 아이디가 존재하지 않습니다.")
        );
        board.update(requestDto);
        return board.getId();
    }
}

  1. Board.java로 가서, update메소드 추가
    public void update(BoardRequestDto requestDto) {
        this.title = requestDto.getTitle();
        this.username = requestDto.getUsername();
        this.boardTime = requestDto.getBoardTime();
        this.content = requestDto.getContent();
        this.password = requestDto.getPassword();
    }

BoardController 만들기

  1. annotation 붙이기
  2. repository와 service를 필드로 불러오기
  3. 클래스 안에 각 기능 메소드 틀 준비하기
package com.sparta.board.controller;

import com.sparta.board.domain.Board;
import com.sparta.board.domain.BoardRepository;
import com.sparta.board.dto.BoardRequestDto;
import com.sparta.board.service.BoardService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Optional;

@RequiredArgsConstructor
@RestController
public class BoardController {
    private final BoardRepository boardRepository;
    private final BoardService boardService;

    // 전체 글 조회
    @GetMapping("/api/boards")
    public List<Board> getBoards() {
        return boardRepository.findAll();
    }
    
    // 글 등록
    @PostMapping("/api/boards")
    public Board createBoard(@RequestBody BoardRequestDto requestDto) {
        Board board = new Board(requestDto);
        return boardRepository.save(board);
    }
    
    // 글 수정
    @PutMapping("/api/boards/{id}")
    public Long updateBoard(@PathVariable Long id, @RequestBody BoardRequestDto requestDto) {
        return boardService.update(id,requestDto);
    }
    
    // 글 삭제
    @DeleteMapping("/api/boards/{id}")
    public Long deleteBoard(@PathVariable Long id) {
        boardRepository.deleteById(id);
        return id;
    }
}

출력 확인하기

{
    "title" : "제목1",
    "username":"유저1",
    "content" : "첫번째 글입니다!!",
    "password" : "1234"
}

Postman으로 localhost:8080/api/boards 주소로 POST, raw - JSON 으로 입력해봤다.

출력을 확인해보니, date에서 뭔가 문제가 일어나서 date를 제외하고 값을 넣어봤다.
(500 server 에러)

기본적인 등록, 조회, 수정, 삭제가 잘 된다.


글 하나 조회하기

  1. 글 하나 조회는 특정 글을 조회해야하는거니까 id를 받아와야한다
    // 글 하나 조회
    @GetMapping("/api/boards/{id}")
    public Board getOneBoard(@PathVariable Long id) {
        return ...;
    }

이렇게 작성을 했는데, return값으로 뭘 받아야하는지.. 어려움을 겪었다

그런데, 존재하지 않는 값을 넣었을 때의 exception을 넣어주니 씻은듯이 오류가 사라졌다

    // 글 하나 조회
    @GetMapping("/api/boards/{id}")
    public Board getOneBoard(@PathVariable Long id) {
        return boardRepository.findById(id).orElseThrow(
                () -> new IllegalArgumentException("존재하지 않습니다.")
        );
    }

/boards/2로 GET했더니 잘 가져와진다
감격적


의문

전체 게시글 목록 조회는
제목, 작성자명, 작성 날짜를 조회해야한다.

지금 내가 만든 API는 이름과 비밀번호까지 모두 노출되는것같은데...??

특정 데이터만을 넘기는 법을 찾아야한다.

게시글 비밀번호 확인 API는 무슨 메소드를 사용할까? -> 비밀번호를 입력 받아야하니까 POST일 것 같다. 그리고 id를 찾아낸것처럼 주소에 {password}를 사용하면 되지 않을까


게시글 특정 컬럼만 조회

    // 전체 글 조회 (제목, 작성자, 날짜만 조회되게)
    @GetMapping("/boards")
    public List<String> getBoards(BoardRequestDto boardRequestDto) {
        List<Board> boardList = boardRepository.findAll();

        List<String> viewList = new ArrayList<>();
        for (int i = 0; i < boardList.size(); i++) {
            viewList.add(boardList.get(i).getTitle());
            viewList.add(boardList.get(i).getUsername());
        }
        return viewList;
    }

for문으로 boardList를 하나씩 돌며 title과 username만 꺼내오게 했다.

그런데, ARC로 값을 넣어보며 확인하니
값을 넣을때마다 배열이 따로 나오는게 아니라,

0 : title : 제목1
1 : username : 유저1
2 : title : 제목2
3 : username : 유저2

이렇게 뭉쳐서 나오는 것을 확인했다.

    // 전체 글 조회 (제목, 작성자, 날짜만 조회되게)
    @GetMapping("/boards")
    public List<List> getBoards(BoardRequestDto boardRequestDto) {
        List<Board> boardList = boardRepository.findAll();

        List<List> viewList = new ArrayList<>();
        for (int i = 0; i < boardList.size(); i++) {
            List<String> tempList = new ArrayList<>();
            tempList.add(boardList.get(i).getTitle());
            tempList.add(boardList.get(i).getUsername());
            viewList.add(tempList);
        }
        return viewList;
    }

그래서 일단 get으로 가져온 값을 배열에 한번 담고, 그 배열들을 담는 배열을 또 만들어서 거기에 모이게 했다.

이렇게 출력이 된다


게시글 비밀번호 확인

  1. 어떤 게시글을 확인할건지 (게시글 id가져오기)
  2. 게시글의 비밀번호 가져오기
  3. 입력받은 비밀번호 가져오기
  4. 두개가 일치하는지 확인하기

많이 헤맸다

boardRepository.findById(id);로 찾아온 게시판 정보에서 비밀번호데이터를 가져오면 좋을텐데.. 참 좋을텐데..
getPassword()메소드가 안먹는 것이다.

'스프링 findById' 를 검색해서 찾아본 결과,
.findById로 찾은 결과물은 Optional<> 이라는 타입으로 반환이된다고한다.

그래서 Optional<>에 담아서 getter메소드를 가져오는 방법을 사용했다.

    // 비밀번호 확인
    @PostMapping("/boards/check/{id}/{inputPassword}")
    public boolean checkPassword(@PathVariable Long id,@PathVariable String inputPassword, @RequestBody BoardRequestDto requestDto) {
        // 조회한 id로 게시판 가져오기
        Optional<Board> board =  boardRepository.findById(id);
        // 게시판의 패스워드
        String boardPassword = board.get().getPassword();

        // 입력한 password와 게시판의 패스워드가 같은지 확인
        if (inputPassword.equals(boardPassword) ) {
            return true;
        } else {
            return false;
        }
    }

패스워드 일치시 true, 불일치시 false를 리턴하는 메소드를 만들었다.

ARC로 작동을 확인해본 결과 . .
일치 시 true

불일치 시 false

잘 작동한다!!
그리고 비밀번호 체크 메소드의 Method를 고민했는데, 아무래도 새로 board를 만드는 것이 아니고 체크만 하는거니까 GET으로 해도 되겠다 싶다.

그리고 위에서 매개변수로 받아놓은 requestDto도 필요 없다.

    // 비밀번호 확인
    @GetMapping("/boards/check/{id}/{inputPassword}")
    public boolean checkPassword(@PathVariable Long id,@PathVariable String inputPassword) {
        // 조회한 id로 게시판 가져오기
        Optional<Board> board =  boardRepository.findById(id);
        // 게시판의 패스워드
        String boardPassword = board.get().getPassword();

        // 입력한 password와 게시판의 패스워드가 같은지 확인
        if (inputPassword.equals(boardPassword) ) {
            return true;
        } else {
            return false;
        }
    }

날짜 입력하기

Timestamped 클래스로 날짜 자동저장하게 하기

package com.sparta.board.domain;

import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;

@MappedSuperclass // Entity가 자동으로 컬럼으로 인식합니다.
@EntityListeners(AuditingEntityListener.class) // 생성/변경 시간을 자동으로 업데이트합니다.
public class Timestamped {

    @CreatedDate
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime modifiedAt;
}

Board.java가 Timestamped를 상속하게 하기

public class Board extends Timestamped

BoardApplication에 annotation 추가하기

@EnableJpaAuditing

날짜 입력하기 2

Timestamped가 안먹는다.. 그래서 자바의 기능을 이용해 date를 직접 새겨주는 쪽으로 했다.

Board.java에 현재 날짜를 가져오는 필드와 생성자를 수정했다.

    LocalDate today = LocalDate.now();
    private String boardTime;
    public Board(BoardRequestDto requestDto) {
        this.title = requestDto.getTitle();
        this.username = requestDto.getUsername();
        this.boardTime = String.valueOf(today);
        this.content = requestDto.getContent();
        this.password = requestDto.getPassword();
    }

    public void update(BoardRequestDto requestDto) {
        this.title = requestDto.getTitle();
        this.username = requestDto.getUsername();
        this.boardTime = String.valueOf(today);
        this.content = requestDto.getContent();
        this.password = requestDto.getPassword();
    }

시간은 생성할 당시에 그냥 넘겨주면 되니까 requestDto에서 가져올 필요가 없다고 생각했다.

날짜가 출력이 잘 된다.
그런데 이렇게 하면 값이 똑같아서 뭐가 먼저인지 모르지 않는가?

시간까지 출력되게 해야할거같다.
LocalDate를 LocalDateTIme으로 바꿨다.

시간이 두 번 출력되는 현상 -> 필드를 두개에서 하나만 수정
그리고 requestDto에 날짜는 없어도 된다고 생각했는데 수정 시의 날짜도 필요하니 넣어줬다.


1차 진행상황

package com.sparta.board.controller;

import com.sparta.board.domain.Board;
import com.sparta.board.domain.BoardRepository;
import com.sparta.board.dto.BoardRequestDto;
import com.sparta.board.service.BoardService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.util.*;

@RequiredArgsConstructor
@RestController
public class BoardController {
    private final BoardRepository boardRepository;
    private final BoardService boardService;

    // 전체 글 조회
    @GetMapping("/boards")
    public List<Board> getBoards() {
        List<Board> boardList = boardRepository.findAll();

//        Collections.sort(boardList, new Comparator<Board>() {
//            @Override
//            public int compare(Board o1, Board o2) {
//                return 0;
//            }
//        });

//        Collections.sort(B);
        return boardRepository.findAll();
    }

//    // 전체 글 조회 : 제목, 작성자, 날짜만 조회
//    @GetMapping("/boards")
//    public List<List> getBoards(BoardRequestDto boardRequestDto) {
//        List<Board> boardList = boardRepository.findAllByOrderByModifiedAtDesc();
//
//        List<List> viewList = new ArrayList<>();
//        for (int i = 0; i < boardList.size(); i++) {
//            List<String> tempList = new ArrayList<>();
//            tempList.add(boardList.get(i).getTitle());
//            tempList.add(boardList.get(i).getUsername());
//            tempList.add(boardList.get(i).getToday().toString());
//            viewList.add(tempList);
//        }
//        return viewList;
//    }

    // 글 하나 조회 : 제목, 작성자명, 작성 날짜, 작성 내용 조회
    @GetMapping("/boards/{id}")
    public List<List> getOneBoard(@PathVariable Long id) {
        // 조회한 id로 게시판 가져오기
        Optional<Board> board =  boardRepository.findById(id);

        List<String> viewList = new ArrayList<>();
        viewList.add(board.get().getTitle());
        viewList.add(board.get().getUsername());
//        viewList.add(board.get().getToday().toString());
        viewList.add(board.get().getContent());


        return Collections.singletonList(viewList);
    }

    // 글 하나 조회
//    @GetMapping("/boards/{id}")
//    public Board getOneBoard(@PathVariable Long id) {
//
//        return boardRepository.findById(id).orElseThrow(
//                () -> new IllegalArgumentException("존재하지 않습니다.")
//        );
//    }

    // 글 등록
    @PostMapping("/boards")
    public Board createBoard(@RequestBody BoardRequestDto requestDto) {
        Board board = new Board(requestDto);
        return boardRepository.save(board);
    }
    
    // 글 수정
    @PutMapping("/boards/{id}")
    public Long updateBoard(@PathVariable Long id, @RequestBody BoardRequestDto requestDto) {
        return boardService.update(id,requestDto);
    }
    
    // 글 삭제
    @DeleteMapping("/boards/{id}")
    public Long deleteBoard(@PathVariable Long id) {
        boardRepository.deleteById(id);
        return id;
    }

    // 비밀번호 확인
    @GetMapping("/boards/check/{id}/{inputPassword}")
    public boolean checkPassword(@PathVariable Long id,@PathVariable String inputPassword) {
//        // 조회한 id로 게시판 가져오기
//        Optional<Board> board =  boardRepository.findById(id);
//        // 게시판의 패스워드
//        String boardPassword = board.get().getPassword();
//
//        // 입력한 password와 게시판의 패스워드가 같은지 확인
//        if (inputPassword.equals(boardPassword) ) {
//            return true;
//        } else {
//            return false;
//        }
        return boardService.checkPassword(id, inputPassword);
    }
}

수정사항

controller, service, repository 역할 나누기
(controller에서 repository를 바로 사용하면 안됨!)

0개의 댓글