n 스프링 부트에서 REST(REpresentational State Transfer) API를 실습해 보자.
n RESTful 웹 서비스는 JSON, XML 및 기타 미디어 유형을 생성하고 활용할 수 있다.
n Spring 또는 Spring Boot 기반의 RESTful 웹 서비스 만들려면 @RestController로 스프링 컨트롤러를 만들어야 한다.
n Spring Boot 애플리케이션에서 RESTful 웹 서비스를 사용하려면 빌드 파일에 spring-boot-starter-web을 포함시켜야 한다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
위 디펜던시(Maven 종속성)는 기본적으로 Jackson JSON 라이브러리, 즉 jackson-databind를 가지고 오도록 하며 Spring Boot REST는 클래스 패스에서 jackson-databind를 감지하기 때문에 기본적으로 JSON 응답을 제공한다.
Spring Boot REST에서 XML 응답을 지원하려면 spring-boot-starter-web과 함께 jackson-dataformat-xml 라이브러리를 제공해야 한다.
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.4</version>
</dependency>
n 스프링 부트는 기본적으로 Jackson JSON 라이브러리, 즉 jackson-databind를 구성하며, 스프링 부트 RESTful 웹 서비스는 classpath에서 Jackson JSON 라이브러리를 감지하면 JSON 응답을 생성하고 Jackson XML 라이브러리를 감지하면 XML 응답을 생성한다. (Jackson XML 라이브러리의 경우, jackson-dataformat-xml을 빌드 파일에 포함시켜야 한다.)
n 스프링 부트, REST 예제를 작성하는데 마리아 DB(MySQL)을 사용하며 JPA CrudRepository를 사용하여 DB를 조작하는 실습 예제로 JSON 응답과 XML 응답을 사용했다.
n Springbootrest 라는 이름으로 Spring Starter Project를 생성하자.
n package : com.exemple.rest
n 다음 화면에서 SQL의 jpa, mysql 그리고 CORE의 Lombok 선택
n 1. pom.xml
ü spring-boot-starter-parent : 종속성 관리를위한 부모 POM.
ü spring-boot-starter-web : 웹 구축을위한 스타터, REST 애플리케이션. Tomcat 서버를 기본 내장 서버로 사용.
ü spring-boot-starter-data-jpa : Spring Data JPA 사용을 위한 설정
ü spring-boot-devtools : 개발자 도구를 제공, 이 도구는 응용 프로그램 개발 모드에서 유용한데 코드가 변경된 경우 서버를 자동으로 다시 시작하는 일들을 한다.
ü spring-boot-maven-plugin : 응용 프로그램의 실행 가능한 JAR을 만든다.
ü XML 응답을 얻으려면 아래 디펜던시를 추가 해야 한다.
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.4</version>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>rest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springbootrest</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
n 3. src/main/resources/application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/bootrest?createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=1234
spring.datasource.tomcat.max-wait=20000
spring.datasource.tomcat.max-active=50
spring.datasource.tomcat.max-idle=20
spring.datasource.tomcat.min-idle=15
//응용프로그램 시작시 마다 새로 테이블 생성
spring.jpa.hibernate.ddl-auto=create
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect
spring.jpa.properties.hibernate.id.new_generator_mappings=false
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
n 4. Model/Entity 클래스(Emp.java)
package com.example.rest.model;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "emp")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Emp implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer empno;
private String ename;
private Integer sal;
}
5. 예외처리용 클래스(ResourceNotFoundException.java)
package com.example.rest.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
private String resourceName;
private String fieldName;
private Object fieldValue;
public ResourceNotFoundException(String resourceName, String fieldName, Object fieldValue) {
super(String.format("%s not found with %s : '%s'", resourceName, fieldName, fieldValue));
this.resourceName = resourceName;
this.fieldName = fieldName;
this.fieldValue = fieldValue;
}
public String getResourceName() {
return resourceName;
}
public String getFieldName() {
return fieldName;
}
public Object getFieldValue() {
return fieldValue;
}
}
n 6. DB 데이터 조작을 위한 영속성 계층(Persistence Layer) 클래스(EmpRepository.java)
package com.example.rest.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import com.example.rest.model.Emp;
public interface EmpRepository extends JpaRepository<Emp, Integer> {
// 쿼리 메소드, 메소드 이름으로 자동으로 SELECT 쿼리 생성
// JPA에서 자동으로 생성하는 쿼리는 다음과 같다.
// select
// emp0_.empno as empno1_0_,
// emp0_.ename as ename2_0_,
// emp0_.sal as sal3_0_
// from
// emp emp0_
// where
// emp0_.sal between ? and ?
List<Emp> findBySalBetween(int sal1, int sal2);
}
n 7 .서비스 계층 클래스(EmpService.java, EmpServiceImpl.java)
[EmpService.java]
package com.example.rest.service;
import java.util.List;
import com.example.rest.model.Emp;
public interface EmpService {
List<Emp> findAll();
Emp findById(int empno);
void deleteById(int empno);
Emp save(Emp emp);
List<Emp> findBySalBetween(int sal1, int sal2);
void updateById(int empno, Emp emp);
}
[EmpServiceImpl.java]
package com.example.rest.service;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.rest.exception.ResourceNotFoundException;
import com.example.rest.model.Emp;
import com.example.rest.repository.EmpRepository;
@Service
public class EmpServiceImpl implements EmpService {
@Autowired
private EmpRepository empRepository;
@Override
public List<Emp> findAll() {
List<Emp> emps = new ArrayList<>();
empRepository.findAll().forEach(e -> emps.add(e));
return emps;
}
@Override
public Emp findById(int empno) {
Emp emp = empRepository.findById(empno).orElseThrow(() -> new ResourceNotFoundException("Emp", "empno", empno));
return emp;
}
@Override
public void deleteById(int empno) {
empRepository.deleteById(empno);
}
@Override
public Emp save(Emp emp) {
empRepository.save(emp);
return emp;
}
@Override
public List<Emp> findBySalBetween(int sal1, int sal2) {
List<Emp> emps = empRepository.findBySalBetween(sal1, sal2);
System.out.println(emps.size() + ">>>>>>>>>>>>>>>>" + sal1 + sal2);
if (emps.size() > 0)
return emps;
else
return null;
}
@Override
public void updateById(int empno, Emp emp) {
Emp e = empRepository.findById(empno).orElseThrow(() -> new ResourceNotFoundException("Emp", "empno", empno));
e.setEname(emp.getEname());
e.setSal(emp.getSal());
empRepository.save(emp);
}
}
n 8. 컨트롤러(EmpController.java)
package com.example.rest.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.rest.model.Emp;
import com.example.rest.service.EmpService;
@RestController
@RequestMapping("emp")
public class EmpController {
@Autowired
private EmpService empService;
// 모든 사원 조회
@GetMapping(produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<List<Emp>> getAllEmps() {
List<Emp> emps = empService.findAll();
return new ResponseEntity<List<Emp>>(emps, HttpStatus.OK);
}
// empno로 한명의 사원 조회
@GetMapping(value = "/{empno}", produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<Emp> getEmp(@PathVariable("empno") int empno) {
return new ResponseEntity<Emp>(empService.findById(empno), HttpStatus.OK);
}
// empno로 사원 삭제
@DeleteMapping(value = "/{empno}", produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<Void> deleteEmp(@PathVariable("empno") int empno) {
empService.deleteById(empno);
return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
}
// empno로 사원 수정(empno로 사원 찾아 인자로 넘어오는 을 Emp 객체의 ename, sal로 수정함)
@PutMapping(value = "/{empno}", produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<Emp> updateEmp(@PathVariable("empno") int empno, @RequestBody Emp emp) {
empService.updateById(empno, emp);
return new ResponseEntity<Emp>(emp, HttpStatus.OK);
}
// 사원 입력
@PostMapping
public ResponseEntity<Emp> save(@RequestBody Emp emp) {
return new ResponseEntity<Emp>(empService.save(emp), HttpStatus.OK);
}
// 급여를 기준으로 사원 검색 (sal > sal1 and sal < sal2)
@GetMapping(value = "/{sal1}/{sal2}")
public ResponseEntity<List<Emp>> getEmpBySalBetween(@PathVariable int sal1, @PathVariable intsal2) {
List<Emp> emps = empService.findBySalBetween(sal1, sal2);
return new ResponseEntity<List<Emp>>(emps, HttpStatus.OK);
}
}
n 스프링 부트 메인
package com.example.rest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.example.rest.model.Emp;
import com.example.rest.repository.EmpRepository;
// CommandLineRunner의 run을 구현하면 SpringApplication.run 이후
// run 메소드를 실행해 준다.
@SpringBootApplication
public class SpringbootrestApplication implements CommandLineRunner {
@Autowired
EmpRepository empRepository;
public static void main(String[] args) {
SpringApplication.run(SpringbootrestApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
empRepository.save(new Emp(1, "이종철", 9000000));
empRepository.save(new Emp(2, "연개소문", 3000000));
empRepository.save(new Emp(3, "강감찬", 6000000));
empRepository.save(new Emp(4, "이순신", 7000000));
empRepository.save(new Emp(5, "김유신", 2000000));
}
}
n [실행 화면 : 구글 Advanced REST Client]
전체 사원조회(GET, localhost:8080/emp)
1번 사원 조회(GET, localhost:8080/emp/1)
2번 사원 삭제(DELETE, localhost:8080/emp/2)
1번 사원의 이름을 “은하철도” 급여를 “9999999”로 수정(PUT, localhost:8080/emp/1)
고주몽, 급여 7000000 입력(POST, locahost:8080/emp)
급여가 3000000 에서 10000000 사이 사원 출력(GET,localahost:8080/3000000/10000000)
댓글 없음:
댓글 쓰기