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

17
6-20-4. Spring Boot REST CRUD 실습(JPA, MariaDB) GitHub : https://github.com/leejongcheol/springbootrest 스프링 부트에서 REST(REpresentational State Transfer) API를 실습해 보자. RESTful 웹 서비스는 JSON, XML 및 기타 미디어 유형을 생성하고 활용할 수 있다. Spring 또는 Spring Boot 기반의 RESTful 웹 서비스 만들려면 @RestController로 스프링 컨트 롤러를 만들어야 한다. 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> 스프링 부트는 기본적으로 Jackson JSON 라이브러리, 즉 jackson-databind를 구성하며, 스프 링 부트 RESTful 웹 서비스는 classpath에서 Jackson JSON 라이브러리를 감지하면 JSON 응답 을 생성하고 Jackson XML 라이브러리를 감지하면 XML 응답을 생성한다. (Jackson XML 라이 브러리의 경우, jackson-dataformat-xml을 빌드 파일에 포함시켜야 한다.) 스프링 부트, REST 예제를 작성하는데 마리아 DB(MySQL)을 사용하며 JPA CrudRepository를 사용하여 DB를 조작하는 실습 예제로 JSON 응답과 XML 응답을 사용했다. Springbootrest 라는 이름으로 Spring Starter Project를 생성하자. package : com.exemple.rest 다음 화면에서 SQL의 jpa, mysql 그리고 CORE의 Lombok 선택 1. pom.xml spring-boot-starter-parent : 종속성 관리를위한 부모 POM. spring-boot-starter-web : 웹 구축을위한 스타터, REST 애플리케이션. Tomcat 서버를 기본

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

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

GitHub : https://github.com/leejongcheol/springbootrest

스프링 부트에서 REST(REpresentational State Transfer) API를 실습해 보자.

RESTful 웹 서비스는 JSON, XML 및 기타 미디어 유형을 생성하고 활용할 수 있다.

Spring 또는 Spring Boot 기반의 RESTful 웹 서비스 만들려면 @RestController로 스프링 컨트

롤러를 만들어야 한다.

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>

스프링 부트는 기본적으로 Jackson JSON 라이브러리, 즉 jackson-databind를 구성하며, 스프

링 부트 RESTful 웹 서비스는 classpath에서 Jackson JSON 라이브러리를 감지하면 JSON 응답

을 생성하고 Jackson XML 라이브러리를 감지하면 XML 응답을 생성한다. (Jackson XML 라이

브러리의 경우, jackson-dataformat-xml을 빌드 파일에 포함시켜야 한다.)

스프링 부트, REST 예제를 작성하는데 마리아 DB(MySQL)을 사용하며 JPA CrudRepository를

사용하여 DB를 조작하는 실습 예제로 JSON 응답과 XML 응답을 사용했다.

Springbootrest 라는 이름으로 Spring Starter Project를 생성하자.

package : com.exemple.rest

다음 화면에서 SQL의 jpa, mysql 그리고 CORE의 Lombok 선택

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>

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

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;

}

}

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); }

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); } }

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 int sal2) { List<Emp> emps = empService.findBySalBetween(sal1, sal2); return new ResponseEntity<List<Emp>>(emps, HttpStatus.OK); } }

스프링 부트 메인

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));

}

}

[실행 화면 : 구글 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)