> 회의록 생성 / 수정 / 삭제 / 조회
프론트엔드에서 캘린더를 눌렀을 때 날짜(즉 date 데이터)가 전달되며, 해당 날짜에 해당하는 회의록이 존재할 경우 그 회의록을 수정하고 존재하지 않을 경우 회의록을 생성할 것
📢 위의 경우는 Monolithic의 경우이고, 현재 구현은 API 형식으로 할 것이므로 기능별로 구현할 예정
> 프로그램을 실행하는 인터페이스로, 프로그램에 요청을 전달하는 방식을 의미
> URI와 HTTP method 사용해 리소스 식별해 요청 처리하는 아키텍쳐 스타일
ex. GET /members/delete/1
2) 자원에 대한 행위는 HTTP method(GET, POST, PUT, DELETE)로 표현
HTTP method | 역할 |
---|---|
POST | 해당 URI를 요청하면 리소스 생성 |
GET | 리소스 조회 |
PUT | 리소스 수정 |
DELETE | 리소스 삭제 |
📢 아래와 같이 행위는 HTTP method로 나타내므로 굳이 URI 넣을 필요 없고(1), 사용하는 목적에 맞는 method를 선택해야함(2)
#1. 회원 정보 가져오는 URI
Get /members/show/1 (X)
Get /members/1 (O)
#2. 회원 추가
GET /members/insert/2 (X)
POST /members/2 (O)
REST의 원칙을 잘 지키면서 API의 의미를 표현하기 쉽고, 파악하기 쉬운 방식으로 데이터를 교환이 이루어지도록 설계된 API
장점
쿼리스트링 또는 폼 데이터를 간편하게 받을 수 있음
매개변수의 기본값을 설정할 수 있어 오류 방지에 도움이 됨
단점
많은 매개변수를 사용하는 경우, 코드가 복잡해질 수 있음
쿼리스트링으로 데이터를 전달하면 보안상의 이슈가 발생할 수 있음
장점
복잡한 데이터 구조를 가진 객체를 쉽게 받아서 처리할 수 있음
JSON, XML과 같은 여러 데이터 형식을 처리할 수 있음
유효성 검사와 데이터 바인딩이 자동으로 처리되므로 편리
단점
클라이언트에서 보낸 데이터가 바로 자바 객체로 변환되기 때문에 보안상 주의가 필요
장점
RESTful API에서 경로에 담긴 데이터를 쉽게 추출할 수 있음
경로에 데이터가 포함되어 있어 URL이 직관적이고 읽기 쉬움
단점
반드시 하나의 요청만 사용할 수 있음
URL 경로에 데이터를 노출하기 때문에, 민감한 정보를 처리할 때 보안상 주의가 필요합니다.
경로가 복잡해지는 경우, 코드의 유지보수가 어려울 수 있습니다.
참고
https://meetup.nhncloud.com/posts/92
@RestController
: @Controller + @ResponseBody. Json 형태로 객체 데이터 반환
@ReponseBody
HTTP 응답 Body에 데이터를 직접 넣어주는 것을 의미
@Data
: @Getter, @Setter, @RequiredArgsConstructor, @ToString, @EqualsAndHashCode를 한 번에 설정해주는 annotation
@RequiredArgsConstructor: final이나 @NonNull인 필드 값만 파라미터로 받는 생성자 만들어줌
@GetMapping()
: API 메서드 지정으로 GET 방식의 API 지정
@Entity
: 이 클래스가 Entity가 될 클래스임을 명시. 테이블과 해당 클래스가 링크되며 @Column으로 지정된 변수가 테이블의 column이 됨
@Id
: 테이블의 PK(Primary Key)가 됨을 선언
@GeneratedValue
: PK를 스프링부트가 자동으로 생성
@Transactional
: DB 접근 도중 오류 발생 시 해당 기능을 모두 완수하지 않은 상태로 멈추는 것이 아니라, DB 접근 이전 상태로 돌려 데이터에 오류가 섞이지 않
@NoArgsConstructor
: 파라미터가 없는 기본 생성자 생성
@AllArgsConstructor
: 모든 필드 값을 파라미터로 받는 생성자 생성
extends CrudRepository
: 간단한 CRUD 사용 가능(Repository)
Optional <T>
: null이 올 수 있는 값을 감싸는 wrapper 클래스로 NPE(NullPointerException)가 발생하지 않도록 하며 각종 메소드 지원
java/com.example.graduation/controller/CalendarController
package com.example.graduation.controller;
import com.example.graduation.dto.MinutesForm;
import com.example.graduation.entity.Minutes;
import com.example.graduation.repository.MinutesRepository;
import com.example.graduation.service.MinutesService;
import jakarta.validation.constraints.Min;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
@RestController
@Slf4j //log
@RequiredArgsConstructor
public class CalendarController {
private final MinutesService minutesService;
//1. 회의록 생성
@PostMapping("/calendars/create")
public Optional<Minutes> createMinutes(@RequestBody MinutesForm form) {
Optional<Minutes> target = minutesService.create(form);
return target;
}
//2-1. 회의록 조회(전체)
@GetMapping("/calendars/watchAll")
public List<Minutes> getAllMinutes() {
List<Minutes> minutesList = minutesService.watchAll();
return minutesList;
}
//2-2. 회의록 조회(세부)
@GetMapping("/calendars/watch")
public Optional<Minutes> getMinutes(@RequestParam String date) {
Optional<Minutes> minutes = minutesService.watch(date);
if (minutes.isEmpty()) {
System.out.println("NOT EXIST");
}
return minutes;
}
//3. 회의록 수정
@PatchMapping("/calendars/edit")
public Minutes editMinutes(@RequestParam String date, @RequestBody MinutesForm form) {
Minutes minutes = minutesService.edit(date, form);
return minutes;
}
//4. 회의록 삭제
@DeleteMapping("/calendars/delete")
public String deleteMinutes(@RequestParam String date) {
minutesService.delete(date);
return "Completely Deleted";
}
}
.../com.example.graduation/dto/MinutesForm
package com.example.graduation.dto;
import com.example.graduation.entity.Minutes;
import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.Min;
import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import java.util.SimpleTimeZone;
@ToString
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class MinutesForm {
@JsonProperty("id")
private Long id;
@JsonProperty("userId")
private Long userId;
@JsonProperty("teamId")
private Long teamId;
@JsonProperty("date")
private String date;
@JsonProperty("title")
private String title;
@JsonProperty("content")
private String content;
// dto -> entity 연결
public Minutes toEntity() {
return new Minutes(id, userId, teamId, date, title, content);
}
}
.../com.example.graduation/entity/Minutes
package com.example.graduation.entity;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
//요청용
@Entity
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Getter
public class Minutes {
@Id
@GeneratedValue
private Long id;
@Column
private Long userId;
@Column
private Long teamId;
@Column
private String date;
@Column
private String title;
@Column
private String content;
public void update(String title, String content, Long userId) {
this.title = title;
this.content = content;
this.userId = userId;
}
}
.../com.example.graduation/repository/MinutesRepository
package com.example.graduation.repository;
import com.example.graduation.entity.Minutes;
import org.springframework.data.repository.CrudRepository;
import java.util.ArrayList;
import java.util.Optional;
//extends 뒤에는 CrudRepository를 작성하여 여러 기능을 사용할 수 있도록 하고, <> 안의 파라미터는 사용할 entity와 그 대푯값(이 프로젝트에서는 id)의 타입 작성
public interface MinutesRepository extends CrudRepository<Minutes, Long> {
@Override
ArrayList<Minutes> findAll();
Optional<Minutes> findByDate(String date);
}
.../com.example.graduation/service/MinutesService
package com.example.graduation.service;
import com.example.graduation.dto.MinutesForm;
import com.example.graduation.entity.Minutes;
import com.example.graduation.repository.MinutesRepository;
import jakarta.transaction.Transactional;
import jakarta.websocket.server.ServerEndpoint;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
@Slf4j
@RequiredArgsConstructor
public class MinutesService {
private final MinutesRepository minutesRepository;
@Transactional
public Optional<Minutes> create(MinutesForm dto) {
Minutes minutes= minutesRepository.save(dto.toEntity());
Optional<Minutes> target = minutesRepository.findById(minutes.getId());
return target;
}
@Transactional
public List<Minutes> watchAll() {
return minutesRepository.findAll();
}
@Transactional
public Optional<Minutes> watch(String date) {
Optional<Minutes> minutes = minutesRepository.findByDate(date);
return minutes;
}
@Transactional
public Minutes edit(String date, MinutesForm dto) {
//현재 입력한 date가 repository에 존재할 경우 edit. orElseThrow()
Minutes minutes = minutesRepository.findByDate(date).orElse(null);
minutes.update(dto.getTitle(), dto.getContent(), dto.getUserId());
return minutes;
}
@Transactional
public void delete(String date) {
Minutes minutes = minutesRepository.findByDate(date).orElse(null);
minutesRepository.delete(minutes);
}
}
lombok
logging => Slf4j
thymeleaf
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.1.1'
id 'io.spring.dependency-management' version '1.1.0'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
repositories {
mavenCentral()
}
dependencies {
//thymeleaf
//implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
//UserDto 생성 시 필요
implementation 'org.springframework.boot:spring-boot-starter-validation'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-mustache'
implementation 'org.springframework.boot:spring-boot-starter-web'
runtimeOnly 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
}
application.properties
spring.h2.console.enabled=true
spring.jpa.defer-datasource-initialization = true
Body에 아래와 같이 데이터 2개 입력
아래 사진과 같이 수정, 삭제가 정상적으로 이루어짐
📢 위의 동작이 정상적으로 이루어지지만 피드백에 따른 수정 필요
(1) watchAll에서 특정 년도와 월을 입력받을 시 해당 년도와 월에 해당하는 회의록만 조회
(2) id와 teamId의 경우 데이터를 어떻게 전송할지 생각하여 수정