본문 바로가기

항해99

23.08.29 항해 99 16기 주특기 Spring 4일차

오늘 공부한 것

* Spring LV1 과제 하기

 

LV1 과제를 하기위해 강의를 듣고서

과제 시작을 하게 되었다

다행히 겨우겨우 완성 할 수 있었다LV2 부터는 다른 분과 함께 팀을 이루어서 하게되는데폐끼치는 일이 없길 바란다. ㅠㅠ 

 

1. Use Case

 

2. APi 명세

기능 Method Url Request Response
전체 게시글 목록 조회 get /api/post - id, title, username,
contents, createAt,
modifiedAt
게시글 작성 post /api/post - id, title, username,
contents, createAt,
modifiedAt
선택한 게시글 조회 get /api/post/{id} title, username,
contents,
password
id, title, username,
contents, createAt,
modifiedAt
선택한 게시글 수정 put /api/post/{id} title, username,
contents,
password
id, title, username,
contents, createAt,
modifiedAt
선택한 게시글 삭제 delete /api/post/{id} password success : true

 

3. 작성 코드

 1) BoardController

package com.sparta.board.controller;

import com.sparta.board.dto.BoardRequestDto;
import com.sparta.board.dto.BoardResponseDto;
import com.sparta.board.service.BoardSurvice;
import org.springframework.web.bind.annotation.*;

import java.util.List;

// Spring 3 Layer Annotation 중 하나 (Controller, Service, Reository)
// 각 계층을 Bean으로 등록할 때 사용
// 해당 어노테이션 안에는 이미 @Component가 추가됨
// @Component 개발자가 직접 작성한 Class를 Bean으로 등록하기위한 어노테이션
@RestController
// 아래에서 구현된 각 API의 URL에 공통적으로 들어가는 부분
// 동일한 URL 부분의 반복을 줄여 줄 수 있다
@RequestMapping("/api")

public class BoardController {
    // BoardSurvice 와 연결 인스턴스화
    private final BoardSurvice boardSurvice;

    // 생성자
    public BoardController(BoardSurvice boardSurvice) {
        this.boardSurvice = boardSurvice;
    }

    @PostMapping("/post")
    // BoardResponseDto 반환타입,  createBoard 메소드명(원하는대로)
    // @RequestBody : Post 안에 저장된 body 값들을 key:value 형태 (JSON 타입)으로 짝지음 body에 들어오는 데이터들을 가지고옴
    // BoardRequestDto : JSON 타입으로 넘어오는 데이터를 받는 객체(데이터를 저장할 공간)
    // requestDto : requestDto 매개변수에 데이터를 담아서, boardService의 createBoard 메서드로 실어보냄
    public BoardResponseDto createBoard(@RequestBody BoardRequestDto requestDto){
        // 생성
        // 매개변수 requestDto 를 메소드 createBoard를 사용하여 boardSurvice로 반환(boardSurvice와 연결)
        return boardSurvice.createBoard(requestDto);
    }

    @GetMapping("/post")
    // List 형태로
    // BoardResponseDto 반환타입 getBoardList 메소드명 () 전부 Client에게 반환하므로 비워둠
    public List<BoardResponseDto> getBoardList(){
        // DB 조회
        // getBoardList 메소드를 사용하여 boardSurvice 와 연결
        return boardSurvice.getBoardList();
    }

    @GetMapping("/post/{id}")
    // List 형태로
    // BoardResponseDto 반환타입 getBoard 메소드명 (@PathVariable 게시글마다 생성되는 id 값을 넣기위해 사용 Long 타입 id)
    public List<BoardResponseDto> getBoard(@PathVariable Long id) {
        // 선택한 DB 내용 조회
        // getBoard 메소드를 사용하여 boardSurvice 와 연결
        return boardSurvice.getBoard(id);
    }


    @PutMapping("/post/{id}")
    // 수정을 위해 BoardResponseDto의 필드값이 필요
    public BoardResponseDto updateBoard(@PathVariable Long id, @RequestBody BoardRequestDto requestDto) {
        // DB 내용 수정
        // requestDto의 id를 가지고옴
        return boardSurvice.updateBoard(id, requestDto);
    }

    @DeleteMapping("/post/{id}")
    public BoardResponseDto deleteBoard(@PathVariable Long id, @RequestBody BoardRequestDto requestDto){
        // DB 내용 삭제
        return boardSurvice.deleteBoard(id, requestDto);
    }
}

 

 2) BoardRequestDto

package com.sparta.board.dto;

import lombok.Getter;

import java.time.LocalDateTime;

@Getter
public class BoardRequestDto {
    //사용자가 요청한 데이터(입력한 값)
    private long id;
    private String title;
    private String username;
    private String contents;
    private String password;
    private LocalDateTime createAt;
    private LocalDateTime modifiedAt;
}

 

 3) BoardResponseDto

package com.sparta.board.dto;

import com.sparta.board.entity.Board;
import lombok.Getter;

import java.time.LocalDateTime;

@Getter
public class BoardResponseDto {
    private long id; // 게시글 구분을 위한 id
    private String title; // 제목
    private String username; // 작성자명
    private String contents; // 작성내용
    private String password; //비밀번호
    private LocalDateTime createAt; // 게시글 생성 날짜
    private LocalDateTime modifiedAt; // 게시글 수정 날짜

    private String msg; // 게시글 삭제시, 삭제 성공 메시지

    // Entity -> ResponseDto 변환
    // Board 라는 Entitiy에 저장값들을 호출해서 getxxx() 메서드를 이용해 BoardResponseDto의 필드에 담음
    public BoardResponseDto(Board board) {
        this.id = board.getId();
        this.username = board.getUsername();
        this.title = board.getTitle();
        this.password = board.getPassword();
        this.contents = board.getContents();
        this.createAt = board.getCreatedAt();
        this.modifiedAt = board.getModifiedAt();
    }

    // 게시글 삭제시, 삭제 성공 메시지
    public BoardResponseDto(String msg){
        this.msg = msg;
    }
}

 

 4) Board(Entity)

package com.sparta.board.entity;

import com.sparta.board.dto.BoardRequestDto;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

// JPA가 관리 할 수 있는 Entity 클래스로 지정
@Entity
@Getter
@Setter

// 매핑할 테이블 이름을 지정
@Table(name = "board")
// 어노테이션을 이용한 생성자 주입
@NoArgsConstructor
public class Board extends Timestamped {
    // 필드, 기본키 지정
    @Id
    // 기본 키값을 자동으로 생성하는 전략을 지정할때 사용
    // 데이터베이스의 자동증가 기능을 통해 id 값이 자동으로 생성되고 할당됨
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    // 필드를 데이터베이스 컬럼과 매핑할때 사용
    // name : 해당 필드를 어떤 컬럼과 매핑할것인지 지정 title 필드가 데이터베이스 테이블의 title 컬럼과 매핑됨
    // nullable 컬럼이 null 값을 허용하는지 여부 설정 false인 경우 해덩 컬럼에 값이 반듯이 있어야함
    @Column(name = "title", nullable = false)
    private String title;

    @Column(name = "username", nullable = false)
    private String username;

    @Column(name = "contents", nullable = false, length = 1000)
    private String contents;

    @Column(name = "password", nullable = false)
    private String password;

    // 게시글 작성
    // Entity 클래스와 Controller 간의 데이터 전달을 위해 사용함
    // 클라이언트에서 들어오는 요청 데이터를 담고 Entity로 변환되거나 Entity에서 추출된 정보를 클라이언트에게 보낼 때 사용
    // Board Entity 클래스의 생성자를 정의함 객체가 생성괼때 초기화 작업 수행
    public Board (BoardRequestDto requestDto){
        // Board Entity 객체의 username 필드를 BoardRequestDto 객체의 getUsername 메서드로 부터 얻은 값으로 초기화
        this.username = requestDto.getUsername();
        this.contents = requestDto.getContents();
        this.password = requestDto.getPassword();
        this.title = requestDto.getTitle();
    }

    // 게시글 수정
    // update 메서드가 Board Entity 객체를 주어진 BoardRequestDto 객체의 값으로 업데이트 하는 역활을 수행
    public void update(BoardRequestDto requestDto){
        this.username = requestDto.getUsername();
        this.contents = requestDto.getContents();
        this.password = requestDto.getPassword();
        this.title = requestDto.getTitle();
    }
}

 

 5) Timestamped(Entity)

package com.sparta.board.entity;

import jakarta.persistence.*;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;

@Getter
// JPA Entity 클래스들이 해당 추상 클래스를 상속할 경우 createdAt, modifiedAt 처럼
// 추상 클래스에 선언한 멤버변수를 컬럼으로 인식할 수 있습니다.
@MappedSuperclass
//해당 클래스에 Auditing 기능을 포함시켜 줍니다.
@EntityListeners(AuditingEntityListener.class)
public abstract class Timestamped {
    // Entity 객체가 생성되어 저장될 때 시간이 자동으로 저장됩니다.
    @CreatedDate
    // 업데이트가 되지않게 막아줌
    // 최초 생성시간만 저장됨
    @Column(updatable = false)
    //날짜 타입(java.util.Date, java.util.Calendar)을 매핑할 때 사용합니다.
    @Temporal(TemporalType.TIMESTAMP)
    private LocalDateTime createdAt;

    @LastModifiedDate //조회한 Entity 객체의 값을 변경할 때 변경된 시간이 자동으로 저장됩니다.
    @Column
    @Temporal(TemporalType.TIMESTAMP)
//    *DATE : ex) 2023-01-01
//    *TIME : ex) 20:21:14
//    *TIMESTAMP : ex) 2023-01-01 20:22:38.771000
    private LocalDateTime modifiedAt;
}

 

6) BoardRepositoty

package com.sparta.board.repository;

import com.sparta.board.entity.Board;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

//JpaRepository<Board,           Long>
//              @Entity 클래스    id 데이터 타입
public interface BoardRepository extends JpaRepository<Board, Long> {

    // findAllBy   OrderBy     ModifiedAt     Desc()
    // 전체셀렉트     정렬          이 필드로      내림차순.
    // Memo 테이블에서 ModifiedAt 즉, 수정 시간을 기준으로 전체 데이터를
    // 내림차순으로 가져오는 SQL을 실행하는 메서드
    List<Board> findAllByOrderByModifiedAtDesc();
}

 

 7) BoardSurvice

package com.sparta.board.service;

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

import java.util.List;

// Spring 3 Layer Annotation 중 하나 (Controller, Service, Reository)
@Service
// 어노테이션을 이용한 생성자 주입
@RequiredArgsConstructor

public class BoardSurvice {
    // BoardRepository 와 연결 인스턴스화
    private final BoardRepository boardRepository;
    public BoardResponseDto createBoard(BoardRequestDto requestDto) {
        //생성
        // RequestDto -> Entity
        // requestDto 열쇠를 담아서 Board 객체로 변환
        Board board = new Board(requestDto);

        // DB 저장
        // 변환된 결과를 담은 board를 담아 boardRepository의
        // save 메서드를 호출해서 DB에 저장하고 그 결과값을 saveBoard 변수에 담는다
        Board saveBoard = boardRepository.save(board);

        // Entity -> ResponseDto
        // saveBoard 변수에 담긴 결과값을 담아서 boardResponseDto 객체로 변환
        BoardResponseDto boardResponseDto = new BoardResponseDto(board);

        // boardResponseDto를 반환
        return boardResponseDto;
    }

    public List<BoardResponseDto> getBoardList() {
        // DB조회
        // findAllBy   OrderBy     ModifiedAt     Desc()
        // 전체셀렉트     정렬          이 필드로      내림차순
        // stream().map(BoardResponseDto::new).toList(); BoardResponseDto 객체를 List 형태로 호출
        return boardRepository.findAllByOrderByModifiedAtDesc().stream().map(BoardResponseDto::new).toList();
    }

    public List<BoardResponseDto> getBoard(Long id) {
        // DB id로 조회
        // boardRepository의 findById 메서드로 매개변수로 넣은 해당 id 값을 찾는다
        return boardRepository.findById(id).stream().map(BoardResponseDto::new).toList();
    }

    public BoardResponseDto updateBoard(Long id, BoardRequestDto requestDto) {
        // DB에 존재 하는지 확인
        Board board = findBoard(id);

        // DB 내용 수정
        // 비밀 번호 일치여부 확인
        // board Entity에 저장된 password와 사용자가 requestDto로 입력한 password를
        // equals()메서드를 이용해서 일치 여부 학인
        if (board.getPassword().equals(requestDto.getPassword())) {
            //일치할 경우 board에 접근하여 requestDto 대로 update 한다
            board.update(requestDto);
        } else {
            return new BoardResponseDto("비밀번호가 일치하지 않습니다");
        }
        return new BoardResponseDto(board);
    }

    public BoardResponseDto deleteBoard(Long id, BoardRequestDto requestDto) {
        // DB에 존재 하는지 확인
        Board board = findBoard(id);

         // DB 내용 삭제
//        boardRepository.delete(board);
//        return id;
        if (board.getPassword().equals(requestDto.getPassword())) {
            //일치할 경우 boardRepository 접근하여 board 를 delete 한다
            boardRepository.delete(board);
        } else {
            return new BoardResponseDto("비밀번호가 일치하지 않습니다");
        }
        return new BoardResponseDto("게시글을 삭제했습니다");
    }

    // id 일치여부 확인
    // 수정과 삭제 공통 부분
    private Board findBoard(Long id){
        return boardRepository.findById(id).orElseThrow(()->
                new IllegalArgumentException("선택한 게시글은 존재하지 않습니다")
        );
    }
}

 

 8) BoardApplication

package com.sparta.board;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

// Timestamped 클래스에서 Auditing 기능을 사용하기 위함
@EnableJpaAuditing
@SpringBootApplication
public class BoardApplication {

    public static void main(String[] args) {
        SpringApplication.run(BoardApplication.class, args);
    }

}

 

 9) application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/board
spring.datasource.username=root
spring.datasource.password= 비밀번호
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.jpa.hibernate.ddl-auto=update

spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true

 

 10) Gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.1.3'
    id 'io.spring.dependency-management' version '1.1.3'
}

group = 'com.sparta'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '17'
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    // JPA 설정
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

    // MySQL
    implementation 'mysql:mysql-connector-java:8.0.28'

    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

 

 작성을 하면서도 연계되는 부분이 헷갈려서 관계도를 해보았다

그래도 복잡복잡하긴한데 하다보면 익숙해 지려나?

 

4. 관계도