SPRING REST Controller를 통한 게시판 기능 구현

min seung moon·2021년 7월 4일
1

Spring

목록 보기
50/50

0. 프로젝트 생성

  • spring initalizr
    • name : community
    • project : gradle project
    • language : java
    • java : 8
    • dependencies : H2 Database, Lombok, Spring Web, Spring Configuration Processor, Spring Security, Spring REST Docs, Spring Data JPA
  • port는 희망 시 변경
  • Spring Security 설정
    • Package : config
    • Class : SecurityConfig
    • Spring Security를 dependencies에 추가하였기에 그냥 접속을 하게되면 로그인 창이 뜬다
    • 그렇기에 WebSecurityConfigurerAdapterconfigure()를 재정의 해주어야 한다
  • config / SecurityConfig.java
    • WebSecurityConfigurerAdapter
      • HTTP 시큐리티 객체를 사용할 수 있게 해준다
    • Whitelabel Error Page
      • 우리가 View 설정을 해주지 않아서 그런다 문제 없음
package com.example.community.security;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
// WebSecurityConfigurerAdapter
// HTTP 시큐리티 객체를 사용할 수 있게 해준다
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // 모든 경로에 대해서 매칭 허용
        http.authorizeRequests()
                .anyRequest().permitAll();

    }
}

1. 주소 요청에 대한 이해

01. 클라이언트 요청에 대한 주소를 만들어 보자

  • Package : hello
  • Class : FirstController
  • [조건]
    • 컨트롤러 인식을 위한 COntroller 어노테이션 이용
    • 주소매핑은 RequestMapping을 이용
    • HTTP 메소드는 GET 방식
    • 리턴값은 아무것도 없음
    • 주소는 "/first-url"
  • hello / FirstController.java
    • @RequestMapping()
      • 요청에 대해 어떤 Controller, 어떤 메소드가 처리할지를 맵핑하기 위한 어노테이션
      • value, 경로
      • method, HTTP 통신 방법
package com.example.community.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller // Web Template(View) Return
public class FirstController {

    @RequestMapping(value = "/first-url", method = RequestMethod.GET)
    public void first() {

    }
}

02. 클라이언트 요청에 대한 주소와 문자열을 리턴하는 함수를 작성해보자

  • [조건]
    • 컨트롤러 인식을 위한 Controller 어노테이션 활용
    • 주소매핑은 RequestMapping을 이용
    • HTTP 메소드는 GET 방식
    • 요청 주소는 "/helloworld"
    • 리턴값은 "hello world" 문자열 리턴
    • 주소는 "/helloworld"
  • hello / FirstController.java
    • @ResponseBody
      • @Controller 어노테이션은 View를 반환하는 어노테이션이기에 문자열을 리턴할 수는 없다
      • 그렇기에 @ResponseBody 어노테이션을 함께 사용함으로써 문자열을 리턴해준다
      • @ResponseBody 어노테이션이 붙은 메소드는 반환 값이 View 를 통해서 출력되지 않고 HTTP Response Body 에 직접 담겨져 반환 된다
package com.example.community.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller // Web Template(View) Return
public class FirstController {

    @RequestMapping(value = "/first-url", method = RequestMethod.GET)
    public void first() {

    }

    @ResponseBody
    @RequestMapping(value = "/helloworld", method = RequestMethod.GET)
    public String helloWorld() {
        return "hello world";
    }
} }
}

03. 클라이언트 요청에 대한 주소에 대한 REST 함수를 작성해보자

  • Class : SecondController
  • [조건]
    • 컨트롤러 인식을 위한 Controller 어노테이션 활용
    • 주소매핑은 RequestMapping이 아닌 REST 형식의 어노테이션 이용
    • HTTP 메소드는 GET 방식(어노테이션 이용)
    • 요청 주소는 "/hello-spring"
    • 리턴값은 "hello spring" 문자열 리턴
    • 문자열을 리턴하기 위한 어노테이션 활용
  • hello / SecondController.java
    • @RestController
      • JSON Return
      • @Controller + @ResponsBody
package com.example.community.controller;

import org.springframework.web.bind.annotation.*;

@RestController // REST JSON Return
public class SecondController {

    @RequestMapping(value = "/hello-spring", method = RequestMethod.GET)
    public String helloSpring() {
        return "hello spring";
    }
}

04. 클라이언트 요청에 대한 REST 형식의 함수를 작성

  • [조건]
    • REST Controller 형식의 어노테이션 활용
    • 주소 매핑은 역시 REST 형식의 어노테이션 이용
    • HTTP 메소드는 GET
    • 요청 주소는 "/hello-rest"
    • 리턴 값은 "hello rest"문자열 리턴
  • hello / SecondController.java
    • @GetMapping("")
      • @RequestMapping(value = "", method = RequestMethod.GET)과 동일한 기능
package com.example.community.controller;

import org.springframework.web.bind.annotation.*;

@RestController // REST JSON Return
public class SecondController {

    @RequestMapping(value = "/hello-spring", method = RequestMethod.GET)
    public String helloSpring() {
        return "hello spring";
    }

    @GetMapping("/hello-rest")
    public String helloRest() {
        return "hello rest";
    }
}

05. 클라이언트 요청에 대한 REST API 형식의 함수를 작성해 보자

  • [조건]
    • REST Controller 형식의 어노테이션 활용
    • 주소 매핑은 역시 REST 형식의 어노테이션 이용
    • HTTP 메소드는 GET
    • 요청 주소는 "/api/helloworld"
    • 리턴 값은 "hello rest api"문자열 리턴
  • hello / SecondController.java
package com.example.community.controller;

import org.springframework.web.bind.annotation.*;

@RestController // REST JSON Return
public class SecondController {

    @RequestMapping(value = "/hello-spring", method = RequestMethod.GET)
    public String helloSpring() {
        return "hello spring";
    }

    @GetMapping("/hello-rest")
    public String helloRest() {
        return "hello rest";
    }

    @GetMapping("/api/helloworld")
    public String helloRestApi() {
        return "hello rest api";
    }
}

2. 게시판 기본 목록

01. 공지사항 게시판의 목록에 대한 요청을 처리하는 API를 만들어 보자

  • Package : notice
  • Class : ApiNoticeController
  • [조건]
    • REST API 형식으로 구현
    • HTTP METHOD는 GET
    • 요청 주소는 "/api/notice"
    • 리턴값은 문자열 "공지사항입니다." 리턴
  • notice / ApiNoticeController.java
package com.example.community.notice;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ApiNoticeController {

    @GetMapping("/api/notice")
    public String noticeString() {
        return "공지사항입니다.";
    }
}

02. 공지사항 게시판의 목록에 대한 요청을 처리하는 API를 만들어 보자

  • Package : notice.controller, notice.model
  • Class : NoticeModel
  • [조건]
    • REST API 형식으로 구현
    • HTTP METHOD는 GET
    • 요청 주소는 "/api/notice"
    • 리턴값은 문자열 공지사항 게시판의 내용을 추상화환 모델(게시글ID, 제목, 내용, 등록일)이며 데이터는 아래 내용 리턴(게시글ID = 1, 제목 = 공지사항입니다, 내용 = 공지사항 내용입니다, 등록일 = 오늘 날짜)
  • ApiNoticeController을 notice.controller Package로 이동
  • notice.model / NoticeModel.java
    • @NoArgsConstructor
      • parameter가 없는 생성자
    • @AllArgsConstructor
      • field 전체를 받는 생성자
    • @Data
      • geeter/setter, toString등의 메소드 집합
package com.example.community.notice.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class NoticeModel {
    private int id;
    private String title;
    private String content;
    private LocalDateTime regDate;
}
  • notice.controller / ApiNoticeController.java
package com.example.community.notice.controller;

import com.example.community.notice.model.NoticeModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;

@RestController
public class ApiNoticeController {

    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }

03. 공지사항 게시판의 목록에 대한 요청을 처리하는 API를 만들어 보자

  • [조건]
    • REST API 형식으로 구현
    • HTTP METHOD는 GET
    • 요청 주소는 "/api/notice"
    • 리턴값은 공지사항 게시판의 내용을 추상화환 모델(게시글ID, 제목, 내용, 등록일)이며 데이터는 아래 내용 리턴(게시글ID = 1, 제목 = 공지사항입니다, 내용 = 공지사항 내용입니다, 등록일 = 오늘 날짜), (게시글ID = 2, 제목 = 두번째 공지사항입니다, 내용 = 두번째 공지사항 내용입니다, 등록일 = 오늘 날짜)
  • notice.model / NoticeModel.java
    • @Builder
      • Builder 패턴, static한 클래스를 이용해서 편리하게 사용할 수 있게 도와줌
      • 빌더 패턴(Builder pattern) 이란 복합 객체의 생성 과정과 표현 방법을 분리하여 동일한 생성 절차에서 서로 다른 표현 결과를 만들 수 있게 하는 패턴
      • 빌더패턴을 활용하면 어떤 필드에 어떤 인자를 넣어줬는지 명확히 알 수 있고, 넣어줄 필요 없는 필드(null)는 굳이 선언할 필요 없으니 좋다고 생각했다.
package com.example.community.notice.model;

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

import java.time.LocalDateTime;

// Builder 패턴, static한 클래스를 이용해서 편리하게 사용할 수 있게 도와줌
// 빌더 패턴(Builder pattern) 이란 복합 객체의 생성 과정과 표현 방법을 분리하여 동일한 생성 절차에서 서로 다른 표현 결과를 만들 수 있게 하는 패턴
// 빌더패턴을 활용하면 어떤 필드에 어떤 인자를 넣어줬는지 명확히 알 수 있고, 넣어줄 필요 없는 필드(null)는 굳이 선언할 필요 없으니 좋다고 생각했다.
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class NoticeModel {
    private int id;
    private String title;
    private String content;
    private LocalDateTime regDate;
}
  • Builder 적용 전notice.controller / ApiNoticeController.java
package com.example.community.notice.controller;

import com.example.community.notice.model.NoticeModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@RestController
public class ApiNoticeController {

    /*
    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }
     */

    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {
        List<NoticeModel> noticeList = new ArrayList<>();

        NoticeModel notice1 = new NoticeModel();
        notice1.setId(1);
        notice1.setTitle("공지사항 입니다");
        notice1.setContent("공지사항 내용 입니다");
        notice1.setRegDate(LocalDateTime.of(2021, 07, 03, 11, 11));
        noticeList.add(notice1);

        NoticeModel notice2 = new NoticeModel();
        notice2.setId(2);
        notice2.setTitle("두번째 공지사항 입니다");
        notice2.setContent("두번째 공지사항 내용 입니다");
        notice2.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));
        noticeList.add(notice2);

        return noticeList;
    }
}
  • Builder 적용 후notice.controller / ApiNoticeController.java
    • 객체(Class).builder()....build() 형식
package com.example.community.notice.controller;

import com.example.community.notice.model.NoticeModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@RestController
public class ApiNoticeController {

    /*
    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }
     */

    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {
        List<NoticeModel> noticeList = new ArrayList<>();

        noticeList.add(NoticeModel.builder()
                .id(1)
                .title("공지사항 입니다")
                .content("공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 03, 11, 11))
                .build());

        noticeList.add(NoticeModel.builder()
                .id(2)
                .title("두번째 공지사항 입니다")
                .content("두번째 공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 04, 11, 11))
                .build());

        return noticeList;
    }
}

04. 공지사항 게시판의 목록에 대한 요청을 처리하는 API를 만들어 보자

  • [조건]
    • REST API 형식으로 구현
    • HTTP METHOD는 GET
    • 요청 주소는 "/api/notice"
    • 리턴값은 공지사항 게시판의 내용을 추상화환 모델(게시글ID, 제목, 내용, 등록일)이며 복수형태의 데이터를 리턴
    • 요청한 내용이 없는 빈 목록을 리턴
  • notice.controller / ApiNoticeController.java
package com.example.community.notice.controller;

import com.example.community.notice.model.NoticeModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@RestController
public class ApiNoticeController {

    /*
    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }
     */

    /*
    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {
        List<NoticeModel> noticeList = new ArrayList<>();

        noticeList.add(NoticeModel.builder()
                .id(1)
                .title("공지사항 입니다")
                .content("공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 03, 11, 11))
                .build());

        noticeList.add(NoticeModel.builder()
                .id(2)
                .title("두번째 공지사항 입니다")
                .content("두번째 공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 04, 11, 11))
                .build());

        return noticeList;
    }
     */

    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {

        List<NoticeModel> noticeList = new ArrayList<>();

        return noticeList;
    }
}

05. 공지사항 게시판의 목록 중 전체 개수 정보에 대한 요청을 처리하는 API를 만들어 보자

  • [조건]
    • REST API 형식으로 구현
    • HTTP METHOD는 GET
    • 요청 주소는 "/api/notice/count"
    • 리턴값은 공지사항 게시판 개수(정수)를 리턴
  • [확인사항]
    • 컨트롤러에서 정수형 리턴하였더라도 클라이언트쪽에 내려가는 부분은 문자열이다
  • notice.controller / ApiNoticeController.java
    • int || String으로 반환 값 정해주면 된다 왜냐하면 내려가면 문자열이기 때문이다
      • 하지만 의미적으로는 int형이 맞다
package com.example.community.notice.controller;

import com.example.community.notice.model.NoticeModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@RestController
public class ApiNoticeController {

    /*
    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }
     */

    /*
    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {
        List<NoticeModel> noticeList = new ArrayList<>();

        noticeList.add(NoticeModel.builder()
                .id(1)
                .title("공지사항 입니다")
                .content("공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 03, 11, 11))
                .build());

        noticeList.add(NoticeModel.builder()
                .id(2)
                .title("두번째 공지사항 입니다")
                .content("두번째 공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 04, 11, 11))
                .build());

        return noticeList;
    }
     */

    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {

        List<NoticeModel> noticeList = new ArrayList<>();

        return noticeList;
    }

    @GetMapping("/api/notice/count")
    public int noticeCount() {

        return 10;
    }
}

3. 게시글 작성

01. 공지사항에 글을 등록하기 위해서 글작성에 대한 API를 만들어 보자(기본)

  • [조건]
    • REST API 형식으로 구현
    • HTTP METHOD는 POST
    • 요청 주소는 "/api/notice"
    • 전달되는 파라미터는 x-www-form-urlencoded 형식의 제목, 내용을 입력 받음
    • 파라미터는 추상화하지 않고 기본데이터 타입 형태로 전달 받음
    • 리턴 값은 입력된 형태에 게시글ID(1)를 추가하여 모델 형태로 리턴
  • notice.controller / ApiNoticeController.java
    • @PostMapping()
      • @RequestMapping(value = "", method = RequestMethod.POST)랑 동일
    • @RequestParam
    • POST method이기 때문에 talend API 사용
package com.example.community.notice.controller;

import com.example.community.notice.model.NoticeModel;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@RestController
public class ApiNoticeController {

    /*
    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }
     */

    /*
    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {
        List<NoticeModel> noticeList = new ArrayList<>();

        noticeList.add(NoticeModel.builder()
                .id(1)
                .title("공지사항 입니다")
                .content("공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 03, 11, 11))
                .build());

        noticeList.add(NoticeModel.builder()
                .id(2)
                .title("두번째 공지사항 입니다")
                .content("두번째 공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 04, 11, 11))
                .build());

        return noticeList;
    }
     */

    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {

        List<NoticeModel> noticeList = new ArrayList<>();

        return noticeList;
    }

    @GetMapping("/api/notice/count")
    public int noticeCount() {

        return 10;
    }

    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestParam String title, @RequestParam String content) {
        return NoticeModel.builder()
                .id(1)
                .title(title)
                .content(content)
                .regDate(LocalDateTime.of(2021, 07,04,11,43))
                .build();
    }
}

  • 403 ERROR
    • HTTP 403 Forbidden 클라이언트 오류 상태 응답 코드는 서버에 요청이 전달되었지만, 권한 때문에 거절되었다는 것을 의미합니다.
    • 이 문제는 CSRF와 sameOrigin 문제이다
      - 이 부분은 WebSecurityConfigurerAdapter에 추가
  • config / SecurityConfig.java
package com.example.community.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
// WebSecurityConfigurerAdapter
// HTTP 시큐리티 객체를 사용할 수 있게 해준다
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 외부 호출에 대한 접근 허용
        http.csrf().disable();
        http.headers().frameOptions().sameOrigin();

        // 모든 경로에 대해서 매칭 허용
        http.authorizeRequests()
                .anyRequest().permitAll();

    }
}


02. 공지사항에 글을 등록하기 위해서 글작성에 대한 API를 만들어 보자(모델)

  • [조건]
    • REST API 형식으로 구현
    • HTTP METHOD는 POST
    • 요청 주소는 "/api/notice"
    • 전달되는 파라미터는 x-www-form-urlencoded 형식의 제목, 내용을 입력 받음
    • 파라미터를 공지사항 모델로 추상화하여 전달받음
    • 리턴 값은 입력된 형태에 게시글ID(2)과 등록일자(현재시간)을 추가하여 모델 형태로 리턴
  • config / SecurityConfig.java
package com.example.community.notice.controller;

import com.example.community.notice.model.NoticeModel;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@RestController
public class ApiNoticeController {

    /*
    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }
     */

    /*
    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {
        List<NoticeModel> noticeList = new ArrayList<>();

        noticeList.add(NoticeModel.builder()
                .id(1)
                .title("공지사항 입니다")
                .content("공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 03, 11, 11))
                .build());

        noticeList.add(NoticeModel.builder()
                .id(2)
                .title("두번째 공지사항 입니다")
                .content("두번째 공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 04, 11, 11))
                .build());

        return noticeList;
    }
     */

    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {

        List<NoticeModel> noticeList = new ArrayList<>();

        return noticeList;
    }

    @GetMapping("/api/notice/count")
    public int noticeCount() {

        return 10;
    }

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestParam String title, @RequestParam String content) {
        return NoticeModel.builder()
                .id(1)
                .title(title)
                .content(content)
                .regDate(LocalDateTime.of(2021, 07,04,11,43))
                .build();
    }
     */

    @PostMapping("/api/notice")
    public NoticeModel addNotice(NoticeModel noticeModel) {
        noticeModel.setId(2);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
}


03. 공지사항에 글을 등록하기 위해서 글작성에 대한 API를 만들어 보자(JSON)

  • [조건]
    • REST API 형식으로 구현
    • HTTP METHOD는 POST
    • 요청 주소는 "/api/notice"
    • 전달되는 파라미터는 application/json 형식의 제목, 내용을 입력 받음
    • 파라미터를 공지사항 모델로 추상화하여 전달받음
    • 리턴 값은 입력된 형태에 게시글ID(3)과 등록일자(현재시간)을 추가하여 모델 형태로 리턴
  • config / SecurityConfig.java
package com.example.community.notice.controller;

import com.example.community.notice.model.NoticeModel;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@RestController
public class ApiNoticeController {

    /*
    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }
     */

    /*
    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {
        List<NoticeModel> noticeList = new ArrayList<>();

        noticeList.add(NoticeModel.builder()
                .id(1)
                .title("공지사항 입니다")
                .content("공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 03, 11, 11))
                .build());

        noticeList.add(NoticeModel.builder()
                .id(2)
                .title("두번째 공지사항 입니다")
                .content("두번째 공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 04, 11, 11))
                .build());

        return noticeList;
    }
     */

    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {

        List<NoticeModel> noticeList = new ArrayList<>();

        return noticeList;
    }

    @GetMapping("/api/notice/count")
    public int noticeCount() {

        return 10;
    }

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestParam String title, @RequestParam String content) {
        return NoticeModel.builder()
                .id(1)
                .title(title)
                .content(content)
                .regDate(LocalDateTime.of(2021, 07,04,11,43))
                .build();
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(NoticeModel noticeModel) {
        noticeModel.setId(2);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestBody NoticeModel noticeModel) {
        noticeModel.setId(3);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
}


04. 공지사항에 글을 등록하기 위한 글작성에 대한 API 만들어 보자(DB, JPA)

  • [조건]
    • REST API 형식으로 구현
    • HTTP METHOD는 POST
    • 요청 주소는 "/api/notice"
    • 전달되는 값은 application/json 형식의 제목, 내용을 입력 받음
    • 전달된 값을 저장하기 위한 JPA Repository와 Entity를 통해서 Database에 저장
    • 리턴 값은 저장된 id값이 포함된 Entity 리턴

-1. H2 DB 연결 설정

  • main / resources / application.yml(properties에서 yml로 변경)
    • memory db로 server가 끊기면 내부 데이터 초기화 된다
server :
  port : 8090

# 데이터 베이스 세팅
spring:
  h2:
    console:
      enabled: true
      path: /h2-console

  datasource:
    url: jdbc:h2:mem:backofficeDb #Memory DB
    driver-class-name: org.h2.Driver
    username: root
    password: '1111'

  jpa:
    hibernate:
      ddl-auto: create-drop #배포 시 none
    generate-ddl: true #배포 시 false

    properties:
      format_sql: true
      hibernate:
        show-sql: true

  mvc:
    hiddenmethod:
      filter:
        enabled: true

  mustache:
    suffix: .html

logging:
  level:
    org.hibernate.SQL: trace
    org.hibernate.type: trace

-2. 전달된 값을 저장하기 위한 JPA Repository와 Entity 작성

  • Package : entity, repository
  • Class : Notice, NoticeRepository
  • notice / model / NoticeModel.java -> notice / model / NoticeInput.java
    • 입력 받는 객체에는 입력 받지 않는 id와 regdate 필드는 필요 없다
package com.example.community.notice.model;

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


// 입력 받는 객체에는 입력 받지 않는 id와 regdate 필드는 필요 없다
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class NoticeInput {

    private String title;
    private String content;

}
  • entity / Notice.java
    • 실질적인 DB의 컬럼 내용
    • @Entity
      • 저장되고, 관리되어야하는 데이터
      • pk 지정이 필요
    • @Id
      • pk 설정
    • @GeneratedValue()
      • GeneratedValue, 값이 어떻게 만들어지는지 설정
      • GenerationType.IDENTITY, 자동으로 값 만들어지게 한다
package com.example.community.notice.entity;

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

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity // pk 지정이 필요
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Notice {

    @Id // pk 설정
    @GeneratedValue(strategy = GenerationType.IDENTITY) // GeneratedValue, 값이 어떻게 만들어지는지 설정, GenerationType.IDENTITY, 자동으로 값 만들어지게 한다
    private long id;

    @Column
    private String title;

    @Column
    private String content;

    @Column
    private LocalDateTime regDate;

}
  • repository / NoticeRepository.java
    • @Repository
      • 해당 클래스를 루트 컨테이너에 빈(Bean) 객체로 생성해주는 어노테이션
      • 퍼시스턴스 레이어, DB나 파일같은 외부 I/O 작업을 처리함
      • @Repository는 Interface로 충분하다
      • JpaRepository<Entity, pk type>
package com.example.community.notice.repository;

import com.example.community.notice.entity.Notice;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

// @Repository는 Interface로 충분하다
@Repository
public interface NoticeRepository extends JpaRepository<Notice, Long> {
}
  • notice / controller / ApiNoticeController.java
    • private final NoticeRepository noticeRepository;
      • Repository를 주입받는다. @RequiredArgsConstructor를 활용 해 생성자에서 주입을 받는다
package com.example.community.notice.controller;

import com.example.community.notice.entity.Notice;
import com.example.community.notice.model.NoticeInput;
import com.example.community.notice.repository.NoticeRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@RestController
@RequiredArgsConstructor
public class ApiNoticeController {

    // Repository를 주입받는다. @RequiredArgsConstructor를 활용 해 생성자에서 주입을 받는다
    private final NoticeRepository noticeRepository;

    /*
    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }
     */

    /*
    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {
        List<NoticeModel> noticeList = new ArrayList<>();

        noticeList.add(NoticeModel.builder()
                .id(1)
                .title("공지사항 입니다")
                .content("공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 03, 11, 11))
                .build());

        noticeList.add(NoticeModel.builder()
                .id(2)
                .title("두번째 공지사항 입니다")
                .content("두번째 공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 04, 11, 11))
                .build());

        return noticeList;
    }
     */

    @GetMapping("/api/notice")
    public List<NoticeInput> notice() {

        List<NoticeInput> noticeList = new ArrayList<>();

        return noticeList;
    }

    @GetMapping("/api/notice/count")
    public int noticeCount() {

        return 10;
    }

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestParam String title, @RequestParam String content) {
        return NoticeModel.builder()
                .id(1)
                .title(title)
                .content(content)
                .regDate(LocalDateTime.of(2021, 07,04,11,43))
                .build();
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(NoticeModel noticeModel) {
        noticeModel.setId(2);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestBody NoticeModel noticeModel) {
        noticeModel.setId(3);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {
        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .build();

        noticeRepository.save(notice);

        return notice;
    }
}






05. 공지사항에 글을 등록하기 위한 글작성에 대한 API 만들어 보자(조회수, 좋아요)

  • [조건]
    • REST API 형식으로 구현
    • HTTP METHOD는 POST
    • 요청 주소는 "/api/notice"
    • 전달되는 값은 application/json 형식의 제목, 내용을 입력 받음
    • 공지사항 등록일은 현재 시간을 저장, 공지사항 조회수와 좋아요수는 초기값을 0으로 설정
    • 전달된 값을 저장하기 위한 JPA Repository와 Entity를 통해서 Database에 저장
    • 리턴 값은 저장된 id값이 포함된 Entity 리턴
  • notice.entity / Notice.java
    • 조회수 컬럼과 좋아요 컬럼 추가
package com.example.community.notice.entity;

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

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity // pk 지정이 필요
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Notice {

    @Id // pk 설정
    @GeneratedValue(strategy = GenerationType.IDENTITY) // GeneratedValue, 값이 어떻게 만들어지는지 설정, GenerationType.IDENTITY, 자동으로 값 만들어지게 한다
    private long id;

    @Column
    private String title;

    @Column
    private String content;

    @Column
    private LocalDateTime regDate;

    // 조회수
    @Column
    private int hits;

    // 좋아요
    @Column
    private int likes;
}
  • notice.controlelr / ApiNoticeController.java
package com.example.community.notice.controller;

import com.example.community.notice.entity.Notice;
import com.example.community.notice.model.NoticeInput;
import com.example.community.notice.repository.NoticeRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@RestController
@RequiredArgsConstructor
public class ApiNoticeController {

    // Repository를 주입받는다. @RequiredArgsConstructor를 활용 해 생성자에서 주입을 받는다
    private final NoticeRepository noticeRepository;

    /*
    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }
     */

    /*
    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {
        List<NoticeModel> noticeList = new ArrayList<>();

        noticeList.add(NoticeModel.builder()
                .id(1)
                .title("공지사항 입니다")
                .content("공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 03, 11, 11))
                .build());

        noticeList.add(NoticeModel.builder()
                .id(2)
                .title("두번째 공지사항 입니다")
                .content("두번째 공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 04, 11, 11))
                .build());

        return noticeList;
    }
     */

    @GetMapping("/api/notice")
    public List<NoticeInput> notice() {

        List<NoticeInput> noticeList = new ArrayList<>();

        return noticeList;
    }

    @GetMapping("/api/notice/count")
    public int noticeCount() {

        return 10;
    }

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestParam String title, @RequestParam String content) {
        return NoticeModel.builder()
                .id(1)
                .title(title)
                .content(content)
                .regDate(LocalDateTime.of(2021, 07,04,11,43))
                .build();
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(NoticeModel noticeModel) {
        noticeModel.setId(2);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestBody NoticeModel noticeModel) {
        noticeModel.setId(3);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {
        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .build();

        noticeRepository.save(notice);

        return notice;
    }
     */

    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {

        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .hits(0)
                .likes(0)
                .build();

        Notice resultNotice = noticeRepository.save(notice);

        return resultNotice;
    }
}




4. 게시글 수정

01. 공지사항에 글을 수정하기 위한 상세정보 요청에 대한 API를 만들어 보자(기본)

  • [조건]
    • REST API 형식으로 구현
    • HTTP METHOD는 GET
    • 요청 주소는 "/api/notice/1" ("1"은 공지사항의 글ID로 동적으로 변함)
    • Database에 프로그램 실행 시 H2DB에 INSERT 되어 있음
    • 조회된 결과가 있는 경우 Entity 리턴 없는 경우 null 리턴

-1. 파일로 데이터베이스 생성 및 데이터 입력

  • resources / schema.sql
DROP TABLE IF EXISTS NOTICE;

-- auto-generated definition
create table NOTICE
(
    ID BIGINT auto_increment primary key,
    TITLE VARCHAR(255),
    CONTENT VARCHAR(255),

    HITS INTEGER,
    LIKES INTEGER,
    REG_DATE TIMESTAMP
);
  • resources / data.sql
INSERT INTO NOTICE(ID, CONTENT, HITS, LIKES, REG_DATE, TITLE) VALUES(1, '내용1', 0, 0, '2021-02-01 01:12:20', '제목1');
INSERT INTO NOTICE(ID, CONTENT, HITS, LIKES, REG_DATE, TITLE) VALUES(2, '내용2', 0, 0, '2021-02-01 01:12:20', '제목2');
INSERT INTO NOTICE(ID, CONTENT, HITS, LIKES, REG_DATE, TITLE) VALUES(3, '내용3', 0, 0, '2021-02-01 01:12:20', '제목3');
  • resources / application.yml
    • jpa 내용 중 ddl-autogenerate-ddl 수정
server :
  port : 8090

# 데이터 베이스 세팅
spring:
  h2:
    console:
      enabled: true
      path: /h2-console

  datasource:
    url: jdbc:h2:mem:backofficeDb #Memory DB
    driver-class-name: org.h2.Driver
    username: root
    password: '1111'

  jpa:
    hibernate:
      ddl-auto:  none #배포 시 none 반대 create-drop, 데이터 베이스를 내가 직접 생성
    generate-ddl: false #배포 시 false 반대 true, true로하면 데이터가 날라가고 flase로 하면 데이터가 남는다

    properties:
      format_sql: true
      hibernate:
        show-sql: true

  mvc:
    hiddenmethod:
      filter:
        enabled: true

  mustache:
    suffix: .html

logging:
  level:
    org.hibernate.SQL: trace
    org.hibernate.type: trace

-2. 데이터 조회

  • notice.controller / ApiNoticeController.java
    • .findById()
      • .findById()는 반환타입이 Optional이다, 즉 null을 반환할 수도 있다
      • 그렇기에 Notice를 Optional로 감싸준다
package com.example.community.notice.controller;

import com.example.community.notice.entity.Notice;
import com.example.community.notice.model.NoticeInput;
import com.example.community.notice.repository.NoticeRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@RestController
@RequiredArgsConstructor
public class ApiNoticeController {

    // Repository를 주입받는다. @RequiredArgsConstructor를 활용 해 생성자에서 주입을 받는다
    private final NoticeRepository noticeRepository;

    /*
    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }
     */

    /*
    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {
        List<NoticeModel> noticeList = new ArrayList<>();

        noticeList.add(NoticeModel.builder()
                .id(1)
                .title("공지사항 입니다")
                .content("공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 03, 11, 11))
                .build());

        noticeList.add(NoticeModel.builder()
                .id(2)
                .title("두번째 공지사항 입니다")
                .content("두번째 공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 04, 11, 11))
                .build());

        return noticeList;
    }
     */

    @GetMapping("/api/notice")
    public List<NoticeInput> notice() {

        List<NoticeInput> noticeList = new ArrayList<>();

        return noticeList;
    }

    @GetMapping("/api/notice/count")
    public int noticeCount() {

        return 10;
    }

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestParam String title, @RequestParam String content) {
        return NoticeModel.builder()
                .id(1)
                .title(title)
                .content(content)
                .regDate(LocalDateTime.of(2021, 07,04,11,43))
                .build();
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(NoticeModel noticeModel) {
        noticeModel.setId(2);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestBody NoticeModel noticeModel) {
        noticeModel.setId(3);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {
        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .build();

        noticeRepository.save(notice);

        return notice;
    }
     */

    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {

        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .hits(0)
                .likes(0)
                .build();

        Notice resultNotice = noticeRepository.save(notice);

        return resultNotice;
    }

    @GetMapping("/api/notice/{id}")
    public Notice notice(@PathVariable Long id) {
        // .findById()는 반환타입이 Optional이다, 즉 null을 반환할 수도 있다
        // 그렇기에 Notice를 Optional로 감싸준다
        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            return notice.get();
        }
        
        return null;
    }
}


02. 공지사항에 글을 수정하기 위한 상세정보 요청에 대한 API를 만들어 보자(PUT)

  • [조건]
    • REST API 형식으로 구현
    • HTTP METHOD는 PUT
    • 요청 주소는 "/api/notice/1" ("1"은 공지사항의 글ID로 동적으로 변함)
    • 전달되는 값은 application/json 형식의 공지사항 글ID, 제목, 내용을 입력받음
    • 공지사항 수정일은 현재 시간을 저장, 공지사항 조회수와 좋아요수는 변경하지 않음
    • 데이터를 수정한 경우는 Data매핑에 대한 Entity로 필요 없는 항목까지 받지 말고 필요한 데이터만 입력받게 작성
    • 전달된 값을 수정하기 위한 JPA Repository와 Entity를 통해서 Database에 수정

-1. UPDATE_DATE Column 추가

  • notice.entity / Notice.java
package com.example.community.notice.entity;

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

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity // pk 지정이 필요
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Notice {

    @Id // pk 설정
    @GeneratedValue(strategy = GenerationType.IDENTITY) // GeneratedValue, 값이 어떻게 만들어지는지 설정, GenerationType.IDENTITY, 자동으로 값 만들어지게 한다
    private long id;

    @Column
    private String title;

    @Column
    private String content;

    @Column
    private LocalDateTime regDate;

    @Column
    private LocalDateTime updateDate;

    // 조회수
    @Column
    private int hits;

    // 좋아요
    @Column
    private int likes;
}
  • resources / schema.sql
DROP TABLE IF EXISTS NOTICE;

-- auto-generated definition
create table NOTICE
(
    ID BIGINT auto_increment primary key,
    TITLE VARCHAR(255),
    CONTENT VARCHAR(255),

    HITS         INTEGER,
    LIKES        INTEGER,
    REG_DATE     TIMESTAMP,
    UPDATE_DATE  TIMESTAMP,
);

-2. controller 생성

  • notice.controller / ApiNoticeController.java
package com.example.community.notice.controller;

import com.example.community.notice.entity.Notice;
import com.example.community.notice.exception.AlreadyDeletedException;
import com.example.community.notice.exception.NoticeNotFoundException;
import com.example.community.notice.model.NoticeInput;
import com.example.community.notice.repository.NoticeRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@RestController
@RequiredArgsConstructor
public class ApiNoticeController {

    // Repository를 주입받는다. @RequiredArgsConstructor를 활용 해 생성자에서 주입을 받는다
    private final NoticeRepository noticeRepository;

    /*
    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }
     */

    /*
    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {
        List<NoticeModel> noticeList = new ArrayList<>();

        noticeList.add(NoticeModel.builder()
                .id(1)
                .title("공지사항 입니다")
                .content("공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 03, 11, 11))
                .build());

        noticeList.add(NoticeModel.builder()
                .id(2)
                .title("두번째 공지사항 입니다")
                .content("두번째 공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 04, 11, 11))
                .build());

        return noticeList;
    }
     */

    @GetMapping("/api/notice")
    public List<NoticeInput> notice() {

        List<NoticeInput> noticeList = new ArrayList<>();

        return noticeList;
    }

    @GetMapping("/api/notice/count")
    public int noticeCount() {

        return 10;
    }

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestParam String title, @RequestParam String content) {
        return NoticeModel.builder()
                .id(1)
                .title(title)
                .content(content)
                .regDate(LocalDateTime.of(2021, 07,04,11,43))
                .build();
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(NoticeModel noticeModel) {
        noticeModel.setId(2);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestBody NoticeModel noticeModel) {
        noticeModel.setId(3);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {
        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .build();

        noticeRepository.save(notice);

        return notice;
    }
     */

    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {

        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .hits(0)
                .likes(0)
                .build();

        Notice resultNotice = noticeRepository.save(notice);

        return resultNotice;
    }

    @GetMapping("/api/notice/{id}")
    public Notice notice(@PathVariable Long id) {
        // .findById()는 반환타입이 Optional이다, 즉 null을 반환할 수도 있다
        // 그렇기에 Notice를 Optional로 감싸준다
        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            return notice.get();
        }

        return null;
    }


    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id,@RequestBody NoticeInput noticeInput) {

        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            notice.get().setTitle(noticeInput.getTitle());
            notice.get().setContent(noticeInput.getContent());
            notice.get().setUpdateDate(LocalDateTime.now());
            noticeRepository.save(notice.get());
        }
    }

}



03. 공지사항에 글을 수정하기 위한 상세정보 요청에 대한 API를 만들어 보자(예외처리, exception)

  • Package : exception
  • Class : NoticeNotFoundException
  • [조건]
    • REST API 형식으로 구현
    • HTTP METHOD는 PUT
    • 요청 주소는 "/api/notice/1" ("1"은 공지사항의 글ID로 동적으로 변함)
    • 전달되는 값은 application/json 형식의 공지사항 글ID, 제목, 내용을 입력받음
    • 공지사항 수정일은 현재 시간을 저장, 공지사항 조회수와 좋아요수는 변경하지 않음
    • 데이터를 수정한 경우는 Data매핑에 대한 Entity로 필요 없는 항목까지 받지 말고 필요한 데이터만 입력받게 작성
    • 공지사항의 글이 존재하지 않을 경우 예외사항을 발생시킨다
    • 예외처리는 ExceptionHandler를 통해서 구현하고, 발생하는 예외에 대해서는 400, 예외 메시지 리턴

-1. 예외처리 생성

  • notice.exception / NoticeNotFoundException.java
    • RuntimeException
      • 실행 중에 발생하며 시스템 환경적으로나 인풋 값이 잘못된 경우, 혹은 의도적으로 프로그래머가 잡아내기 위한 조건등에 부합할 때 발생(throw)되게 만든다
      • RuntimeException을 사용하면 따로 예외처리를 해주지 않아도 된다
package com.example.community.notice.exception;

public class NoticeNotFoundException extends RuntimeException {
    public NoticeNotFoundException(String message) {
        super(message);
    }
}
  • notice.controller / ApiNoticeController.java
    • @ExceptionHandler()
      • 예외가 발생한 요청을 처리하기위한 핸들러, Exception이 발생하게 될 경우 자동으로 정의한 Handler가 실행
package com.example.community.notice.controller;

import com.example.community.notice.entity.Notice;
import com.example.community.notice.exception.AlreadyDeletedException;
import com.example.community.notice.exception.NoticeNotFoundException;
import com.example.community.notice.model.NoticeInput;
import com.example.community.notice.repository.NoticeRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@RestController
@RequiredArgsConstructor
public class ApiNoticeController {

    // Repository를 주입받는다. @RequiredArgsConstructor를 활용 해 생성자에서 주입을 받는다
    private final NoticeRepository noticeRepository;

    /*
    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }
     */

    /*
    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {
        List<NoticeModel> noticeList = new ArrayList<>();

        noticeList.add(NoticeModel.builder()
                .id(1)
                .title("공지사항 입니다")
                .content("공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 03, 11, 11))
                .build());

        noticeList.add(NoticeModel.builder()
                .id(2)
                .title("두번째 공지사항 입니다")
                .content("두번째 공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 04, 11, 11))
                .build());

        return noticeList;
    }
     */

    @GetMapping("/api/notice")
    public List<NoticeInput> notice() {

        List<NoticeInput> noticeList = new ArrayList<>();

        return noticeList;
    }

    @GetMapping("/api/notice/count")
    public int noticeCount() {

        return 10;
    }

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestParam String title, @RequestParam String content) {
        return NoticeModel.builder()
                .id(1)
                .title(title)
                .content(content)
                .regDate(LocalDateTime.of(2021, 07,04,11,43))
                .build();
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(NoticeModel noticeModel) {
        noticeModel.setId(2);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestBody NoticeModel noticeModel) {
        noticeModel.setId(3);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {
        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .build();

        noticeRepository.save(notice);

        return notice;
    }
     */

    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {

        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .hits(0)
                .likes(0)
                .build();

        Notice resultNotice = noticeRepository.save(notice);

        return resultNotice;
    }

    @GetMapping("/api/notice/{id}")
    public Notice notice(@PathVariable Long id) {
        // .findById()는 반환타입이 Optional이다, 즉 null을 반환할 수도 있다
        // 그렇기에 Notice를 Optional로 감싸준다
        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            return notice.get();
        }

        return null;
    }

    /*
    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id,@RequestBody NoticeInput noticeInput) {

        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            notice.get().setTitle(noticeInput.getTitle());
            notice.get().setContent(noticeInput.getContent());
            notice.get().setUpdateDate(LocalDateTime.now());
            noticeRepository.save(notice.get());
        }
    }
     */


    @ExceptionHandler(NoticeNotFoundException.class)
    public ResponseEntity<String> handlerNoticeNotFoundException(NoticeNotFoundException exception) {
            return new ResponseEntity<>(exception.getMessage(), HttpStatus.BAD_REQUEST);
    }

    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id, @RequestBody NoticeInput noticeInput) {


        Optional<Notice> notice = noticeRepository.findById(id);
        if(!notice.isPresent()) {
            // 예외 발생
            throw new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다.");
        }

        // 정상로직 수행
        notice.get().setTitle(noticeInput.getTitle());
        notice.get().setContent(noticeInput.getContent());
        notice.get().setUpdateDate(LocalDateTime.now());
        noticeRepository.save(notice.get());

    }

   
}




-2. 예외처리 생성(.orElseThrow() 활용)

  • notice.controller / ApiNoticeController.java
    • 이 경우 Optional이 아니기에 객체를 바로 받아 사용가능하다
package com.example.community.notice.controller;

import com.example.community.notice.entity.Notice;
import com.example.community.notice.exception.AlreadyDeletedException;
import com.example.community.notice.exception.NoticeNotFoundException;
import com.example.community.notice.model.NoticeInput;
import com.example.community.notice.repository.NoticeRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@RestController
@RequiredArgsConstructor
public class ApiNoticeController {

    // Repository를 주입받는다. @RequiredArgsConstructor를 활용 해 생성자에서 주입을 받는다
    private final NoticeRepository noticeRepository;

    /*
    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }
     */

    /*
    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {
        List<NoticeModel> noticeList = new ArrayList<>();

        noticeList.add(NoticeModel.builder()
                .id(1)
                .title("공지사항 입니다")
                .content("공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 03, 11, 11))
                .build());

        noticeList.add(NoticeModel.builder()
                .id(2)
                .title("두번째 공지사항 입니다")
                .content("두번째 공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 04, 11, 11))
                .build());

        return noticeList;
    }
     */

    @GetMapping("/api/notice")
    public List<NoticeInput> notice() {

        List<NoticeInput> noticeList = new ArrayList<>();

        return noticeList;
    }

    @GetMapping("/api/notice/count")
    public int noticeCount() {

        return 10;
    }

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestParam String title, @RequestParam String content) {
        return NoticeModel.builder()
                .id(1)
                .title(title)
                .content(content)
                .regDate(LocalDateTime.of(2021, 07,04,11,43))
                .build();
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(NoticeModel noticeModel) {
        noticeModel.setId(2);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestBody NoticeModel noticeModel) {
        noticeModel.setId(3);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {
        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .build();

        noticeRepository.save(notice);

        return notice;
    }
     */

    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {

        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .hits(0)
                .likes(0)
                .build();

        Notice resultNotice = noticeRepository.save(notice);

        return resultNotice;
    }

    @GetMapping("/api/notice/{id}")
    public Notice notice(@PathVariable Long id) {
        // .findById()는 반환타입이 Optional이다, 즉 null을 반환할 수도 있다
        // 그렇기에 Notice를 Optional로 감싸준다
        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            return notice.get();
        }

        return null;
    }

    /*
    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id,@RequestBody NoticeInput noticeInput) {

        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            notice.get().setTitle(noticeInput.getTitle());
            notice.get().setContent(noticeInput.getContent());
            notice.get().setUpdateDate(LocalDateTime.now());
            noticeRepository.save(notice.get());
        }
    }
     */


    @ExceptionHandler(NoticeNotFoundException.class)
    public ResponseEntity<String> handlerNoticeNotFoundException(NoticeNotFoundException exception) {
            return new ResponseEntity<>(exception.getMessage(), HttpStatus.BAD_REQUEST);
    }

    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id, @RequestBody NoticeInput noticeInput) {

        /*
        Optional<Notice> notice = noticeRepository.findById(id);
        if(!notice.isPresent()) {
            // 예외 발생
            throw new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다.");
        }
        */


        // 이 경우 Optional이 아니기에 객체를 바로 받아 사용가능하다
        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));


        // 정상로직 수행
        notice.setTitle(noticeInput.getTitle());
        notice.setContent(noticeInput.getContent());
        notice.setUpdateDate(LocalDateTime.now());
        noticeRepository.save(notice);

    }

    
}




02. 공지사항에 글의 조회수를 증가시키는 API를 만들어 보자

  • [조건]
    • REST API 형식으로 구현
    • HTTP METHOD는 PATCH
    • 요청 주소는 "/api/notice/1/hits" ("1"은 공지사항의 글ID로 동적으로 변함)
  • notice.controller / ApiNoticeController.java
package com.example.community.notice.controller;

import com.example.community.notice.entity.Notice;
import com.example.community.notice.exception.AlreadyDeletedException;
import com.example.community.notice.exception.NoticeNotFoundException;
import com.example.community.notice.model.NoticeInput;
import com.example.community.notice.repository.NoticeRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@RestController
@RequiredArgsConstructor
public class ApiNoticeController {

    // Repository를 주입받는다. @RequiredArgsConstructor를 활용 해 생성자에서 주입을 받는다
    private final NoticeRepository noticeRepository;

    /*
    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }
     */

    /*
    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {
        List<NoticeModel> noticeList = new ArrayList<>();

        noticeList.add(NoticeModel.builder()
                .id(1)
                .title("공지사항 입니다")
                .content("공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 03, 11, 11))
                .build());

        noticeList.add(NoticeModel.builder()
                .id(2)
                .title("두번째 공지사항 입니다")
                .content("두번째 공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 04, 11, 11))
                .build());

        return noticeList;
    }
     */

    @GetMapping("/api/notice")
    public List<NoticeInput> notice() {

        List<NoticeInput> noticeList = new ArrayList<>();

        return noticeList;
    }

    @GetMapping("/api/notice/count")
    public int noticeCount() {

        return 10;
    }

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestParam String title, @RequestParam String content) {
        return NoticeModel.builder()
                .id(1)
                .title(title)
                .content(content)
                .regDate(LocalDateTime.of(2021, 07,04,11,43))
                .build();
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(NoticeModel noticeModel) {
        noticeModel.setId(2);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestBody NoticeModel noticeModel) {
        noticeModel.setId(3);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {
        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .build();

        noticeRepository.save(notice);

        return notice;
    }
     */

    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {

        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .hits(0)
                .likes(0)
                .build();

        Notice resultNotice = noticeRepository.save(notice);

        return resultNotice;
    }

    @GetMapping("/api/notice/{id}")
    public Notice notice(@PathVariable Long id) {
        // .findById()는 반환타입이 Optional이다, 즉 null을 반환할 수도 있다
        // 그렇기에 Notice를 Optional로 감싸준다
        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            return notice.get();
        }

        return null;
    }

    /*
    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id,@RequestBody NoticeInput noticeInput) {

        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            notice.get().setTitle(noticeInput.getTitle());
            notice.get().setContent(noticeInput.getContent());
            notice.get().setUpdateDate(LocalDateTime.now());
            noticeRepository.save(notice.get());
        }
    }
     */


    @ExceptionHandler(NoticeNotFoundException.class)
    public ResponseEntity<String> handlerNoticeNotFoundException(NoticeNotFoundException exception) {
            return new ResponseEntity<>(exception.getMessage(), HttpStatus.BAD_REQUEST);
    }

    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id, @RequestBody NoticeInput noticeInput) {

        /*
        Optional<Notice> notice = noticeRepository.findById(id);
        if(!notice.isPresent()) {
            // 예외 발생
            throw new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다.");
        }
        */


        // 이 경우 Optional이 아니기에 객체를 바로 받아 사용가능하다
        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));


        // 정상로직 수행
        notice.setTitle(noticeInput.getTitle());
        notice.setContent(noticeInput.getContent());
        notice.setUpdateDate(LocalDateTime.now());
        noticeRepository.save(notice);

    }

    @PatchMapping("/api/notice/{id}/hits")
    public void noticeHits(@PathVariable Long id) {
        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        notice.setHits(notice.getHits() + 1);

        noticeRepository.save(notice);
    }
    
}




5. 게시글 삭제

01. 공지사항의 글을 삭제하기 위한 API를 만들어 보자

  • [조건]

    • REST API 형식으로 구현
    • HTTP METHOD는 DELETE
    • 요청 주소는 "/api/notice/1" ("1"은 공지사항의 글ID로 동적으로 변함)
  • notice.controller / ApiNoticeController.java

package com.example.community.notice.controller;

import com.example.community.notice.entity.Notice;
import com.example.community.notice.exception.AlreadyDeletedException;
import com.example.community.notice.exception.NoticeNotFoundException;
import com.example.community.notice.model.NoticeInput;
import com.example.community.notice.repository.NoticeRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@RestController
@RequiredArgsConstructor
public class ApiNoticeController {

    // Repository를 주입받는다. @RequiredArgsConstructor를 활용 해 생성자에서 주입을 받는다
    private final NoticeRepository noticeRepository;

    /*
    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }
     */

    /*
    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {
        List<NoticeModel> noticeList = new ArrayList<>();

        noticeList.add(NoticeModel.builder()
                .id(1)
                .title("공지사항 입니다")
                .content("공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 03, 11, 11))
                .build());

        noticeList.add(NoticeModel.builder()
                .id(2)
                .title("두번째 공지사항 입니다")
                .content("두번째 공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 04, 11, 11))
                .build());

        return noticeList;
    }
     */

    @GetMapping("/api/notice")
    public List<NoticeInput> notice() {

        List<NoticeInput> noticeList = new ArrayList<>();

        return noticeList;
    }

    @GetMapping("/api/notice/count")
    public int noticeCount() {

        return 10;
    }

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestParam String title, @RequestParam String content) {
        return NoticeModel.builder()
                .id(1)
                .title(title)
                .content(content)
                .regDate(LocalDateTime.of(2021, 07,04,11,43))
                .build();
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(NoticeModel noticeModel) {
        noticeModel.setId(2);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestBody NoticeModel noticeModel) {
        noticeModel.setId(3);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {
        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .build();

        noticeRepository.save(notice);

        return notice;
    }
     */

    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {

        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .hits(0)
                .likes(0)
                .build();

        Notice resultNotice = noticeRepository.save(notice);

        return resultNotice;
    }

    @GetMapping("/api/notice/{id}")
    public Notice notice(@PathVariable Long id) {
        // .findById()는 반환타입이 Optional이다, 즉 null을 반환할 수도 있다
        // 그렇기에 Notice를 Optional로 감싸준다
        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            return notice.get();
        }

        return null;
    }

    /*
    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id,@RequestBody NoticeInput noticeInput) {

        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            notice.get().setTitle(noticeInput.getTitle());
            notice.get().setContent(noticeInput.getContent());
            notice.get().setUpdateDate(LocalDateTime.now());
            noticeRepository.save(notice.get());
        }
    }
     */


    @ExceptionHandler(NoticeNotFoundException.class)
    public ResponseEntity<String> handlerNoticeNotFoundException(NoticeNotFoundException exception) {
            return new ResponseEntity<>(exception.getMessage(), HttpStatus.BAD_REQUEST);
    }

    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id, @RequestBody NoticeInput noticeInput) {

        /*
        Optional<Notice> notice = noticeRepository.findById(id);
        if(!notice.isPresent()) {
            // 예외 발생
            throw new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다.");
        }
        */


        // 이 경우 Optional이 아니기에 객체를 바로 받아 사용가능하다
        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));


        // 정상로직 수행
        notice.setTitle(noticeInput.getTitle());
        notice.setContent(noticeInput.getContent());
        notice.setUpdateDate(LocalDateTime.now());
        noticeRepository.save(notice);

    }

    @PatchMapping("/api/notice/{id}/hits")
    public void noticeHits(@PathVariable Long id) {
        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        notice.setHits(notice.getHits() + 1);

        noticeRepository.save(notice);
    }


    @DeleteMapping("/api/notice/{id}")
    public void deleteNotice(@PathVariable Long id) {

        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        noticeRepository.delete(notice);

    }

}




02. 공지사항의 글을 삭제하기 위한 API를 만들어 보자(삭제 예외처리 추가 및 DELETED, DELETED_DATE 추가)

  • [조건]
    • REST API 형식으로 구현
    • HTTP METHOD는 DELETE
    • 요청 주소는 "/api/notice/1" ("1"은 공지사항의 글ID로 동적으로 변함)
    • 게시판의 글을 물리적으로 삭제하지 않고 삭제 플래그값을 이용하여 삭제를 진행
    • 삭제 일시는 현재 시간으로 설정
    • 공지사항의 글이 이미 삭제된 경우 200코드와 "이미 삭제된 글입니다."라는 메시지를 리턴

-1. DELETED, DELETED_DATE 추가

  • notice.entity / Notice.java
package com.example.community.notice.entity;

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

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity // pk 지정이 필요
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Notice {

    @Id // pk 설정
    @GeneratedValue(strategy = GenerationType.IDENTITY) // GeneratedValue, 값이 어떻게 만들어지는지 설정, GenerationType.IDENTITY, 자동으로 값 만들어지게 한다
    private long id;

    @Column
    private String title;

    @Column
    private String content;

    @Column
    private LocalDateTime regDate;

    @Column
    private LocalDateTime updateDate;

    // 조회수
    @Column
    private int hits;

    // 좋아요
    @Column
    private int likes;

    // 삭제 여부
    @Column
    private boolean deleted;

    // 삭제 날짜짜
    @Column
    private LocalDateTime deletedDate;
}
  • resources / schema.sql
DROP TABLE IF EXISTS NOTICE;

-- auto-generated definition
create table NOTICE
(
    ID BIGINT auto_increment primary key,
    TITLE VARCHAR(255),
    CONTENT VARCHAR(255),

    HITS         INTEGER,
    LIKES        INTEGER,
    REG_DATE     TIMESTAMP,
    UPDATE_DATE  TIMESTAMP,
    DELETED_DATE TIMESTAMP,
    DELETED      BOOLEAN
);
  • resources / data.sql
INSERT INTO NOTICE(ID, CONTENT, HITS, LIKES, REG_DATE, TITLE, DELETED) VALUES(1, '내용1', 0, 0, '2021-02-01 01:12:20', '제목1', 0);
INSERT INTO NOTICE(ID, CONTENT, HITS, LIKES, REG_DATE, TITLE, DELETED) VALUES(2, '내용2', 0, 0, '2021-02-01 01:12:20', '제목2', 0);
INSERT INTO NOTICE(ID, CONTENT, HITS, LIKES, REG_DATE, TITLE, DELETED) VALUES(3, '내용3', 0, 0, '2021-02-01 01:12:20', '제목3', 0);

-2. 예외처리 생성

  • notice.exception / AlreadyDeletedException.java
package com.example.community.notice.exception;

public class AlreadyDeletedException extends RuntimeException {
    public AlreadyDeletedException(String message) {
        super(message);
    }
}
  • notice.controller / ApiNoticeController.java
package com.example.community.notice.controller;

import com.example.community.notice.entity.Notice;
import com.example.community.notice.exception.AlreadyDeletedException;
import com.example.community.notice.exception.NoticeNotFoundException;
import com.example.community.notice.model.NoticeInput;
import com.example.community.notice.repository.NoticeRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@RestController
@RequiredArgsConstructor
public class ApiNoticeController {

    // Repository를 주입받는다. @RequiredArgsConstructor를 활용 해 생성자에서 주입을 받는다
    private final NoticeRepository noticeRepository;

    /*
    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }
     */

    /*
    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {
        List<NoticeModel> noticeList = new ArrayList<>();

        noticeList.add(NoticeModel.builder()
                .id(1)
                .title("공지사항 입니다")
                .content("공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 03, 11, 11))
                .build());

        noticeList.add(NoticeModel.builder()
                .id(2)
                .title("두번째 공지사항 입니다")
                .content("두번째 공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 04, 11, 11))
                .build());

        return noticeList;
    }
     */

    @GetMapping("/api/notice")
    public List<NoticeInput> notice() {

        List<NoticeInput> noticeList = new ArrayList<>();

        return noticeList;
    }

    @GetMapping("/api/notice/count")
    public int noticeCount() {

        return 10;
    }

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestParam String title, @RequestParam String content) {
        return NoticeModel.builder()
                .id(1)
                .title(title)
                .content(content)
                .regDate(LocalDateTime.of(2021, 07,04,11,43))
                .build();
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(NoticeModel noticeModel) {
        noticeModel.setId(2);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestBody NoticeModel noticeModel) {
        noticeModel.setId(3);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {
        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .build();

        noticeRepository.save(notice);

        return notice;
    }
     */

    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {

        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .hits(0)
                .likes(0)
                .build();

        Notice resultNotice = noticeRepository.save(notice);

        return resultNotice;
    }

    @GetMapping("/api/notice/{id}")
    public Notice notice(@PathVariable Long id) {
        // .findById()는 반환타입이 Optional이다, 즉 null을 반환할 수도 있다
        // 그렇기에 Notice를 Optional로 감싸준다
        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            return notice.get();
        }

        return null;
    }

    /*
    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id,@RequestBody NoticeInput noticeInput) {

        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            notice.get().setTitle(noticeInput.getTitle());
            notice.get().setContent(noticeInput.getContent());
            notice.get().setUpdateDate(LocalDateTime.now());
            noticeRepository.save(notice.get());
        }
    }
     */


    @ExceptionHandler(NoticeNotFoundException.class)
    public ResponseEntity<String> handlerNoticeNotFoundException(NoticeNotFoundException exception) {
            return new ResponseEntity<>(exception.getMessage(), HttpStatus.BAD_REQUEST);
    }

    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id, @RequestBody NoticeInput noticeInput) {

        /*
        Optional<Notice> notice = noticeRepository.findById(id);
        if(!notice.isPresent()) {
            // 예외 발생
            throw new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다.");
        }
        */


        // 이 경우 Optional이 아니기에 객체를 바로 받아 사용가능하다
        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));


        // 정상로직 수행
        notice.setTitle(noticeInput.getTitle());
        notice.setContent(noticeInput.getContent());
        notice.setUpdateDate(LocalDateTime.now());
        noticeRepository.save(notice);

    }

    @PatchMapping("/api/notice/{id}/hits")
    public void noticeHits(@PathVariable Long id) {
        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        notice.setHits(notice.getHits() + 1);

        noticeRepository.save(notice);
    }

    /*
    @DeleteMapping("/api/notice/{id}")
    public void deleteNotice(@PathVariable Long id) {

        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        noticeRepository.delete(notice);

    }
     */

    @ExceptionHandler(AlreadyDeletedException.class)
    public ResponseEntity<String> handlerAlreadyDeletedException(AlreadyDeletedException exception) {
        return new ResponseEntity<>(exception.getMessage(), HttpStatus.OK);
    }

    @DeleteMapping("/api/notice/{id}")
    public void deleteNotice(@PathVariable Long id) {

        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        if(notice.isDeleted()) {
            throw new AlreadyDeletedException("이미 삭제된 공지사항의 글입니다.");
        }

        notice.setDeleted(true);
        notice.setDeletedDate(LocalDateTime.now());

        noticeRepository.save(notice);
    }
}





02. 공지사항의 글을 삭제하기 위한 API를 만들어 보자(여러 개의 공지사항글 삭제)

  • Class : NoticeDeleteInput
  • [조건]
    • REST API 형식으로 구현
    • HTTP METHOD는 DELETE
    • 요청 주소는 "/api/notice"
    • 여러개의 글을 동시에 삭제하기 위해서 noticeId 목록을 파라미터로 받아서 해당 공지사항의 글을 삭제

-1.여러개의 ID를 받아오는 Method 생성

  • notice.reoisutiry / NoticeRepository.java
package com.example.community.notice.repository;

import com.example.community.notice.entity.Notice;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

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

// @Repository는 Interface로 충분하다
@Repository
public interface NoticeRepository extends JpaRepository<Notice, Long> {

    Optional<List<Notice>> findByIdIn(List<Long> idList);
}
  • notice.model / NoticeDeleteInput.java
package com.example.community.notice.model;

import lombok.Data;

import java.util.List;

@Data
public class NoticeDeleteInput {

    private List<Long> idList;
}
  • notice.controller / ApiNoticeController.java
package com.example.community.notice.controller;

import com.example.community.notice.entity.Notice;
import com.example.community.notice.exception.AlreadyDeletedException;
import com.example.community.notice.exception.NoticeNotFoundException;
import com.example.community.notice.model.NoticeDeleteInput;
import com.example.community.notice.model.NoticeInput;
import com.example.community.notice.repository.NoticeRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@RestController
@RequiredArgsConstructor
public class ApiNoticeController {

    // Repository를 주입받는다. @RequiredArgsConstructor를 활용 해 생성자에서 주입을 받는다
    private final NoticeRepository noticeRepository;

    /*
    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }
     */

    /*
    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {
        List<NoticeModel> noticeList = new ArrayList<>();

        noticeList.add(NoticeModel.builder()
                .id(1)
                .title("공지사항 입니다")
                .content("공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 03, 11, 11))
                .build());

        noticeList.add(NoticeModel.builder()
                .id(2)
                .title("두번째 공지사항 입니다")
                .content("두번째 공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 04, 11, 11))
                .build());

        return noticeList;
    }
     */

    @GetMapping("/api/notice")
    public List<NoticeInput> notice() {

        List<NoticeInput> noticeList = new ArrayList<>();

        return noticeList;
    }

    @GetMapping("/api/notice/count")
    public int noticeCount() {

        return 10;
    }

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestParam String title, @RequestParam String content) {
        return NoticeModel.builder()
                .id(1)
                .title(title)
                .content(content)
                .regDate(LocalDateTime.of(2021, 07,04,11,43))
                .build();
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(NoticeModel noticeModel) {
        noticeModel.setId(2);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestBody NoticeModel noticeModel) {
        noticeModel.setId(3);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {
        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .build();

        noticeRepository.save(notice);

        return notice;
    }
     */

    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {

        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .hits(0)
                .likes(0)
                .build();

        Notice resultNotice = noticeRepository.save(notice);

        return resultNotice;
    }

    @GetMapping("/api/notice/{id}")
    public Notice notice(@PathVariable Long id) {
        // .findById()는 반환타입이 Optional이다, 즉 null을 반환할 수도 있다
        // 그렇기에 Notice를 Optional로 감싸준다
        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            return notice.get();
        }

        return null;
    }

    /*
    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id,@RequestBody NoticeInput noticeInput) {

        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            notice.get().setTitle(noticeInput.getTitle());
            notice.get().setContent(noticeInput.getContent());
            notice.get().setUpdateDate(LocalDateTime.now());
            noticeRepository.save(notice.get());
        }
    }
     */


    @ExceptionHandler(NoticeNotFoundException.class)
    public ResponseEntity<String> handlerNoticeNotFoundException(NoticeNotFoundException exception) {
            return new ResponseEntity<>(exception.getMessage(), HttpStatus.BAD_REQUEST);
    }

    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id, @RequestBody NoticeInput noticeInput) {

        /*
        Optional<Notice> notice = noticeRepository.findById(id);
        if(!notice.isPresent()) {
            // 예외 발생
            throw new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다.");
        }
        */


        // 이 경우 Optional이 아니기에 객체를 바로 받아 사용가능하다
        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));


        // 정상로직 수행
        notice.setTitle(noticeInput.getTitle());
        notice.setContent(noticeInput.getContent());
        notice.setUpdateDate(LocalDateTime.now());
        noticeRepository.save(notice);

    }

    @PatchMapping("/api/notice/{id}/hits")
    public void noticeHits(@PathVariable Long id) {
        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        notice.setHits(notice.getHits() + 1);

        noticeRepository.save(notice);
    }

    /*
    @DeleteMapping("/api/notice/{id}")
    public void deleteNotice(@PathVariable Long id) {

        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        noticeRepository.delete(notice);

    }
     */

    @ExceptionHandler(AlreadyDeletedException.class)
    public ResponseEntity<String> handlerAlreadyDeletedException(AlreadyDeletedException exception) {
        return new ResponseEntity<>(exception.getMessage(), HttpStatus.OK);
    }

    @DeleteMapping("/api/notice/{id}")
    public void deleteNotice(@PathVariable Long id) {

        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        if(notice.isDeleted()) {
            throw new AlreadyDeletedException("이미 삭제된 공지사항의 글입니다.");
        }

        notice.setDeleted(true);
        notice.setDeletedDate(LocalDateTime.now());

        noticeRepository.save(notice);
    }

    @DeleteMapping("/api/notice")
    public void deleteNoticeList(@RequestBody NoticeDeleteInput noticeDeleteInput) {

        List<Notice> noticeList = noticeRepository.findByIdIn(noticeDeleteInput.getIdList())
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        noticeList.stream().forEach(e -> {
            e.setDeleted(true);
            e.setDeletedDate(LocalDateTime.now());
        });

        noticeRepository.saveAll(noticeList);
    }
}



02. 공지사항의 모든 글을 삭제하기 위한 API를 만들어 보자

  • [조건]

    • REST API 형식으로 구현
    • HTTP METHOD는 DELETE
    • 요청 주소는 "/api/notice/all"
  • notice.controller / ApiNoticeController.java

package com.example.community.notice.controller;

import com.example.community.notice.entity.Notice;
import com.example.community.notice.exception.AlreadyDeletedException;
import com.example.community.notice.exception.NoticeNotFoundException;
import com.example.community.notice.model.NoticeDeleteInput;
import com.example.community.notice.model.NoticeInput;
import com.example.community.notice.repository.NoticeRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@RestController
@RequiredArgsConstructor
public class ApiNoticeController {

    // Repository를 주입받는다. @RequiredArgsConstructor를 활용 해 생성자에서 주입을 받는다
    private final NoticeRepository noticeRepository;

    /*
    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }
     */

    /*
    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {
        List<NoticeModel> noticeList = new ArrayList<>();

        noticeList.add(NoticeModel.builder()
                .id(1)
                .title("공지사항 입니다")
                .content("공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 03, 11, 11))
                .build());

        noticeList.add(NoticeModel.builder()
                .id(2)
                .title("두번째 공지사항 입니다")
                .content("두번째 공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 04, 11, 11))
                .build());

        return noticeList;
    }
     */

    @GetMapping("/api/notice")
    public List<NoticeInput> notice() {

        List<NoticeInput> noticeList = new ArrayList<>();

        return noticeList;
    }

    @GetMapping("/api/notice/count")
    public int noticeCount() {

        return 10;
    }

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestParam String title, @RequestParam String content) {
        return NoticeModel.builder()
                .id(1)
                .title(title)
                .content(content)
                .regDate(LocalDateTime.of(2021, 07,04,11,43))
                .build();
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(NoticeModel noticeModel) {
        noticeModel.setId(2);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestBody NoticeModel noticeModel) {
        noticeModel.setId(3);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {
        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .build();

        noticeRepository.save(notice);

        return notice;
    }
     */

    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {

        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .hits(0)
                .likes(0)
                .build();

        Notice resultNotice = noticeRepository.save(notice);

        return resultNotice;
    }

    @GetMapping("/api/notice/{id}")
    public Notice notice(@PathVariable Long id) {
        // .findById()는 반환타입이 Optional이다, 즉 null을 반환할 수도 있다
        // 그렇기에 Notice를 Optional로 감싸준다
        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            return notice.get();
        }

        return null;
    }

    /*
    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id,@RequestBody NoticeInput noticeInput) {

        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            notice.get().setTitle(noticeInput.getTitle());
            notice.get().setContent(noticeInput.getContent());
            notice.get().setUpdateDate(LocalDateTime.now());
            noticeRepository.save(notice.get());
        }
    }
     */


    @ExceptionHandler(NoticeNotFoundException.class)
    public ResponseEntity<String> handlerNoticeNotFoundException(NoticeNotFoundException exception) {
            return new ResponseEntity<>(exception.getMessage(), HttpStatus.BAD_REQUEST);
    }

    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id, @RequestBody NoticeInput noticeInput) {

        /*
        Optional<Notice> notice = noticeRepository.findById(id);
        if(!notice.isPresent()) {
            // 예외 발생
            throw new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다.");
        }
        */


        // 이 경우 Optional이 아니기에 객체를 바로 받아 사용가능하다
        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));


        // 정상로직 수행
        notice.setTitle(noticeInput.getTitle());
        notice.setContent(noticeInput.getContent());
        notice.setUpdateDate(LocalDateTime.now());
        noticeRepository.save(notice);

    }

    @PatchMapping("/api/notice/{id}/hits")
    public void noticeHits(@PathVariable Long id) {
        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        notice.setHits(notice.getHits() + 1);

        noticeRepository.save(notice);
    }

    /*
    @DeleteMapping("/api/notice/{id}")
    public void deleteNotice(@PathVariable Long id) {

        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        noticeRepository.delete(notice);

    }
     */

    @ExceptionHandler(AlreadyDeletedException.class)
    public ResponseEntity<String> handlerAlreadyDeletedException(AlreadyDeletedException exception) {
        return new ResponseEntity<>(exception.getMessage(), HttpStatus.OK);
    }

    @DeleteMapping("/api/notice/{id}")
    public void deleteNotice(@PathVariable Long id) {

        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        if(notice.isDeleted()) {
            throw new AlreadyDeletedException("이미 삭제된 공지사항의 글입니다.");
        }

        notice.setDeleted(true);
        notice.setDeletedDate(LocalDateTime.now());

        noticeRepository.save(notice);
    }

    @DeleteMapping("/api/notice")
    public void deleteNoticeList(@RequestBody NoticeDeleteInput noticeDeleteInput) {

        List<Notice> noticeList = noticeRepository.findByIdIn(noticeDeleteInput.getIdList())
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        noticeList.stream().forEach(e -> {
            e.setDeleted(true);
            e.setDeletedDate(LocalDateTime.now());
        });

        noticeRepository.saveAll(noticeList);
    }

    @DeleteMapping("/api/notice/all")
    public void deleteNoticeAll() {
        List<Notice> noticeList = noticeRepository.findAll();

        if(!noticeList.isEmpty()) {
            noticeList.stream().forEach(e -> {
                if(!e.isDeleted()) {
                    e.setDeleted(true);
                    e.setDeletedDate(LocalDateTime.now());
                }
            });
        }

        noticeRepository.saveAll(noticeList);
    }
}



6. 게시판 추가 기능

01. 글을 작성할 때 제목고 내용을 받아서 저장하는 API를 만들어 보자

  • Class : ResponseError
  • [조건]
    • METHOD : POST
    • DTO를 통한 파라미터를 형태로 받음
    • 제목과 내용은 필수 입력 조건임(입력되지 않은 경우 400리턴)
    • 예외 발생시 각각의 에러를 취합하여 콜렉션 형태로 리턴

-1. Controller에 작성

  • notice.controller / ApiNoticeController.java
package com.example.community.notice.controller;

import com.example.community.notice.entity.Notice;
import com.example.community.notice.exception.AlreadyDeletedException;
import com.example.community.notice.exception.NoticeNotFoundException;
import com.example.community.notice.model.NoticeDeleteInput;
import com.example.community.notice.model.NoticeInput;
import com.example.community.notice.repository.NoticeRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@RestController
@RequiredArgsConstructor
public class ApiNoticeController {

    // Repository를 주입받는다. @RequiredArgsConstructor를 활용 해 생성자에서 주입을 받는다
    private final NoticeRepository noticeRepository;

    /*
    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }
     */

    /*
    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {
        List<NoticeModel> noticeList = new ArrayList<>();

        noticeList.add(NoticeModel.builder()
                .id(1)
                .title("공지사항 입니다")
                .content("공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 03, 11, 11))
                .build());

        noticeList.add(NoticeModel.builder()
                .id(2)
                .title("두번째 공지사항 입니다")
                .content("두번째 공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 04, 11, 11))
                .build());

        return noticeList;
    }
     */

    @GetMapping("/api/notice")
    public List<NoticeInput> notice() {

        List<NoticeInput> noticeList = new ArrayList<>();

        return noticeList;
    }

    @GetMapping("/api/notice/count")
    public int noticeCount() {

        return 10;
    }

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestParam String title, @RequestParam String content) {
        return NoticeModel.builder()
                .id(1)
                .title(title)
                .content(content)
                .regDate(LocalDateTime.of(2021, 07,04,11,43))
                .build();
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(NoticeModel noticeModel) {
        noticeModel.setId(2);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestBody NoticeModel noticeModel) {
        noticeModel.setId(3);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {
        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .build();

        noticeRepository.save(notice);

        return notice;
    }
     */

    /*
    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {

        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .hits(0)
                .likes(0)
                .build();

        Notice resultNotice = noticeRepository.save(notice);

        return resultNotice;
    }
    */

    @GetMapping("/api/notice/{id}")
    public Notice notice(@PathVariable Long id) {
        // .findById()는 반환타입이 Optional이다, 즉 null을 반환할 수도 있다
        // 그렇기에 Notice를 Optional로 감싸준다
        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            return notice.get();
        }

        return null;
    }

    /*
    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id,@RequestBody NoticeInput noticeInput) {

        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            notice.get().setTitle(noticeInput.getTitle());
            notice.get().setContent(noticeInput.getContent());
            notice.get().setUpdateDate(LocalDateTime.now());
            noticeRepository.save(notice.get());
        }
    }
     */


    @ExceptionHandler(NoticeNotFoundException.class)
    public ResponseEntity<String> handlerNoticeNotFoundException(NoticeNotFoundException exception) {
            return new ResponseEntity<>(exception.getMessage(), HttpStatus.BAD_REQUEST);
    }

    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id, @RequestBody NoticeInput noticeInput) {

        /*
        Optional<Notice> notice = noticeRepository.findById(id);
        if(!notice.isPresent()) {
            // 예외 발생
            throw new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다.");
        }
        */


        // 이 경우 Optional이 아니기에 객체를 바로 받아 사용가능하다
        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));


        // 정상로직 수행
        notice.setTitle(noticeInput.getTitle());
        notice.setContent(noticeInput.getContent());
        notice.setUpdateDate(LocalDateTime.now());
        noticeRepository.save(notice);

    }

    @PatchMapping("/api/notice/{id}/hits")
    public void noticeHits(@PathVariable Long id) {
        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        notice.setHits(notice.getHits() + 1);

        noticeRepository.save(notice);
    }

    /*
    @DeleteMapping("/api/notice/{id}")
    public void deleteNotice(@PathVariable Long id) {

        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        noticeRepository.delete(notice);

    }
     */

    @ExceptionHandler(AlreadyDeletedException.class)
    public ResponseEntity<String> handlerAlreadyDeletedException(AlreadyDeletedException exception) {
        return new ResponseEntity<>(exception.getMessage(), HttpStatus.OK);
    }

    @DeleteMapping("/api/notice/{id}")
    public void deleteNotice(@PathVariable Long id) {

        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        if(notice.isDeleted()) {
            throw new AlreadyDeletedException("이미 삭제된 공지사항의 글입니다.");
        }

        notice.setDeleted(true);
        notice.setDeletedDate(LocalDateTime.now());

        noticeRepository.save(notice);
    }

    @DeleteMapping("/api/notice")
    public void deleteNoticeList(@RequestBody NoticeDeleteInput noticeDeleteInput) {

        List<Notice> noticeList = noticeRepository.findByIdIn(noticeDeleteInput.getIdList())
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        noticeList.stream().forEach(e -> {
            e.setDeleted(true);
            e.setDeletedDate(LocalDateTime.now());
        });

        noticeRepository.saveAll(noticeList);
    }

    @DeleteMapping("/api/notice/all")
    public void deleteNoticeAll() {
        List<Notice> noticeList = noticeRepository.findAll();

        if(!noticeList.isEmpty()) {
            noticeList.stream().forEach(e -> {
                if(!e.isDeleted()) {
                    e.setDeleted(true);
                    e.setDeletedDate(LocalDateTime.now());
                }
            });
        }

        noticeRepository.saveAll(noticeList);
    }

    @PostMapping("/api/notice")
    public ResponseEntity<Object> addNotice(@RequestBody NoticeInput noticeInput) {

        if(noticeInput.getTitle() == null ||
            noticeInput.getTitle().length() < 1 ||
            noticeInput.getContent() == null ||
            noticeInput.getContent().length() < 1) {
            return new ResponseEntity<>("입력값이 정확하지 않습니다", HttpStatus.BAD_REQUEST);
        }

        // 정상 로직
        noticeRepository.save(Notice.builder()
        .title(noticeInput.getTitle())
        .content(noticeInput.getContent())
        .hits(0)
        .likes(0)
        .regDate(LocalDateTime.now())
        .build());

        return ResponseEntity.ok().build();
    }
}




-2. Validation 적용

  • Validation Library 추가

    • build.gradle
    • implementation("org.springframework.boot:spring-boot-starter-validation")
  • notice.model / NoticeInput.java

    • @NotBlank
      • 공백 및 빈값 허용 안함
package com.example.community.notice.model;

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

import javax.validation.constraints.NotBlank;


// 입력 받는 객체에는 입력 받지 않는 id와 regdate 필드는 필요 없다
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class NoticeInput {

    @NotBlank(message = "제목은 필수 항목 입니다.")
    private String title;

    @NotBlank(message = "내용은 필수 항목 입니다.")
    private String content;

}
  • notice.controller / ApiNoticeController.java
package com.example.community.notice.controller;

import com.example.community.notice.entity.Notice;
import com.example.community.notice.exception.AlreadyDeletedException;
import com.example.community.notice.exception.NoticeNotFoundException;
import com.example.community.notice.model.NoticeDeleteInput;
import com.example.community.notice.model.NoticeInput;
import com.example.community.notice.model.ResponseError;
import com.example.community.notice.repository.NoticeRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@RestController
@RequiredArgsConstructor
public class ApiNoticeController {

    // Repository를 주입받는다. @RequiredArgsConstructor를 활용 해 생성자에서 주입을 받는다
    private final NoticeRepository noticeRepository;

    /*
    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }
     */

    /*
    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {
        List<NoticeModel> noticeList = new ArrayList<>();

        noticeList.add(NoticeModel.builder()
                .id(1)
                .title("공지사항 입니다")
                .content("공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 03, 11, 11))
                .build());

        noticeList.add(NoticeModel.builder()
                .id(2)
                .title("두번째 공지사항 입니다")
                .content("두번째 공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 04, 11, 11))
                .build());

        return noticeList;
    }
     */

    @GetMapping("/api/notice")
    public List<NoticeInput> notice() {

        List<NoticeInput> noticeList = new ArrayList<>();

        return noticeList;
    }

    @GetMapping("/api/notice/count")
    public int noticeCount() {

        return 10;
    }

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestParam String title, @RequestParam String content) {
        return NoticeModel.builder()
                .id(1)
                .title(title)
                .content(content)
                .regDate(LocalDateTime.of(2021, 07,04,11,43))
                .build();
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(NoticeModel noticeModel) {
        noticeModel.setId(2);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestBody NoticeModel noticeModel) {
        noticeModel.setId(3);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {
        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .build();

        noticeRepository.save(notice);

        return notice;
    }
     */

    /*
    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {

        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .hits(0)
                .likes(0)
                .build();

        Notice resultNotice = noticeRepository.save(notice);

        return resultNotice;
    }
    */

    @GetMapping("/api/notice/{id}")
    public Notice notice(@PathVariable Long id) {
        // .findById()는 반환타입이 Optional이다, 즉 null을 반환할 수도 있다
        // 그렇기에 Notice를 Optional로 감싸준다
        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            return notice.get();
        }

        return null;
    }

    /*
    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id,@RequestBody NoticeInput noticeInput) {

        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            notice.get().setTitle(noticeInput.getTitle());
            notice.get().setContent(noticeInput.getContent());
            notice.get().setUpdateDate(LocalDateTime.now());
            noticeRepository.save(notice.get());
        }
    }
     */


    @ExceptionHandler(NoticeNotFoundException.class)
    public ResponseEntity<String> handlerNoticeNotFoundException(NoticeNotFoundException exception) {
            return new ResponseEntity<>(exception.getMessage(), HttpStatus.BAD_REQUEST);
    }

    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id, @RequestBody NoticeInput noticeInput) {

        /*
        Optional<Notice> notice = noticeRepository.findById(id);
        if(!notice.isPresent()) {
            // 예외 발생
            throw new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다.");
        }
        */


        // 이 경우 Optional이 아니기에 객체를 바로 받아 사용가능하다
        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));


        // 정상로직 수행
        notice.setTitle(noticeInput.getTitle());
        notice.setContent(noticeInput.getContent());
        notice.setUpdateDate(LocalDateTime.now());
        noticeRepository.save(notice);

    }

    @PatchMapping("/api/notice/{id}/hits")
    public void noticeHits(@PathVariable Long id) {
        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        notice.setHits(notice.getHits() + 1);

        noticeRepository.save(notice);
    }

    /*
    @DeleteMapping("/api/notice/{id}")
    public void deleteNotice(@PathVariable Long id) {

        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        noticeRepository.delete(notice);

    }
     */

    @ExceptionHandler(AlreadyDeletedException.class)
    public ResponseEntity<String> handlerAlreadyDeletedException(AlreadyDeletedException exception) {
        return new ResponseEntity<>(exception.getMessage(), HttpStatus.OK);
    }

    @DeleteMapping("/api/notice/{id}")
    public void deleteNotice(@PathVariable Long id) {

        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        if(notice.isDeleted()) {
            throw new AlreadyDeletedException("이미 삭제된 공지사항의 글입니다.");
        }

        notice.setDeleted(true);
        notice.setDeletedDate(LocalDateTime.now());

        noticeRepository.save(notice);
    }

    @DeleteMapping("/api/notice")
    public void deleteNoticeList(@RequestBody NoticeDeleteInput noticeDeleteInput) {

        List<Notice> noticeList = noticeRepository.findByIdIn(noticeDeleteInput.getIdList())
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        noticeList.stream().forEach(e -> {
            e.setDeleted(true);
            e.setDeletedDate(LocalDateTime.now());
        });

        noticeRepository.saveAll(noticeList);
    }

    @DeleteMapping("/api/notice/all")
    public void deleteNoticeAll() {
        List<Notice> noticeList = noticeRepository.findAll();

        if(!noticeList.isEmpty()) {
            noticeList.stream().forEach(e -> {
                if(!e.isDeleted()) {
                    e.setDeleted(true);
                    e.setDeletedDate(LocalDateTime.now());
                }
            });
        }

        noticeRepository.saveAll(noticeList);
    }

    @PostMapping("/api/notice")
    public ResponseEntity<Object> addNotice(@RequestBody @Valid NoticeInput noticeInput
                                        , Errors errors) {

        if(errors.hasErrors()) {
            return new ResponseEntity<>(errors.getAllErrors(), HttpStatus.BAD_REQUEST);
        }
        // 정상 로직
        noticeRepository.save(Notice.builder()
        .title(noticeInput.getTitle())
        .content(noticeInput.getContent())
        .hits(0)
        .likes(0)
        .regDate(LocalDateTime.now())
        .build());

        return ResponseEntity.ok().build();
    }
}




-3. Error 객체 만들기

  • notice.model / ReponseError.java
package com.example.community.notice.model;

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

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ResponseError {

    private String field;
    private String message;
}
  • notice.controller / ApiNoticeController.java
package com.example.community.notice.controller;

import com.example.community.notice.entity.Notice;
import com.example.community.notice.exception.AlreadyDeletedException;
import com.example.community.notice.exception.NoticeNotFoundException;
import com.example.community.notice.model.NoticeDeleteInput;
import com.example.community.notice.model.NoticeInput;
import com.example.community.notice.model.ResponseError;
import com.example.community.notice.repository.NoticeRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@RestController
@RequiredArgsConstructor
public class ApiNoticeController {

    // Repository를 주입받는다. @RequiredArgsConstructor를 활용 해 생성자에서 주입을 받는다
    private final NoticeRepository noticeRepository;

    /*
    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }
     */

    /*
    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {
        List<NoticeModel> noticeList = new ArrayList<>();

        noticeList.add(NoticeModel.builder()
                .id(1)
                .title("공지사항 입니다")
                .content("공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 03, 11, 11))
                .build());

        noticeList.add(NoticeModel.builder()
                .id(2)
                .title("두번째 공지사항 입니다")
                .content("두번째 공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 04, 11, 11))
                .build());

        return noticeList;
    }
     */

    @GetMapping("/api/notice")
    public List<NoticeInput> notice() {

        List<NoticeInput> noticeList = new ArrayList<>();

        return noticeList;
    }

    @GetMapping("/api/notice/count")
    public int noticeCount() {

        return 10;
    }

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestParam String title, @RequestParam String content) {
        return NoticeModel.builder()
                .id(1)
                .title(title)
                .content(content)
                .regDate(LocalDateTime.of(2021, 07,04,11,43))
                .build();
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(NoticeModel noticeModel) {
        noticeModel.setId(2);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestBody NoticeModel noticeModel) {
        noticeModel.setId(3);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {
        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .build();

        noticeRepository.save(notice);

        return notice;
    }
     */

    /*
    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {

        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .hits(0)
                .likes(0)
                .build();

        Notice resultNotice = noticeRepository.save(notice);

        return resultNotice;
    }
    */

    @GetMapping("/api/notice/{id}")
    public Notice notice(@PathVariable Long id) {
        // .findById()는 반환타입이 Optional이다, 즉 null을 반환할 수도 있다
        // 그렇기에 Notice를 Optional로 감싸준다
        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            return notice.get();
        }

        return null;
    }

    /*
    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id,@RequestBody NoticeInput noticeInput) {

        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            notice.get().setTitle(noticeInput.getTitle());
            notice.get().setContent(noticeInput.getContent());
            notice.get().setUpdateDate(LocalDateTime.now());
            noticeRepository.save(notice.get());
        }
    }
     */


    @ExceptionHandler(NoticeNotFoundException.class)
    public ResponseEntity<String> handlerNoticeNotFoundException(NoticeNotFoundException exception) {
            return new ResponseEntity<>(exception.getMessage(), HttpStatus.BAD_REQUEST);
    }

    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id, @RequestBody NoticeInput noticeInput) {

        /*
        Optional<Notice> notice = noticeRepository.findById(id);
        if(!notice.isPresent()) {
            // 예외 발생
            throw new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다.");
        }
        */


        // 이 경우 Optional이 아니기에 객체를 바로 받아 사용가능하다
        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));


        // 정상로직 수행
        notice.setTitle(noticeInput.getTitle());
        notice.setContent(noticeInput.getContent());
        notice.setUpdateDate(LocalDateTime.now());
        noticeRepository.save(notice);

    }

    @PatchMapping("/api/notice/{id}/hits")
    public void noticeHits(@PathVariable Long id) {
        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        notice.setHits(notice.getHits() + 1);

        noticeRepository.save(notice);
    }

    /*
    @DeleteMapping("/api/notice/{id}")
    public void deleteNotice(@PathVariable Long id) {

        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        noticeRepository.delete(notice);

    }
     */

    @ExceptionHandler(AlreadyDeletedException.class)
    public ResponseEntity<String> handlerAlreadyDeletedException(AlreadyDeletedException exception) {
        return new ResponseEntity<>(exception.getMessage(), HttpStatus.OK);
    }

    @DeleteMapping("/api/notice/{id}")
    public void deleteNotice(@PathVariable Long id) {

        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        if(notice.isDeleted()) {
            throw new AlreadyDeletedException("이미 삭제된 공지사항의 글입니다.");
        }

        notice.setDeleted(true);
        notice.setDeletedDate(LocalDateTime.now());

        noticeRepository.save(notice);
    }

    @DeleteMapping("/api/notice")
    public void deleteNoticeList(@RequestBody NoticeDeleteInput noticeDeleteInput) {

        List<Notice> noticeList = noticeRepository.findByIdIn(noticeDeleteInput.getIdList())
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        noticeList.stream().forEach(e -> {
            e.setDeleted(true);
            e.setDeletedDate(LocalDateTime.now());
        });

        noticeRepository.saveAll(noticeList);
    }

    @DeleteMapping("/api/notice/all")
    public void deleteNoticeAll() {
        List<Notice> noticeList = noticeRepository.findAll();

        if(!noticeList.isEmpty()) {
            noticeList.stream().forEach(e -> {
                if(!e.isDeleted()) {
                    e.setDeleted(true);
                    e.setDeletedDate(LocalDateTime.now());
                }
            });
        }

        noticeRepository.saveAll(noticeList);
    }

    @PostMapping("/api/notice")
    public ResponseEntity<Object> addNotice(@RequestBody @Valid NoticeInput noticeInput
                                        , Errors errors) {

        if(errors.hasErrors()) {

            List<ResponseError> responseErrors = new ArrayList<>();

            errors.getAllErrors().stream().forEach(e -> {
                ResponseError responseError = new ResponseError();
                responseError.setField(((FieldError)e).getField());
                responseError.setMessage(e.getDefaultMessage());
                responseErrors.add(responseError);
            });

            return new ResponseEntity<>(responseErrors, HttpStatus.BAD_REQUEST);
        }
        // 정상 로직
        noticeRepository.save(Notice.builder()
        .title(noticeInput.getTitle())
        .content(noticeInput.getContent())
        .hits(0)
        .likes(0)
        .regDate(LocalDateTime.now())
        .build());

        return ResponseEntity.ok().build();
    }
}






-4. ResponseError 리펙토링

  • notice.model / ResponseError.java
package com.example.community.notice.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.validation.FieldError;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ResponseError {

    private String field;
    private String message;

    public static ResponseError of(FieldError e) {
        return ResponseError.builder()
                .field((e).getField())
                .message(e.getDefaultMessage())
                .build();
    }
}
  • notice.controller / ApiNoticeController.java
package com.example.community.notice.controller;

import com.example.community.notice.entity.Notice;
import com.example.community.notice.exception.AlreadyDeletedException;
import com.example.community.notice.exception.NoticeNotFoundException;
import com.example.community.notice.model.NoticeDeleteInput;
import com.example.community.notice.model.NoticeInput;
import com.example.community.notice.model.ResponseError;
import com.example.community.notice.repository.NoticeRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@RestController
@RequiredArgsConstructor
public class ApiNoticeController {

    // Repository를 주입받는다. @RequiredArgsConstructor를 활용 해 생성자에서 주입을 받는다
    private final NoticeRepository noticeRepository;

    /*
    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }
     */

    /*
    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {
        List<NoticeModel> noticeList = new ArrayList<>();

        noticeList.add(NoticeModel.builder()
                .id(1)
                .title("공지사항 입니다")
                .content("공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 03, 11, 11))
                .build());

        noticeList.add(NoticeModel.builder()
                .id(2)
                .title("두번째 공지사항 입니다")
                .content("두번째 공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 04, 11, 11))
                .build());

        return noticeList;
    }
     */

    @GetMapping("/api/notice")
    public List<NoticeInput> notice() {

        List<NoticeInput> noticeList = new ArrayList<>();

        return noticeList;
    }

    @GetMapping("/api/notice/count")
    public int noticeCount() {

        return 10;
    }

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestParam String title, @RequestParam String content) {
        return NoticeModel.builder()
                .id(1)
                .title(title)
                .content(content)
                .regDate(LocalDateTime.of(2021, 07,04,11,43))
                .build();
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(NoticeModel noticeModel) {
        noticeModel.setId(2);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestBody NoticeModel noticeModel) {
        noticeModel.setId(3);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {
        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .build();

        noticeRepository.save(notice);

        return notice;
    }
     */

    /*
    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {

        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .hits(0)
                .likes(0)
                .build();

        Notice resultNotice = noticeRepository.save(notice);

        return resultNotice;
    }
    */

    @GetMapping("/api/notice/{id}")
    public Notice notice(@PathVariable Long id) {
        // .findById()는 반환타입이 Optional이다, 즉 null을 반환할 수도 있다
        // 그렇기에 Notice를 Optional로 감싸준다
        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            return notice.get();
        }

        return null;
    }

    /*
    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id,@RequestBody NoticeInput noticeInput) {

        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            notice.get().setTitle(noticeInput.getTitle());
            notice.get().setContent(noticeInput.getContent());
            notice.get().setUpdateDate(LocalDateTime.now());
            noticeRepository.save(notice.get());
        }
    }
     */


    @ExceptionHandler(NoticeNotFoundException.class)
    public ResponseEntity<String> handlerNoticeNotFoundException(NoticeNotFoundException exception) {
            return new ResponseEntity<>(exception.getMessage(), HttpStatus.BAD_REQUEST);
    }

    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id, @RequestBody NoticeInput noticeInput) {

        /*
        Optional<Notice> notice = noticeRepository.findById(id);
        if(!notice.isPresent()) {
            // 예외 발생
            throw new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다.");
        }
        */


        // 이 경우 Optional이 아니기에 객체를 바로 받아 사용가능하다
        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));


        // 정상로직 수행
        notice.setTitle(noticeInput.getTitle());
        notice.setContent(noticeInput.getContent());
        notice.setUpdateDate(LocalDateTime.now());
        noticeRepository.save(notice);

    }

    @PatchMapping("/api/notice/{id}/hits")
    public void noticeHits(@PathVariable Long id) {
        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        notice.setHits(notice.getHits() + 1);

        noticeRepository.save(notice);
    }

    /*
    @DeleteMapping("/api/notice/{id}")
    public void deleteNotice(@PathVariable Long id) {

        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        noticeRepository.delete(notice);

    }
     */

    @ExceptionHandler(AlreadyDeletedException.class)
    public ResponseEntity<String> handlerAlreadyDeletedException(AlreadyDeletedException exception) {
        return new ResponseEntity<>(exception.getMessage(), HttpStatus.OK);
    }

    @DeleteMapping("/api/notice/{id}")
    public void deleteNotice(@PathVariable Long id) {

        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        if(notice.isDeleted()) {
            throw new AlreadyDeletedException("이미 삭제된 공지사항의 글입니다.");
        }

        notice.setDeleted(true);
        notice.setDeletedDate(LocalDateTime.now());

        noticeRepository.save(notice);
    }

    @DeleteMapping("/api/notice")
    public void deleteNoticeList(@RequestBody NoticeDeleteInput noticeDeleteInput) {

        List<Notice> noticeList = noticeRepository.findByIdIn(noticeDeleteInput.getIdList())
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        noticeList.stream().forEach(e -> {
            e.setDeleted(true);
            e.setDeletedDate(LocalDateTime.now());
        });

        noticeRepository.saveAll(noticeList);
    }

    @DeleteMapping("/api/notice/all")
    public void deleteNoticeAll() {
        List<Notice> noticeList = noticeRepository.findAll();

        if(!noticeList.isEmpty()) {
            noticeList.stream().forEach(e -> {
                if(!e.isDeleted()) {
                    e.setDeleted(true);
                    e.setDeletedDate(LocalDateTime.now());
                }
            });
        }

        noticeRepository.saveAll(noticeList);
    }

    @PostMapping("/api/notice")
    public ResponseEntity<Object> addNotice(@RequestBody @Valid NoticeInput noticeInput
                                        , Errors errors) {

        if(errors.hasErrors()) {

            List<ResponseError> responseErrors = new ArrayList<>();

            errors.getAllErrors().stream().forEach(e -> {
                responseErrors.add(ResponseError.of((FieldError)e));
            });

            return new ResponseEntity<>(responseErrors, HttpStatus.BAD_REQUEST);
        }
        // 정상 로직
        noticeRepository.save(Notice.builder()
        .title(noticeInput.getTitle())
        .content(noticeInput.getContent())
        .hits(0)
        .likes(0)
        .regDate(LocalDateTime.now())
        .build());

        return ResponseEntity.ok().build();
    }
}

02. 글을 작성할 때 제목과 내용을 받아서 저장하는 API를 만들어보자(글자 길이 제한)

  • [조건]
    • DTO를 통한 파라미터를 형태로 받음
    • 제목과 내용은 필수 입력 조건임(입력되지 않은 경우 400리턴)
    • 제목의 경우 10자 이상 100자 이하로 입력
    • 내용의 경우 50자 이상 1000자 이하로 입력
    • 예외 발생시 각각의 에러를 취합하여 콜력센 형태로 리턴
  • notice.model / NoticeInput.java
    • @Size()
      • 입력받은 값의 길이
package com.example.community.notice.model;

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

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;


// 입력 받는 객체에는 입력 받지 않는 id와 regdate 필드는 필요 없다
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class NoticeInput {

    @NotBlank(message = "제목은 필수 항목 입니다.")
    @Size(min = 10, max = 100, message = "제목은 10자 이상 100자 이하로 입력해주세요.")
    private String title;

    @NotBlank(message = "내용은 필수 항목 입니다.")
    @Size(min = 50, max = 1000, message = "내용은 50자 이상 1000자 이하로 입력해주세요.")
    private String content;

}


03. 데이터베이스에서 공지사항 목록 중에서 파라미터로 전달된 개수 만큼 최근 공지사항을 리턴하는 API를 만들어 보자

  • [예]
    • 최근 5개
  • notice.controller / ApiNoticeController.java
package com.example.community.notice.controller;

import com.example.community.notice.entity.Notice;
import com.example.community.notice.exception.AlreadyDeletedException;
import com.example.community.notice.exception.NoticeNotFoundException;
import com.example.community.notice.model.NoticeDeleteInput;
import com.example.community.notice.model.NoticeInput;
import com.example.community.notice.model.ResponseError;
import com.example.community.notice.repository.NoticeRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@RestController
@RequiredArgsConstructor
public class ApiNoticeController {

    // Repository를 주입받는다. @RequiredArgsConstructor를 활용 해 생성자에서 주입을 받는다
    private final NoticeRepository noticeRepository;

    /*
    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }
     */

    /*
    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {
        List<NoticeModel> noticeList = new ArrayList<>();

        noticeList.add(NoticeModel.builder()
                .id(1)
                .title("공지사항 입니다")
                .content("공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 03, 11, 11))
                .build());

        noticeList.add(NoticeModel.builder()
                .id(2)
                .title("두번째 공지사항 입니다")
                .content("두번째 공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 04, 11, 11))
                .build());

        return noticeList;
    }
     */

    @GetMapping("/api/notice")
    public List<NoticeInput> notice() {

        List<NoticeInput> noticeList = new ArrayList<>();

        return noticeList;
    }

    @GetMapping("/api/notice/count")
    public int noticeCount() {

        return 10;
    }

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestParam String title, @RequestParam String content) {
        return NoticeModel.builder()
                .id(1)
                .title(title)
                .content(content)
                .regDate(LocalDateTime.of(2021, 07,04,11,43))
                .build();
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(NoticeModel noticeModel) {
        noticeModel.setId(2);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestBody NoticeModel noticeModel) {
        noticeModel.setId(3);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {
        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .build();

        noticeRepository.save(notice);

        return notice;
    }
     */

    /*
    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {

        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .hits(0)
                .likes(0)
                .build();

        Notice resultNotice = noticeRepository.save(notice);

        return resultNotice;
    }
    */

    @GetMapping("/api/notice/{id}")
    public Notice notice(@PathVariable Long id) {
        // .findById()는 반환타입이 Optional이다, 즉 null을 반환할 수도 있다
        // 그렇기에 Notice를 Optional로 감싸준다
        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            return notice.get();
        }

        return null;
    }

    /*
    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id,@RequestBody NoticeInput noticeInput) {

        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            notice.get().setTitle(noticeInput.getTitle());
            notice.get().setContent(noticeInput.getContent());
            notice.get().setUpdateDate(LocalDateTime.now());
            noticeRepository.save(notice.get());
        }
    }
     */


    @ExceptionHandler(NoticeNotFoundException.class)
    public ResponseEntity<String> handlerNoticeNotFoundException(NoticeNotFoundException exception) {
            return new ResponseEntity<>(exception.getMessage(), HttpStatus.BAD_REQUEST);
    }

    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id, @RequestBody NoticeInput noticeInput) {

        /*
        Optional<Notice> notice = noticeRepository.findById(id);
        if(!notice.isPresent()) {
            // 예외 발생
            throw new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다.");
        }
        */


        // 이 경우 Optional이 아니기에 객체를 바로 받아 사용가능하다
        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));


        // 정상로직 수행
        notice.setTitle(noticeInput.getTitle());
        notice.setContent(noticeInput.getContent());
        notice.setUpdateDate(LocalDateTime.now());
        noticeRepository.save(notice);

    }

    @PatchMapping("/api/notice/{id}/hits")
    public void noticeHits(@PathVariable Long id) {
        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        notice.setHits(notice.getHits() + 1);

        noticeRepository.save(notice);
    }

    /*
    @DeleteMapping("/api/notice/{id}")
    public void deleteNotice(@PathVariable Long id) {

        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        noticeRepository.delete(notice);

    }
     */

    @ExceptionHandler(AlreadyDeletedException.class)
    public ResponseEntity<String> handlerAlreadyDeletedException(AlreadyDeletedException exception) {
        return new ResponseEntity<>(exception.getMessage(), HttpStatus.OK);
    }

    @DeleteMapping("/api/notice/{id}")
    public void deleteNotice(@PathVariable Long id) {

        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        if(notice.isDeleted()) {
            throw new AlreadyDeletedException("이미 삭제된 공지사항의 글입니다.");
        }

        notice.setDeleted(true);
        notice.setDeletedDate(LocalDateTime.now());

        noticeRepository.save(notice);
    }

    @DeleteMapping("/api/notice")
    public void deleteNoticeList(@RequestBody NoticeDeleteInput noticeDeleteInput) {

        List<Notice> noticeList = noticeRepository.findByIdIn(noticeDeleteInput.getIdList())
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        noticeList.stream().forEach(e -> {
            e.setDeleted(true);
            e.setDeletedDate(LocalDateTime.now());
        });

        noticeRepository.saveAll(noticeList);
    }

    @DeleteMapping("/api/notice/all")
    public void deleteNoticeAll() {
        List<Notice> noticeList = noticeRepository.findAll();

        if(!noticeList.isEmpty()) {
            noticeList.stream().forEach(e -> {
                if(!e.isDeleted()) {
                    e.setDeleted(true);
                    e.setDeletedDate(LocalDateTime.now());
                }
            });
        }

        noticeRepository.saveAll(noticeList);
    }

    @PostMapping("/api/notice")
    public ResponseEntity<Object> addNotice(@RequestBody @Valid NoticeInput noticeInput
            , Errors errors) {

        if(errors.hasErrors()) {

            List<ResponseError> responseErrors = new ArrayList<>();

            errors.getAllErrors().stream().forEach(e -> {
                responseErrors.add(ResponseError.of((FieldError)e));
            });

            return new ResponseEntity<>(responseErrors, HttpStatus.BAD_REQUEST);
        }
        // 정상 로직
        noticeRepository.save(Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .hits(0)
                .likes(0)
                .regDate(LocalDateTime.now())
                .build());

        return ResponseEntity.ok().build();
    }

    @GetMapping("/api/notice/latest/{size}")
    public Page<Notice> noticeLatest(@PathVariable int size) {

        Page<Notice> noticeList
                = noticeRepository.findAll(
                        PageRequest.of(0, size, Sort.Direction.DESC, "regDate"));

        return noticeList;
    }
}


04. 공지사항의 내용을 등록한 이후에 바로 동일한 제목과 공지사항을 등록하는 경우 등록을 막는 API를 만들어보자

  • Class : DuplicateNoticeException
  • 중복 경우(조건 : 동일제목, 동일 내용과 등록일이 현재시간 기준 1분 이내의 경우 중복으로 판단)
  • 예외발생(DuplicationNoticeException)
  • 갯수(count)로 갖고와서 로직의 비용을 줄이는게 좋다
  • notice.repository / NoticeRepository.java
package com.example.community.notice.repository;

import com.example.community.notice.entity.Notice;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

// @Repository는 Interface로 충분하다
@Repository
public interface NoticeRepository extends JpaRepository<Notice, Long> {

    Optional<List<Notice>> findByIdIn(List<Long> idList);

    int countByTitleAndContentAndRegDateIsGreaterThanEqual(String title, String content, LocalDateTime regDate);

}
  • notice.exception / DuplicateNoticeException.java
package com.example.community.notice.exception;

public class DuplicateNoticeException extends RuntimeException {
    public DuplicateNoticeException(String message) {
        super(message);
    }
}
  • notice.controller / ApiNoticeController.java
package com.example.community.notice.controller;

import com.example.community.notice.entity.Notice;
import com.example.community.notice.exception.AlreadyDeletedException;
import com.example.community.notice.exception.DuplicateNoticeException;
import com.example.community.notice.exception.NoticeNotFoundException;
import com.example.community.notice.model.NoticeDeleteInput;
import com.example.community.notice.model.NoticeInput;
import com.example.community.notice.model.ResponseError;
import com.example.community.notice.repository.NoticeRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@RestController
@RequiredArgsConstructor
public class ApiNoticeController {

    // Repository를 주입받는다. @RequiredArgsConstructor를 활용 해 생성자에서 주입을 받는다
    private final NoticeRepository noticeRepository;

    /*
    @GetMapping("/api/notice")
    public NoticeModel notice() {

        NoticeModel noticeModel = new NoticeModel();
        noticeModel.setId(1);
        noticeModel.setTitle("공지사항 입니다");
        noticeModel.setContent("공지사항 내용입니다");
        noticeModel.setRegDate(LocalDateTime.of(2021, 07, 04, 11, 11));

        return noticeModel;
    }
     */

    /*
    @GetMapping("/api/notice")
    public List<NoticeModel> notice() {
        List<NoticeModel> noticeList = new ArrayList<>();

        noticeList.add(NoticeModel.builder()
                .id(1)
                .title("공지사항 입니다")
                .content("공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 03, 11, 11))
                .build());

        noticeList.add(NoticeModel.builder()
                .id(2)
                .title("두번째 공지사항 입니다")
                .content("두번째 공지사항 내용 입니다")
                .regDate(LocalDateTime.of(2021, 07, 04, 11, 11))
                .build());

        return noticeList;
    }
     */

    @GetMapping("/api/notice")
    public List<NoticeInput> notice() {

        List<NoticeInput> noticeList = new ArrayList<>();

        return noticeList;
    }

    @GetMapping("/api/notice/count")
    public int noticeCount() {

        return 10;
    }

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestParam String title, @RequestParam String content) {
        return NoticeModel.builder()
                .id(1)
                .title(title)
                .content(content)
                .regDate(LocalDateTime.of(2021, 07,04,11,43))
                .build();
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(NoticeModel noticeModel) {
        noticeModel.setId(2);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public NoticeModel addNotice(@RequestBody NoticeModel noticeModel) {
        noticeModel.setId(3);
        noticeModel.setRegDate(LocalDateTime.now());
        return noticeModel;
    }
     */

    /*
    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {
        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .build();

        noticeRepository.save(notice);

        return notice;
    }
     */

    /*
    @PostMapping("/api/notice")
    public Notice addNotice(@RequestBody NoticeInput noticeInput) {

        Notice notice = Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .regDate(LocalDateTime.now())
                .hits(0)
                .likes(0)
                .build();

        Notice resultNotice = noticeRepository.save(notice);

        return resultNotice;
    }
    */

    @GetMapping("/api/notice/{id}")
    public Notice notice(@PathVariable Long id) {
        // .findById()는 반환타입이 Optional이다, 즉 null을 반환할 수도 있다
        // 그렇기에 Notice를 Optional로 감싸준다
        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            return notice.get();
        }

        return null;
    }

    /*
    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id,@RequestBody NoticeInput noticeInput) {

        Optional<Notice> notice = noticeRepository.findById(id);
        if(notice.isPresent()) {
            notice.get().setTitle(noticeInput.getTitle());
            notice.get().setContent(noticeInput.getContent());
            notice.get().setUpdateDate(LocalDateTime.now());
            noticeRepository.save(notice.get());
        }
    }
     */


    @ExceptionHandler(NoticeNotFoundException.class)
    public ResponseEntity<String> handlerNoticeNotFoundException(NoticeNotFoundException exception) {
            return new ResponseEntity<>(exception.getMessage(), HttpStatus.BAD_REQUEST);
    }

    @PutMapping("/api/notice/{id}")
    public void updateNotice(@PathVariable Long id, @RequestBody NoticeInput noticeInput) {

        /*
        Optional<Notice> notice = noticeRepository.findById(id);
        if(!notice.isPresent()) {
            // 예외 발생
            throw new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다.");
        }
        */


        // 이 경우 Optional이 아니기에 객체를 바로 받아 사용가능하다
        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));


        // 정상로직 수행
        notice.setTitle(noticeInput.getTitle());
        notice.setContent(noticeInput.getContent());
        notice.setUpdateDate(LocalDateTime.now());
        noticeRepository.save(notice);

    }

    @PatchMapping("/api/notice/{id}/hits")
    public void noticeHits(@PathVariable Long id) {
        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        notice.setHits(notice.getHits() + 1);

        noticeRepository.save(notice);
    }

    /*
    @DeleteMapping("/api/notice/{id}")
    public void deleteNotice(@PathVariable Long id) {

        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        noticeRepository.delete(notice);

    }
     */

    @ExceptionHandler(AlreadyDeletedException.class)
    public ResponseEntity<String> handlerAlreadyDeletedException(AlreadyDeletedException exception) {
        return new ResponseEntity<>(exception.getMessage(), HttpStatus.OK);
    }

    @DeleteMapping("/api/notice/{id}")
    public void deleteNotice(@PathVariable Long id) {

        Notice notice = noticeRepository.findById(id)
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        if(notice.isDeleted()) {
            throw new AlreadyDeletedException("이미 삭제된 공지사항의 글입니다.");
        }

        notice.setDeleted(true);
        notice.setDeletedDate(LocalDateTime.now());

        noticeRepository.save(notice);
    }

    @DeleteMapping("/api/notice")
    public void deleteNoticeList(@RequestBody NoticeDeleteInput noticeDeleteInput) {

        List<Notice> noticeList = noticeRepository.findByIdIn(noticeDeleteInput.getIdList())
                .orElseThrow(() -> new NoticeNotFoundException("공지사항의 글이 존재하지 않습니다."));

        noticeList.stream().forEach(e -> {
            e.setDeleted(true);
            e.setDeletedDate(LocalDateTime.now());
        });

        noticeRepository.saveAll(noticeList);
    }

    @DeleteMapping("/api/notice/all")
    public void deleteNoticeAll() {
        List<Notice> noticeList = noticeRepository.findAll();

        if(!noticeList.isEmpty()) {
            noticeList.stream().forEach(e -> {
                if(!e.isDeleted()) {
                    e.setDeleted(true);
                    e.setDeletedDate(LocalDateTime.now());
                }
            });
        }

        noticeRepository.saveAll(noticeList);
    }

    /*
    @PostMapping("/api/notice")
    public ResponseEntity<Object> addNotice(@RequestBody @Valid NoticeInput noticeInput
            , Errors errors) {

        if(errors.hasErrors()) {

            List<ResponseError> responseErrors = new ArrayList<>();

            errors.getAllErrors().stream().forEach(e -> {
                responseErrors.add(ResponseError.of((FieldError)e));
            });

            return new ResponseEntity<>(responseErrors, HttpStatus.BAD_REQUEST);
        }
        // 정상 로직
        noticeRepository.save(Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .hits(0)
                .likes(0)
                .regDate(LocalDateTime.now())
                .build());

        return ResponseEntity.ok().build();
    }
     */

    @GetMapping("/api/notice/latest/{size}")
    public Page<Notice> noticeLatest(@PathVariable int size) {

        Page<Notice> noticeList
                = noticeRepository.findAll(
                        PageRequest.of(0, size, Sort.Direction.DESC, "regDate"));

        return noticeList;
    }

    @ExceptionHandler(DuplicateNoticeException.class)
    public ResponseEntity<?> handlerDuplicateNoticeException(DuplicateNoticeException duplicateNoticeException) {
        return new ResponseEntity(duplicateNoticeException.getMessage(), HttpStatus.BAD_REQUEST);
    }

    @PostMapping("/api/notice")
    public ResponseEntity<Object> addNotice(@RequestBody @Valid NoticeInput noticeInput
            , Errors errors) {

        // validation 로직
        if(errors.hasErrors()) {
            List<ResponseError> responseErrors = new ArrayList<>();
            errors.getAllErrors().stream().forEach(e -> {
                responseErrors.add(ResponseError.of((FieldError)e));
            });
            return new ResponseEntity<>(responseErrors, HttpStatus.BAD_REQUEST);
        }

        // Duplication 로직(중복체크)
        LocalDateTime checkDate = LocalDateTime.now().minusMinutes(1);
        int noticeCount = noticeRepository.countByTitleAndContentAndRegDateIsGreaterThanEqual(
                noticeInput.getTitle(),
                noticeInput.getContent(),
                checkDate
        );
        if (noticeCount > 0) {
            throw new DuplicateNoticeException("1분 이내에 동일한 내용의 공지사항이 등록되었습니다.");
        }

        // add 로직
        noticeRepository.save(Notice.builder()
                .title(noticeInput.getTitle())
                .content(noticeInput.getContent())
                .hits(0)
                .likes(0)
                .regDate(LocalDateTime.now())
                .build());

        return ResponseEntity.ok().build();
    }
}


profile
아직까지는 코린이!

1개의 댓글

comment-user-thumbnail
2022년 11월 28일

차근차근 진행하는게 많은 도움이 되었습니다. 주석처리한 코드는 뒤로 갈 수록 너무 길어져서
코드읽기 좀 힘들었습니다. 코드가 전부 흰색 글씨라 보기 힘들었습니다. 코드 작성 태그에 ```java 추가하시면 훨씬 보기 좋을것 같습니다.

답글 달기