구리

[Spring Boot] REST API - Java Persistence API 사용 본문

카테고리 없음

[Spring Boot] REST API - Java Persistence API 사용

guriguriguri 2021. 10. 10. 22:24

Rest API에 대하여 인프런에서 들은 강의를 토대로 배운 내용을 정리한 것입니다. 참고로 강의는 Dowon Lee 님의 Spring Boot를 이용한 RESTful Web Services 개발 입니다.

 

목차

더보기

[JPA 사용을 위한 Dependency 추가 및 설정]

[Spring Data JPA를 이용한 Entity 설정과 초기 데이터 생성]

[JPA Service 구현을 위한 Controller, Repository 생성]

[JPA를 이용한 사용자 목록 조회 - GET HTTP Method]

[JPA를 이용한 사용자 추가 및 삭제 - POST/DELETE HTTP Method]

[게시물 관리를 위한 Post Entity와 User Entity와의 관계 설정]

[게시물 조회를 위한 Post Entity와 User Entity와의 관계 설정]

[JPA를 이용한 새 게시물 추가 - POST HTTP Method]

 

[JPA 사용을 위한 Dependency 추가 및 설정]

(1) pom.xml에 dependency 추가

<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
		<groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
		<scope>runtime</scope>
</dependency>

(2) application.yml에 설정 추가

spring:
  jpa:
    show-sql: true

  h2:
    console:
      enabled: true

(3) SecurityConfig 재설정

package com.example.restful.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/h2-consloe").permitAll();
        http.csrf().disable();
        http.headers().frameOptions().disable();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth)
        throws Exception {
        auth.inMemoryAuthentication()
                .withUser("raccoon")
                .password("{noop}test12")   // noop : 어떤 인코딩도 없이 사용할 수 있는 no operation
                .roles("USER");
    }
}

(4) h2 database 접속 

  • localhost:8088/h2-console 에 접속
  • mem : memory DB (현재 어플리케이션이 기동되는 동안에만 유지되는 DB)
  • 어플리케이션이 종료되도 유지되는 DB를 원한다면 TCP 방식이 지원되는 드라이버 방식으로 변경하기

접속완료

 

[Spring Data JPA를 이용한 Entity 설정과 초기 데이터 생성]

  • User 도메인 재정의 (엔티티 설정 및 기본키 자동 생성)
package com.example.restful.user;

import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.validation.constraints.Past;
import javax.validation.constraints.Size;
import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(description = "All details about the user")
@Entity
public class User {
    @Id
    @GeneratedValue
    private Integer id;

    @Size(min=2, message = "Name은 2글자 이상 입력해주세요.")
    @ApiModelProperty(notes = "사용자 이름을 입력해주세요")
    private String name;

    @Past
    @ApiModelProperty(notes = "사용자의 등록일을 입력해주세요")
    private Date joinDate;

    @ApiModelProperty(notes = "사용자의 패스워드를 입력해주세요")
    private String password;

    @ApiModelProperty(notes = "사용자의 주민번호를 입력해주세요")
    private String ssn;
}

@Entity로 인해 table 자동 생성
User 테이블 생성 확인 가능 

  • resource > data.sql 파일 생성 후 insert  쿼리문 작성, 그 후 서버 재구동
insert into user values(1, sysdate(), 'User1', 'tset1', '701010-2222222');
insert into user values(2, sysdate(), 'User2', 'tset2', '800707-2222222');
insert into user values(3, sysdate(), 'User3', 'tset3', '901212-2222222');

insert 쿼리문이 실행된 콘솔창 결과
DB 재접속시 데이터가 저장된 결과를 볼 수 있다

 

[JPA Service 구현을 위한 Controller, Repository 생성]

  • JpaRepository 를 이용하여 전체 사용자 목록 조회 메소드 생성

(1) UserRepository Interface 생성

package com.example.restful.user;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
}

(2) UserJpaController에서 UserRepository를 이용한 전체 사용자 조회 메소드 구현

package com.example.restful.user;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/jpa")
public class UserJpaController {
    @Autowired
    private UserRepository repository;

    @GetMapping("/users")
    public List<User> retrieveAllUser(){
        return repository.findAll();
    }
}

data.sql에서 설정했던 insert 쿼리문이 실행되면서 저장된 데이터를 정상적으로 반환하는 것을 볼 수 있다

 

[JPA를 이용한 사용자 개별 조회 - GET HTTP Method]

  • primary key 값을 이용하여 개별 사용자 조회
package com.example.restful.user;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.mvc.ControllerLinkBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Optional;

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;

@RestController
@RequestMapping("/jpa")
public class UserJpaController {
    ... 중략

    @GetMapping("/users/{id}")
    public Resource<User> retrieveUser(@PathVariable int id){
        Optional<User> user = repository.findById(id);
        if(!user.isPresent()){
            throw new UserNotFoundException(String.format("ID[%s] not found", id));
        }
        // HATEOAS 사용
        Resource<User> resource = new Resource<>(user.get());
        ControllerLinkBuilder linkTo = linkTo(methodOn(this.getClass()).retrieveAllUser());
        resource.add(linkTo.withRel("all-users"));

        // return user.get();
        return resource;
    }
}

HATEOAS를 이용해 개별 사용자 조회 및 전체 사용자를 조회할 수 있는 링크도 같이 반환

 

[JPA를 이용한 사용자 추가 및 삭제 - POST/DELETE HTTP Method]

  • 회원 정보를 삭제하는 deleteUser 메소드 구현
package com.example.restful.user;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.mvc.ControllerLinkBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import javax.validation.Valid;
import java.net.URI;
import java.util.List;
import java.util.Optional;

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;

@RestController
@RequestMapping("/jpa")
public class UserJpaController {
   ... 중략
   
    @DeleteMapping("/users/{id}")
    public void deleteUser(@PathVariable int id){
        repository.deleteById(id);
    }
}

delete 실행 후 정상 status 확인
전체 사용자 조회시 1번 사용자 삭제된 것을 확인할 수 있음

  • User 데이터 생성하는 HTTP POST 메소드 생성
package com.example.restful.user;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.mvc.ControllerLinkBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import javax.validation.Valid;
import java.net.URI;
import java.util.List;
import java.util.Optional;

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;

@RestController
@RequestMapping("/jpa")
public class UserJpaController {
    ... 중략

    @PostMapping("/users")
    public ResponseEntity<User> createUser(@Valid @RequestBody User user){
        User savesUser = repository.save(user);

	// 사용자에게 요청 값을 변환해주기 
        // fromCurrentRequest() :현재 요청되어진 request값을 사용한다는 뜻 
        // path : 반환 시켜줄 값 
        // savedUser.getId() : {id} 가변변수에 새롭게 만들어진 id값 저장 
        // toUri() : URI형태로 변환
        URI location = ServletUriComponentsBuilder.fromCurrentRequest()
                .path("/{id}")
                .buildAndExpand(savesUser.getId())
                .toUri();
        return ResponseEntity.created(location).build();
    }
}

데이터를 생성했으나 서버 오류(sql) 발생
에러 원인

  • User domain class에서 id(primary key) 값을 자동으로 생성해주는 sequence로 설정해놓음
  • 하지만 이전에 insert sql문에서 1번 데이터가 저장된 상태기에 sequence를 통해 id="1" 로 설정한 값을 추가하면 중복으로 인해 오류 발생
  • 초기 데이터 값을 JPA와 상관없이 등록시켰기에 => 임의의 데이터 (큰 값)으로 수정해놓기
insert into user values(100001, sysdate(), 'User1', 'tset1', '701010-2222222');
insert into user values(100002, sysdate(), 'User2', 'tset2', '800707-2222222');
insert into user values(100003, sysdate(), 'User3', 'tset3', '901212-2222222');

 

 

  • Header 값을 조회하면 지금 추가한 데이터 조회(사용)하기 위해 어떤 key 값을 사용해야 하는지 알려줌 (http://localhost:8088/jpa/user/1) -> ResponseEntity 를 사용했기 때문

 

 

 

[게시물 관리를 위한 Post Entity와 User Entity와의 관계 설정]

  • Post : User = N : 1 의 관계를 Post Entity 추가하여 관계 설정 (양방향)

(1) Post Entity class 생성

  • @ManyToOne 어노테이션에서 fetch = FetchType.LAZY 속성값 의미 : 지연로딩 방식으로 연관 관계에 있는 Entity를 가져오지 않고, getter로 접근시 가져옴 <-> Eager
package com.example.restful.user;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Post {
    @Id
    @GeneratedValue
    private Integer id;

    private String description;

    @JsonIgnore // 데이터 주고 받을 때 해당 데이터는 Ingonre되어서 응답값에 보이지 않음
    @ManyToOne(fetch = FetchType.LAZY)  // 지연로딩 방식 : 연관 관계에 있는 Entity를 가져오지 않고, getter로 접근시 가져옴 <-> Eager
    private User user;
}

(2) User domain class에 Post 칼럼 추가 및 연관관계 설정 및 새로운 생성자 추가

  • Post 객체가 연관관계의 주인이므로 User 도메인에서는 @OneToMany에 mappedBy 추가
package com.example.restful.user;

import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.validation.constraints.Past;
import javax.validation.constraints.Size;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
// 원하는 이름으로 설정 (컴트롤러나 서비스 클래스에서 사용)
//@JsonFilter("UserInfo")
@ApiModel(description = "All details about the user")
@Entity
public class User {
    @OneToMany(mappedBy = "user")
    private List<Post> posts;

    @Id
    @GeneratedValue
    private Integer id;

    @Size(min=2, message = "Name은 2글자 이상 입력해주세요.")
    @ApiModelProperty(notes = "사용자 이름을 입력해주세요")
    private String name;

    @Past
    @ApiModelProperty(notes = "사용자의 등록일을 입력해주세요")
    private Date joinDate;

    @ApiModelProperty(notes = "사용자의 패스워드를 입력해주세요")
    private String password;

    @ApiModelProperty(notes = "사용자의 주민번호를 입력해주세요")
    private String ssn;

    public User(int id, String name, Date joinDate, String password, String ssn) {
        this.id = id;
        this.name = name;
        this.joinDate = joinDate;
        this.password = password;
        this.ssn = ssn;
    }
}

(3) data.sql에서 post 초기 데이터값 설정

insert into user values(100001, sysdate(), 'User1', 'tset1', '701010-2222222');
insert into user values(100002, sysdate(), 'User2', 'tset2', '800707-2222222');
insert into user values(100003, sysdate(), 'User3', 'tset3', '901212-2222222');

insert into post values(10001, 'My first post', 100001);
insert into post values(10002, 'My second post', 100002);

(4) 데이터 추가 확인

 

User,Post 모두 insert 쿼리문 정상 실행
DB에도 정상적으로 데이터 저장 완료

 

[게시물 조회를 위한 Post Entity와 User Entity와의 관계 설정]

  • UserJpaController에 특정 사용자 게시물 조회 메소드 추가
// UserJpaController
@GetMapping("/users/{id}/posts")
    public List<Post> retrieveAllPostsByUser(@PathVariable int id){
        Optional<User> user = repository.findById(id);
        if(!user.isPresent()){
            throw new UserNotFoundException(String.format("ID[%s] not found", id));
        }
        return user.get().getPosts();
    }

정상적으로 조회되는 것을 확인할 수 있음
전체 사용자 조회시 post에 대한 정보도 조회 가능

 

[JPA를 이용한 새 게시물 추가 - POST HTTP Method]

  • PostRepository 생성
package com.example.restful.user;

import org.springframework.data.jpa.repository.JpaRepository;

public interface PostRepository extends JpaRepository<Post, Integer> {
}
  • UserJpaController 메소드 추가 (사용자 정보를 검색한 후 그 정보의 id 값을 post에 지정)
@PostMapping("/users/{id}/posts")
    public ResponseEntity<User> createPost(@RequestBody Post post, @PathVariable int id){
        Optional<User> user = repository.findById(id);
        if(!user.isPresent()){
            throw new UserNotFoundException(String.format("ID[%s] not found", id));
        }
        post.setUser(user.get());
        Post savedPost = postRepository.save(post);

        // 사용자에게 요청 값을 변환해주기
        // fromCurrentRequest() :현재 요청되어진 request값을 사용한다는 뜻
        // path : 반환 시켜줄 값
        // savedUser.getId() : {id} 가변변수에 새롭게 만들어진 id값 저장
        // toUri() : URI형태로 변환
        URI location = ServletUriComponentsBuilder.fromCurrentRequest()
                .path("/{id}")
                .buildAndExpand(savedPost.getId())
                .toUri();
        return ResponseEntity.created(location).build();
    }