Spring - todo 서버 만들기

yuns·2022년 10월 3일
0

Spring

목록 보기
9/13
post-thumbnail

4개의 레이어 필요!

  • model
  • repository
  • service
  • controller

각각의 레이어가 각자의 역할만 담당하게 하여 유지보수가 용이함


model

각각의 객체를 뜻함.
todo클래스, 요청과 응답을 받는 클래스 필요

TodoEntity.java

  1. annotation 붙이기
  2. private로 변수 설정.
    id는
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)

어노테이션, 각각의 컬럼은

    @Column(nullable = false)

붙이기. (컬럼이면서, 값이 없으면 안된다는 뜻)

package org.example.model;

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

import javax.persistence.*;

@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
public class TodoEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false)
    private String title;

    // h2에서 order가 예약어이기 때문에 따로 이름 지정
    @Column(name = "todoOrder", nullable = false)
    private Long order;

    @Column(nullable = false)
    private Boolean completed;
}

TodoRequest.java

  1. annotation 붙이기
  2. todoEntity의 변수 (id제외)를 입력
package org.example.model;

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

@Data
@NoArgsConstructor
@AllArgsConstructor
public class TodoRequest {
    private String title;

    private Long order;

    private Boolean completed;
}

TodoResponse.java

  1. annotation 붙이기
  2. todoEntity의 변수 입력( id 포함, url 추가)
  3. 파라미터로 todoEntity를 받아 생성자 만들기 ( url은 todoEntity에 없는 정보이기 때문에 주소 + id값 을 이용하여 생성해줌)
package org.example.model;

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

@Data
@NoArgsConstructor
@AllArgsConstructor
public class TodoResponse {
    private Long id;

    private String title;

    private Long order;

    private Boolean completed;

    private String url;

    public TodoResponse(TodoEntity todoEntity) {
        this.id = todoEntity.getId();
        this.title = todoEntity.getTitle();
        this.order = todoEntity.getOrder();
        this.completed = todoEntity.getCompleted();

        this.url = "http://localhost:8080/" + this.id;
    }
}

Repository

TodoRepository.java

db와 데이터를 주고받기 위한 인터페이스

  1. repository 패키지 안에 TodoRepository.java 생성
  2. class를 interface키워드로 바꾸고, JpaRepository를 상속, 제네릭<>으로 db와 연결될 테이블 객체를, 뒤에는 해당 객체의 id의 타입을 작성
  3. 클래스 위에 @Repository annotation 추가
package org.example.repository;

import org.example.model.TodoEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface TodoRepository  extends JpaRepository<TodoEntity, Long> {
}

이렇게 만들면 repository.save()처럼 상속했던 JapRepository에 저장되어있는 메소드들을 사용할 수 있게됨.


Service

  1. TodoRepository를 private final로 멤버로 선언

  2. annotation 붙이기

  3. 기능 별로 메소드 추가

    • 목록에 todo 추가 : add() - TodoRequest를 매개변수로 받아 Entity를 리턴
    • 조회 : id값 필요, 조회된 todo 반환
    • 리스트 전체 조회 : List 반환
    • 수정 : 수정할 id값과 어떤 값으로 바꿀지 TodoRequest에서 가져오는 값 필요, 수정된 결과 Entity 반환
    • 삭제 : 삭제할 id값 필요, 결과 반환 필요없음(void)
    • 전체 삭제 : 결과 반환 필요없음(void)
  4. 등록 메소드

    • TodoEntity 객체 생성
    • .setTitle로 값 할당시키기
    • 할당시킬 값은 => todoRequest의 title부분을 받아야하니 .getTitle()
    • todoRepository에 .save()로 todoEntity 넣기
    • todoEntity의 값이 반환되기 때문에 return값으로 .save했던 코드를 넣음
      // 등록
      public TodoEntity add(TodoRequest todoRequest) {
          TodoEntity todoEntity = new TodoEntity();
          todoEntity.setTitle(todoRequest.getTitle());
          todoEntity.setOrder(todoRequest.getOrder());
          todoEntity.setCompleted(todoRequest.getCompleted());
          return this.todoRepository.save(todoEntity);
      }
  5. todo 조회 메소드
    - todoRepository의 .findById로 id값을 찾기
    - 값이 없으면 notfound exception 날리기
    - 이것을 바로 return값으로 지정

        // 조회
    public TodoEntity searchById(Long id) {
    //        this.todoRepository.findById(id).orElseThrow(
    //                () -> new IllegalArgumentException("존재하지 않습니다.")
    //        );
          return this.todoRepository.findById(id).orElseThrow(
                  () -> new ResponseStatusException(HttpStatus.NOT_FOUND)
          );
      }
  6. 모든 목록 조회 메소드
    - todoRepository의 .findAll() 반환

        // 목록 전체 조회
        public List<TodoEntity> searchAll() {
            return todoRepository.findAll();
        }
  7. 수정하기 메소드

    • searchById로 id값을 찾아 Entity로 가져옴
    • if문으로 값이 null이 아닐 경우, entity를 todoRequest에서 .get으로 가져온 값으로 .set해줌
    • todoRepository에 todoEntity를 .save한 값을 return하게 함
    // 수정
    public TodoEntity updateById(Long id, TodoRequest todoRequest) {
        TodoEntity todoEntity = this.searchById(id);
        if (todoRequest.getTitle() != null) {
            todoEntity.setTitle(todoRequest.getTitle());
        }
        if (todoRequest.getOrder() != null) {
            todoEntity.setOrder(todoRequest.getOrder());
        }
        if (todoRequest.getCompleted() != null) {
            todoEntity.setCompleted(todoRequest.getCompleted());
        }
        return this.todoRepository.save(todoEntity);
    }
  8. 삭제하기 메소드

    • todoRepository에서 .delete로 아이디값을 받아 삭제
  9. 전체 삭제 메소드

    • todoRepository에서 .deleteAll()
package org.example.service;

import lombok.AllArgsConstructor;
import org.example.model.TodoEntity;
import org.example.model.TodoRequest;
import org.example.repository.TodoRepository;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;

import java.util.List;

@Service
@AllArgsConstructor
public class TodoService {
    private final TodoRepository todoRepository;

    // 등록
    public TodoEntity add(TodoRequest todoRequest) {
        TodoEntity todoEntity = new TodoEntity();
        todoEntity.setTitle(todoRequest.getTitle());
        todoEntity.setOrder(todoRequest.getOrder());
        todoEntity.setCompleted(todoRequest.getCompleted());
        return this.todoRepository.save(todoEntity);
    }
    
    // 조회
    public TodoEntity searchById(Long id) {
//        this.todoRepository.findById(id).orElseThrow(
//                () -> new IllegalArgumentException("존재하지 않습니다.")
//        );
        return this.todoRepository.findById(id).orElseThrow(
                () -> new ResponseStatusException(HttpStatus.NOT_FOUND)
        );
    }

    // 목록 전체 조회
    public List<TodoEntity> searchAll() {
        return todoRepository.findAll();
    }

    // 수정
    public TodoEntity updateById(Long id, TodoRequest todoRequest) {
        TodoEntity todoEntity = this.searchById(id);
        if (todoRequest.getTitle() != null) {
            todoEntity.setTitle(todoRequest.getTitle());
        }
        if (todoRequest.getOrder() != null) {
            todoEntity.setOrder(todoRequest.getOrder());
        }
        if (todoRequest.getCompleted() != null) {
            todoEntity.setCompleted(todoRequest.getCompleted());
        }
        return this.todoRepository.save(todoEntity);
    }

    // 삭제
    public void deleteById(Long id) {
        this.todoRepository.deleteById(id);
    }

    // 전체 삭제
    public void deleteAll() {
        this.todoRepository.deleteAll();
    }
}

Controller

  1. annotation 붙이기 (controller 알림, CORS 이슈 방지, 생성자 생성, RequestMapping("/"))
  2. 멤버로 private final로 TodoService 가져오기
  3. 기능별 메소드 틀 생성
    • todo 생성하기 (PostMapping)
    • todo 조회 (GetMapping, 경로로 id값 가져오기 {id})
    • 전체 목록 조회 (GetMapping, List로 가져오기)
    • 수정 (PatchMapping, 경로로 id값 가져오기)
    • 삭제 (DeleteMapping, 경로로 id값 가져오기)
    • 전체 삭제 (DeleteMapping)
package org.example.controller;

import lombok.AllArgsConstructor;
import org.example.model.TodoResponse;
import org.example.repository.TodoRepository;
import org.example.service.TodoService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@CrossOrigin
@RestController
@AllArgsConstructor
@RequestMapping("/")
public class TodoController {
    private final TodoService todoService;

    @PostMapping
    public ResponseEntity<TodoResponse> create() {
        System.out.println("Create");
        return null;
    }

    @GetMapping("{id}")
    public ResponseEntity<TodoResponse> readOne() {
        System.out.println("Read One");
        return null;
    }

    @GetMapping
    public ResponseEntity<List<TodoResponse>> readAll() {
        System.out.println("Read All");
        return null;
    }

    @PatchMapping("{id}")
    public ResponseEntity<TodoResponse> update() {
        System.out.println("Update");
        return null;
    }

    @DeleteMapping("{id}")
    public ResponseEntity<?> deleteOne() {
        System.out.println("Delete One");
        return null;
    }

    @DeleteMapping
    public ResponseEntity<?> deleteAll() {
        System.out.println("Delete All");
        return null;
    }
}
  1. 어플리케이션을 실행하기 위해 Application.java로 가서 메인메소드 수정
    • 클래스 위에 @SpringBootApplication 어노테이션 붙이기
    • 메인메소드 안에 SpringApplication.run(TodoServerApplication.class, args); 넣기
package org.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import javax.swing.*;

@SpringBootApplication
public class TodoServerApplication {
    public static void main(String[] args) {
        System.out.println("hi");
        SpringApplication.run(TodoServerApplication.class, args);
    }
}
  1. todo 생성 메소드

    • 파라미터로 @RequestBody이용 TodoRequest를 받음
    • if문으로 값이 없으면 badRequest 리턴
    • todoEntity에 수정한 todoRequest값을 넣고, return값으로 보내기
  2. 하나만 조회 메소드

    • 경로로 받은 id값을 쓰기 위해 파라미터로 @PathVariable Long id 받기
    • TodoEntity에 todoService의 serchById를 id값을 받아 넣기
    • 리턴 보내기
  3. 모두 조회 메소드

    • TodoEntity 리스트(list)에 todoService의 searchAll()넣기
    • list를 TodoResponse 리스트에 넣기
    • 리턴 보내기
  4. 업데이트 메소드

    • 파라미터로 @PathVariable Long id , @RequestBody TodoRequest todoRequest 받기
    • todoService에 updateById로 바꿔질 값은 id로, 바꿀 값은 todoRequest로 설정
    • 이것을 todoEntity result 변수로 넣기
  5. 하나 삭제 메소드

    • 파라미터로 @PathVariable Long id 받기
    • 서비스에 접근해서 deleteById(id)
    • 리턴은 void값이기 때문에 그냥 return ResponseEntity.ok().build();
  6. 전체 삭제 메소드

    • 서비스에 접근해서 .deleteAll()
    • 리턴은 void값이기 때문에 그냥 return ResponseEntity.ok().build();
package org.example.controller;

import lombok.AllArgsConstructor;
import org.example.model.TodoEntity;
import org.example.model.TodoRequest;
import org.example.model.TodoResponse;
import org.example.repository.TodoRepository;
import org.example.service.TodoService;
import org.springframework.http.ResponseEntity;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;

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

@CrossOrigin
@RestController
@AllArgsConstructor
@RequestMapping("/")
public class TodoController {
    private final TodoService todoService;

    // 생성
    @PostMapping
    public ResponseEntity<TodoResponse> create(@RequestBody TodoRequest todoRequest) {
        System.out.println("Create");

        // 제목이 없으면 badRequest 처리
        if (ObjectUtils.isEmpty(todoRequest.getTitle()))
            return ResponseEntity.badRequest().build();

        // order값이 없으면 기본(default)처리
        if (ObjectUtils.isEmpty(todoRequest.getOrder()))
            todoRequest.setOrder(0L);

        // completed값이 없으면 기본(default)처리
        if (ObjectUtils.isEmpty(todoRequest.getCompleted()))
            todoRequest.setCompleted(false);

        TodoEntity result = this.todoService.add(todoRequest);
        return ResponseEntity.ok(new TodoResponse(result));
    }

    // 하나만 조회
    @GetMapping("{id}")
    public ResponseEntity<TodoResponse> readOne(@PathVariable Long id) {
        System.out.println("Read One");

        TodoEntity result = this.todoService.searchById(id);

        return ResponseEntity.ok(new TodoResponse(result));
    }

    // 모두 조회
    @GetMapping
    public ResponseEntity<List<TodoResponse>> readAll() {
        System.out.println("Read All");
        List<TodoEntity> list = this.todoService.searchAll();
        List<TodoResponse> response = list.stream().map(TodoResponse::new).collect(Collectors.toList());
        return ResponseEntity.ok(response);
    }

    // 업데이트
    @PatchMapping("{id}")
    public ResponseEntity<TodoResponse> update(@PathVariable Long id, @RequestBody TodoRequest todoRequest) {
        System.out.println("Update");
        TodoEntity result = this.todoService.updateById(id, todoRequest);
        return ResponseEntity.ok(new TodoResponse(result));
    }

    // 하나 삭제
    @DeleteMapping("{id}")
    public ResponseEntity<?> deleteOne(@PathVariable Long id) {
        System.out.println("Delete One");
        this.todoService.deleteById(id);
        return ResponseEntity.ok().build();
    }

    @DeleteMapping
    public ResponseEntity<?> deleteAll() {
        System.out.println("Delete All");
        this.todoService.deleteAll();
        return ResponseEntity.ok().build();
    }
}

POSTMAN으로 확인해보기


주소 입력 - Body - raw - JSON 선택 후 빈칸에 아래처럼 입력 후 send 클릭

{
    "title" : "테스트입니다."
}

delete 시에는 주소에
localhost:8080/아이디 번호
처럼 입력


테스트 코드 작성하기

  1. build.gradle에 와서 테스트 의존성 추가
testImplementation('org.springframework.boot:spring-boot-starter-test')
  1. 테스트할 파일 TodoService.java로 와서 alt + Insert로 test... 클릭 후 테스트할 메소드 선택하고 만들기

  2. moc 개체를 사용하기 위해
    @ExtendWith(MockitoExtension.class)
    어노테이션 추가

  3. mock과 mock을 주입받을 필드 추가

        @Mock
        private TodoRepository todoRepository;
    
        @InjectMocks
        private TodoService todoService;

    mock을 사용하는 이유 : 외부시스템을 사용하지 않고 자체적으로 테스트 가능, 실제 db와 연결하지 않고(함부로 만지면 위험하니까) 진행해야하기 때문

0개의 댓글

관련 채용 정보