-
[To-Do 앱] 스프링 부트(Spring Boot) RESTful API - POST웹 어플리케이션 2019. 2. 24. 11:44
지난 포스트에서는 스프링 자바 RESTful API를 설계하고 관련 패키지와 클래스를 만들었다. 그 후 가장 간단한 API인 GET을 테스트 해 보았다. 이 포스트에서는 임베디드 몽고디비를 이용해서 실제로 데이터를 생성하는 POST API를 만들어 보도록 한다. 들어가기 앞서, 이전 포스트에 있는 에러와 버그를 수정했으니 전 포스트와 조금 달라도 당황하지 말고 이 포스트를 따라 수정하면 된다.
예상 독자
- 자바와 이클립스 또는 IntelliJ가 설치되어있다.
- Gradle이 설치되어 있다. (Gradle 설치)
- 자바를 좀 안다.
- Lombok을 사용 할 줄 안다. (포스트 : Lombok을 이용해 Getter/Setter/Builder에서 벗어나는 법)
- [To-Do 앱]스프링부트(SpringBoot) 웹 어플리케이션) 를 마쳤다.
- [To-Do 앱] 스프링 부트(Spring Boot) RESTful API - GET 를 마쳤다.
목표
- 몽고디비 설정
- 리파지토리 (JPA Repository)
- 서비스에서 리파지토리 이용하기 (Service)
- 컨트롤러 메서드 만들기 (Controller)
몽고디비 설정
build.gradle
임베디드 몽고디비를 사용하기 위해서는 build.gradle에 디펜덴시를 추가해야한다. 추가해야 할 디펜던시는 다음과 같다.
compile('org.springframework.boot:spring-boot-starter-data-mongodb')
compile('cz.jirutka.spring:embedmongo-spring:1.3.1')
compile('de.flapdoodle.embed:de.flapdoodle.embed.mongo')참고를 위해 전체 build.gradle을 첨부한다.
plugins {
id 'org.springframework.boot' version '2.1.3.RELEASE'
id 'java'
}
apply plugin: 'io.spring.dependency-management'
group = 'com.fsoftwareengineer'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
}
dependencies {
//implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok:1.18.4'
runtimeOnly 'org.springframework.boot:spring-boot-devtools'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// mongo db related
compile('org.springframework.boot:spring-boot-starter-data-mongodb')
compile('cz.jirutka.spring:embedmongo-spring:1.3.1')
compile('de.flapdoodle.embed:de.flapdoodle.embed.mongo')
}참고로 spring-boot-starter-data-jpa는 대신 spring-boot-starter-data-mongodb를 사용해야해 주석처리했다.
MongoConfig.java
몽고디비를 이용하기위해 몽고디비 Config 클래스를 만들어야한다. 이를 위해 Config라는 패키지를 추가하고 그 아래 MongoConfig.java를 추가한다.package com.fsoftwareengineer.MySpringApp.Config;
import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Profile;
@Profile(value = "mongo")
@Configuration
@Import(EmbeddedMongoAutoConfiguration.class)
public class MongoConfig {
}@Profile : 프로파일을 이용해 데이터베이스를 구분 할 수 있다. 예를들어 Application.properties에 spring.profiles.active = mongo로 하면 현재 Config를 사용 할 것이고 다른 값을 준다면 다른 값을 사용 할 것이다. 이부분은 아직 내가 확인을 안해봤으니 넘어가도록 하겠다.@Configuration : 이 어노테이션을 이용하면 Spring이 알아서 이 클래스는 환경설정을 위한 클래스 인식하고 알아서 그 내용을 불러온다.@Import : 이 어노테이션으로 티폴트 설정인 EmbeddedMongoAutoConfiguration을 불러온다. 이 어노테이션이 없으면 우리가 직접 configuration을 작성해야 하는 셈이다.여기까지 했으면 실행 해 보자. 아플리케이션이 정상적으로 실행한다면 제대로 환경설정이 된 것이다.리파지토리 (JPA Repository)
몽고디비를 설정한 어플리케이션이 잘 돌아간다면 이제 리파지토리를 만들 차례이다. 마찬가지로 ToDoItem패키지에 ToDoItemRepository를 만들어 보도록 하자.
package com.fsoftwareengineer.MySpringApp.ToDoItem;
import org.springframework.data.mongodb.repository.MongoRepository;
public interface ToDoItemRepository extends MongoRepository<ToDoItem, String> {
}JPA리파지토리가 뭔지 간단히 설명하도록 하겠다. JPA는 간단히 말하면 자바 메서드의 형태로 SQL을 실행하고 결과값을 가져 올 수 있게 하는 API를 말한다. 리파지토리는 말 그대로 데이터베이스를 의미한다. 웹 어플리케이션에서 자주 사용되는 DB 오퍼레이션들이 몇 가지 있다. 대표적으로 create, update, findById, findByName등등이 있다. 그래서 옛날에는 개발자들이 어플리케이션을 만들 때 디비와 연결하기 위해서는 항상 몇 가지 데이터베이스 SQL을 직접 짜고 파싱 해야 했었다. 세월이 흘러 데이터베이스 ORM(Object Relational Mapping)이라고 데이터베이스 스키마를 만들어주고, 또 SQL의 결과의 파싱을 대신 해주는 툴들이 생겼다. Hibernate이 대표적인 ORM 툴이다. 여기서 더 나아가 자바에서는 Java Persistent API라는 것을 만들었다. 즉 데이터베이스의 제공자(여기서는 Mongo DB)가 개발자들이 쉽게 이용 할 수 있도록 자바 API를 만든 것이다. 따라서 우리는 이렇게 interface를 만들고 Repository를 extends~하면 JPA를 바로 이용 할 수 있다.
여기서 <ToDoItem, String>에 대해 잠깐 설명하겠다. <Model, ID>로 첫번째에는 이 리파지토리가 표현해야하는 모델, 여기서는 ToDoItem이 들어가고, ID부분에는 이 ToDoItem을 구별 할 수 있는 ID의 타입이 들어간다. 그렇다면 ToDoItem.java를 보자. 이와 관련해 수정해야 하는 것이 있다.
ToDoItem.java
package com.fsoftwareengineer.MySpringApp.ToDoItem;
import lombok.*;
import org.springframework.data.annotation.Id;
@Data
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ToDoItem {
@Id
private String id;
private String title;
private boolean done;
}id가 몽고 디비에서의 ID임을 나타내기 위해 @Id 어노테이션을 추가하자. 이 ID는 몽고 디비가 알아서 만들어 줌으로 우리가 앞으로 고유한 값을 생성 할 필요가 없어진다.
서비스에서 리파지토리 이용하기 (Service)
지난번의 get 메서드에 이어서 이번엔 create메서드와 getAll메서드를 작성 해 보았다.
ToDoItemService.java
package com.fsoftwareengineer.MySpringApp.ToDoItem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ToDoItemService {
@Autowired
private ToDoItemRepository toDoItemRepository;
public ToDoItem get(final String id) {
// do id validation
return toDoItemRepository.findById(id).orElse(null);
}
public ToDoItem create(final ToDoItem toDoItem) {
if(toDoItem == null) {
throw new NullPointerException("To Do Item cannot be null.");
}
return toDoItemRepository.insert(toDoItem);
}
public List<ToDoItem> getAll() {
return toDoItemRepository.findAll();
}
}서비스에 위에서 생성한 ToDoItemRespository를 서비스에 추가 해 주고 @Getter @Setter를 만들어 줬다. 그리고 create메서드를 만들어 toToItemRepository에 insert하는 메서드를 짜본다. 또한 이전에 아무렇게나 리턴했었던 get을 toDoItemRepository를 이용해 수정 해 주고 getAll메서드도 만들어 주었다.
눈치 챘을 지 모르겠지만 toToItemRepository하고 .을 누르면 사용 할 수 있는 여러가지 메서드들이 나오는 것을 볼 수 있다. 이게 바로 위에서 말했던 JPA 라는 것이다. 우리는 insert, count, delete같은 함수의 내부를 구현하지 않았지만 extends MongoRepository가 구현한 JPA를 이용하면 이와 같은함수들을 사용 할 수 있다.
컨트롤러 메서드 만들기 (Controller)
ToDoItemController에 다음과 같이 두 메서드를 추가했다.
@RequestMapping(method = RequestMethod.GET)
public @ResponseBody List<ToDoItemResponse> getAll() {
List<String> errors = new ArrayList<>();
List<ToDoItem> toDoItems = toDoItemService.getAll();
List<ToDoItemResponse> toDoItemResponses = new ArrayList<>();
toDoItems.stream().forEach(toDoItem -> {
toDoItemResponses.add(ToDoItemAdapter.toToDoItemResponse(toDoItem, errors));
});
return toDoItemResponses;
}
@RequestMapping(method = RequestMethod.POST)
public @ResponseBody ToDoItemResponse create(@RequestBody final ToDoItemRequest toDoItemRequest) {
List<String> errors = new ArrayList<>();
ToDoItem toDoItem = ToDoItemAdapter.toToDoItem(toDoItemRequest);
try {
toDoItem = toDoItemService.create(toDoItem);
} catch (final Exception e) {
errors.add(e.getMessage());
}
return ToDoItemAdapter.toToDoItemResponse(toDoItem, errors);
}ToDoItemResponse와 마찬가지로 ToDoItemRequest를 ToDoItem으로 변환 해 줄 아답터가 필요하다.ToDoItemAdapter.java에 다음과 같은 메서드를 추가 해 줬다.public static ToDoItem toToDoItem(final ToDoItemRequest toDoItemRequest) {
if(toDoItemRequest == null) {
return null;
}
return ToDoItem.builder()
.title(toDoItemRequest.getTitle())
.done(toDoItemRequest.isDone())
.build();
}참고를 위해 전체 ToDoItemController와 ToDoItemAdapter를 첨부한다.ToDoItemController.javapackage com.fsoftwareengineer.MySpringApp.ToDoItem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/todo")
public class ToDoItemController {
@Autowired
private ToDoItemService toDoItemService;
@RequestMapping(method = RequestMethod.GET, value = "/{id}")
public @ResponseBody ToDoItemResponse get(@PathVariable(value="id") String id) {
List<String> errors = new ArrayList<>();
ToDoItem toDoItem = null;
try {
toDoItem = toDoItemService.get(id);
} catch (final Exception e) {
errors.add(e.getMessage());
}
return ToDoItemAdapter.toToDoItemResponse(toDoItem, errors);
}
@RequestMapping(method = RequestMethod.GET)
public @ResponseBody List<ToDoItemResponse> getAll() {
List<String> errors = new ArrayList<>();
List<ToDoItem> toDoItems = toDoItemService.getAll();
List<ToDoItemResponse> toDoItemResponses = new ArrayList<>();
toDoItems.stream().forEach(toDoItem -> {
toDoItemResponses.add(ToDoItemAdapter.toToDoItemResponse(toDoItem, errors));
});
return toDoItemResponses;
}
@RequestMapping(method = RequestMethod.POST)
public @ResponseBody ToDoItemResponse create(@RequestBody final ToDoItemRequest toDoItemRequest) {
List<String> errors = new ArrayList<>();
ToDoItem toDoItem = ToDoItemAdapter.toToDoItem(toDoItemRequest);
System.out.println(toDoItemRequest.getTitle());
try {
toDoItem = toDoItemService.create(toDoItem);
} catch (final Exception e) {
errors.add(e.getMessage());
e.printStackTrace();
}
return ToDoItemAdapter.toToDoItemResponse(toDoItem, errors);
}
}ToDoItemAdapter.javapackage com.fsoftwareengineer.MySpringApp.ToDoItem;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class ToDoItemAdapter {
public static ToDoItem toToDoItem(final ToDoItemRequest toDoItemRequest) {
if(toDoItemRequest == null) {
return null;
}
return ToDoItem.builder()
.title(toDoItemRequest.getTitle())
.done(toDoItemRequest.isDone())
.build();
}
public static ToDoItemResponse toToDoItemResponse(final ToDoItem toDoItem, final List<String> errors) {
return ToDoItemResponse.builder()
.toDoItem(toDoItem)
.errors(Optional.ofNullable(errors).orElse(new ArrayList<>()))
.build();
}
}실행 및 테스팅실행이 됐다면 Postman을 이용해 API 테스팅을 해 보자. Postman을 이용 해 API Test를 하는 법은 포스트맨(Postman)을 이용한 API 테스트를 참고한다.create 테스트create 을 이용해 To Do Item을 2개정도 만들어 보자.리턴 값은 다음과 같다. id가 몽고디비에 의해 자동으로 생성 된 것을 확인 할 수 있다.
{
"data": {
"id": "5c72015ea1bd3074ba588b5d",
"title": "test create method 2",
"done": false
},
"errors": []}
이제 http://localhost:8080/todo를 이용해 getAll을 불러보자. 여러분이 생성한 두 개의 To Do Item들이 리턴 될 것이다.
리턴값은 다음과 같다. 생성된 아이템들이 리턴 된 것을 확인 할 수 있다.
[
{
"data": {
"id": "5c72011ea1bd3074ba588b5c",
"title": "test create method",
"done": false
},
"errors": []
},
{
"data": {
"id": "5c72015ea1bd3074ba588b5d",
"title": "test create method 2",
"done": false
},
"errors": []
}
]끝
이번 포스트에서는 임베디드 몽고 디비를 설정하고 POST메서드를 이용해 ToDoItem을 데이터베이스에 생성하는 서비스를 만들어 보았다. 다음 포스트 부터는 node.js와 vue.js를 이용해 드디어 Frontend의 개발환경을 설정 해 보도록 하겠다.
이 코드는 ToDoTutorial Github에서 확인 할 수 있다.
다음포스트 :
'웹 어플리케이션' 카테고리의 다른 글
[To-Do 앱]Vue.js/Node.js 앱 에서 API Call 하기 (Axios) (2) 2019.02.26 스프링 부트 도커에 올리기(Dockerizing Spring Boot App) (3) 2019.02.25 [To-Do 앱]Vue.js 와 Node.js를 이용해 웹 앱 만들기 (4) 2019.02.25 [To-Do 앱] 스프링 부트(Spring Boot) RESTful API - GET (8) 2019.02.21 [To-Do 앱]스프링부트(SpringBoot) 웹 어플리케이션 (4) 2019.02.20 댓글