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-bootstarter-web 을포함시켜야한다. <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> 위디펜던시 (Maven 종속성 ) 는기본적으로 Jackson JSON 라이브러리, 즉 jackson-databind 를가지고오도록하며 Spring Boot REST 는클래스패스에서 jackson-databind 를감지하기때문에기본적으로 JSON 응답을제공한다. Spring Boot REST 에서 XML 응답을지원하려면 spring-boot-starter-web 과함께 jacksondataformat-xml 라이브러리를제공해야한다. <groupid>com.fasterxml.jackson.dataformat</groupid> <artifactid>jackson-dataformat-xml</artifactid> <version>2.9.4</version> 스프링부트는기본적으로 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 응답을얻으려면아래디펜던시를추가해야한다. <groupid>com.fasterxml.jackson.dataformat</groupid> <artifactid>jackson-dataformat-xml</artifactid> <version>2.9.4</version> <?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> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-data-jpa</artifactid> <groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-web</artifactid> <groupid>mysql</groupid> <artifactid>mysql-connector-java</artifactid> <groupid>org.projectlombok</groupid> <artifactid>lombok</artifactid> <optional>true</optional> <groupid>javax.xml.bind</groupid> <artifactid>jaxb-api</artifactid> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-devtools</artifactid> <optional>true</optional> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-test</artifactid> <scope>test</scope> </dependencies> <build> <plugins> <plugin> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-mavenplugin</artifactid> </plugin> </plugins> </build> </project> 3. src/main/resources/application.properties spring.datasource.url=jdbc:mysql://localhost:3306/bootrest?createdatabasei fnotexist=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.mariadbdiale ct 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)