2018년 6월 28일 목요일

스프링부트(Spring Boot) REST CRUD 이론 및 실습(JPA, MariaDB)



6-20-4. Spring Boot REST CRUD 실습(JPA, MariaDB)


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"?>
       <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 sal1int 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 sal1int 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 sal1int sal2) {
             List<Emp> emps = empRepository.findBySalBetween(sal1sal2);
             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 객체의 enamesal 수정함)
       @PutMapping(value = "/{empno}", produces = { MediaType.APPLICATION_JSON_VALUE })
       public ResponseEntity<Emp> updateEmp(@PathVariable("empno"int empno@RequestBody Emp emp) {
             empService.updateById(empnoemp);
             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(sal1sal2);
             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)



댓글 없음:

댓글 쓰기