GDSC web - Spring 서비스 코드 작성

이유석·2022년 12월 9일
1

gdsc

목록 보기
3/6
post-thumbnail
post-custom-banner

카드뉴스

Annotation 이란?

  • 사전적 의미로 주석이라는 뜻 입니다.
  • 프로그램에게 추가적인 정보를 제공해주는 메타데이터 이며 특별한 의미 및 기능을 수행하도록 하는 기술 입니다.

    메타데이터 : 데이터를 위한 데이터

기능

  • 컴파일러에게 코드 작성 문법 에러를 체크하도록 정보를 제공
    ex) @Valid, @NotNull

  • 소프트웨어 개발툴이 빌드나 배치 시, 코드를 자동으로 생성 할 수 있도록 정보를 제공
    ex) @Getter, @NoArgsConstructor

  • 실행 시, 특정 기능을 수행하도록 정보를 제공
    ex) @Transactional, @ControllerAdvice

Anntation 예제 - @Getter

@Getter Annotation을 사용하지 않은 코드

public class ExampleDto {

	private String title;
    
    public String getTitle() {
    	return title;
    }
    
}

@Getter Annotation을 사용한 코드

@Getter
public class ExampleDto {

	private String title;

}

해당 코드는 위 코드와 동일하게 동작하며, 코드의 줄이 줄어드는 것 을 확인할 수 있다.

Entity 코드 작성

Entity

  • Database 의 테이블 및 테이블에 쓰일 필드, 다른 테이블과의 연관관계 등 을 정의합니다.

Entity class 에 쓰이는 Annotation

  • @Entity
    class 위에 선언하여, 해당 class가 Entity 임을 나타내어 줍니다.
    해당 class 에는 @Id 를 사용하여 Primary Key를 필수적으로 선택하여야 합니다.

  • @Id
    class의 필드 위에 선언하여, 해당 필드가 해당 Entity 의 주요 키(Primary Key, PK)가 될 것 임을 지정해줍니다.

  • @GeneratedValue
    PK 필드 값의 생성 전략을 설정해주기 위하여 사용합니다.
    GenerationType 종류

    • TABLE
      데이터베이스에 키 생성 전용 테이블을 하나 만들고 이를 사용하여 기본키를 생성합니다.

    • SEQUENCE
      데이터베이스의 특별한 오브젝트 시퀀스를 사용하여 기본키를 생성합니다.

      SEQUENCE : 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트

    • IDENTITY
      기본키 생성을 데이터베이스에 위임합니다. 즉, 데이터베이스의 기본키 생성(번호 증가) 전략을 따라갑니다.

    • AUTO
      JPA 구현체가 자동으로 생성 전략을 결정합니다.

Entity 코드

gdsc.blog.domain.Post.java

@Data // Getter, Setter 생성
@NoArgsConstructor // 파라미터가 없는 기본 생성자를 생성해줍니다.
@AllArgsConstructor // 모든 필드 값을 파라미터로 받는 생성자를 생성해줍니다.
@Builder // builder 메서드를 자동 생성해줍니다.
@Entity // SpringContainer 에 Bean으로 등록해주는 @Component 를 포함하고 있습니다.
public class Post {

	@Id // PK 를 해당 변수로 하겠다는 뜻.
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    // 해당 데이터베이스 번호 증가 전략을 따라가겠다는 뜻.
    private Long id;
    
    private String title;
    
    @Column(length = 5000) // 최대 길이 설정
    private String content;
    
}

Repository 코드 작성

Repository

정의

  • Entity에 의해 생성된 DB 테이블에 접근하는 메서드(CRUD)들을 사용하기 위한 인터페이스

특징

  • CrudRepository 를 상속받은 JpaRepository 를 상속받도록 함으로써 기본적인 동작(CRUD)이 모두 가능해진다.

  • JpaRepository는 어떤 엔티티를 메서드의 대상으로 할지를 아래의 키워드로 지정한다.
    JpaRepository<대상으로 지정할 엔티티, 해당 엔티티의 PK의 타입>

위 사진은, JpaRepository 의 상속 관계를 보여준다.

JpaRepository

  • 기본적인 CRUD 기능 + Paging, Sorting + Jpa 특화 기능(flushing, 배치성 작업)
public inerface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID> {

   ...

   void flush();

   <S extends T> S saveAndFlush(S entity);

   ...

}

PagingAndSortingRepository

  • 기본적인 CRUD 기능 + Paging, Sorting 기능
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {

  Iterable<T> findAll(Sort sort);

  Page<T> findAll(Pageable pageable);
}

CrudRepository

  • 기본적인 CRUD 기능
public interface CrudRepository<T, ID> extends Repository<T, ID> {

  <S extends T> S save(S entity);

  <S extends T> Iterable<S> saveAll(Iterable<S> entities);

  Optional<T> findById(ID id);

  boolean existsById(ID id);

  Iterable<T> findAll();

  Iterable<T> findAllById(Iterable<ID> ids);

  long count();

  void deleteById(ID id);

  void delete(T entity);

  void deleteAllById(Iterable<? extends ID> ids);

  void deleteAll(Iterable<? extends T> entities);

  void deleteAll();
}

Repository 코드

gdsc.blog.repository.PostRepository.java

public interface PostRepository extends JpaRepository<Post, Long> {

}

Service 코드 작성

Service

  • Repository 로부터 얻어온 데이터를 다시 가공 후, Controller 를 통해 사용자에게 보내준다.

  • 사용자가 Controller 에게 보내온 요청 데이터를 가공하여, Repository를 통해 Database에 저장, 수정, 또는 삭제한다.

위 사진은, 전체적인 데이터 교환 흐름이다. 각 계층은 DTO 라는 객체를 통해서 데이터를 주고 받는다.

DTO (Data Transfer Object)

  • 계층간 데이터 교환을 위해 사용되는 객체(class)

Entity class 와 형태가 거의 유사하지만, DTO class 를 추가로 생성하는 이유

  • Entity class 가 Database 와 밀접한 핵심 class 이기 때문
  • 다양한 비즈니스 로직과 요구 사항에 유연하게 대응하기 위해
  • 사용자가 원하는 데이터가 기존 Entity 의 형태와 다를 수 있기 때문

DTO 코드
gdsc.blog.dto.post.WritePostReq.java

@Getter
@Builder
pubilc class WritePostReq {

	// Swagger 문서에 명시하기 위한 Annotation
    @ApiModelProperty(example = "게시글 제목")
    private String title;
    
    @ApiModelProperty(example = "게시글 내용")
    private String content;

}

Service 코드

gdsc.blog.service.PostService.java

  • Controller 로부터 전달받은 데이터를 가공하여, Repository를 통해 Database에 CRUD 연산을 적용한다.
    • PostRepository의 메서드를 사용하기 위해, 객체를 생성 또는 입력 받아야 한다.
      Spring 에서는 이를 의존성 주입을 통해, 생성된 객체에 접근한다.
    • 필요 메서드 : 단건 생성, 전체 조회, 단건 조회, 단건 수정, 단건 삭제
@Service // 해당 클래스를 Spring Container에 빈으로 등록 후, Spring MVC 서비스로 표시
@RequiredArgsConstructor // final 또는 @NotNull 이 붙은 필드의 생성자를 자동으로 생성해준다.
public class PostService {

    // Spring Container에 싱글톤으로 생성되고 관리되는, 
    // PostRepository Bean 을 의존성 주입 받는다.
    private final PostRepository postRepository;


}

PostService - 단건 생성

  • save
    • Parameter : WritePostReq
    • Return : Post(저장된 객체)
@Service 
@RequiredArgsConstructor 
public class PostService {

    ...
    @Transactional // 해당 함수 종료 시, commit 또는 Rollback 수행 (트랜잭션 관리)
    public Post save(WritePostReq writePostReq) {
       Post post = Post.builder()
               .title(writePostReq.getTitle())
               .content(writePostReq.getContent()).build();
       return postRepository.save(post);
    }

}

PostService - 전체 조회

  • findAll
    • Parameter : 없음
    • Return : List<Post>
@Service 
@RequiredArgsConstructor 
public class PostService {

    ...
    @Transactional(readOnly = true)
    // JPA 변경감지(Database의 객체 필드값의 변경을 감지하는 내부 기능) Off
    public List<Post> findAll() {
       return postRepository.findAll();
    }
   
}

PostService - 단건 조회

  • findById
    • Parameter : Long (Post 객체의 PK)
    • Return : Post
@Service 
@RequiredArgsConstructor 
public class PostService {

    ...
    @Transactional(readOnly = true) 
    public Post findById(Long id) {
       return postRepository.findById(id)
               .orElseThrow(() -> new NoSuchElementException("id를 확인해주세요!!"));
    }

}

PostService - 단건 수정

  • updateById
    • Parameter : Long, WritePostReq
    • Return : Post
@Service 
@RequiredArgsConstructor 
public class PostService {

    ...
    // 해당 서비스 함수 종료 시 => 트랜잭션 종료 시 => 영속화 되어있는 데이터를 DB로 갱신 (flush) => DB에 commit
    @Transactional
    public Post updateById(Long id, WritePostReq writePostReq) {
       Post postEntity = postRepository.findById(id)
               .orElseThrow(()->new NoSuchElementException("id를 확인해주세요!!"));

       postEntity.setTitle(writePostReq.getTitle());
       postEntity.setContent(writePostReq.getContent());

       return postEntity;
    } 
}

영속성 컨텍스트

  • Java (객체 지향 프로그래밍) 과 RDB (관계형 데이터베이스) 사이의 간극을 메우기 위해 ORM(Object Relational Mapping) 이라는 개념 등장
  • ORM 에서는 영속성 컨텍스트 라는 일종의 논리적인 임시 저장소 공간을 통해 관계형 데이터를 객체 (Object)로 매핑하여 관리
  • 즉, 영속화란 관계형 데이터를 영속성 컨텍스트에 임시적으로 저장 후, 객체로 매핑하여 관리하는 것
  • 신규 엔티티 영속화, 데이터 캐싱, 변경 감지, 트랜잭션 쓰기 지연 등의 특징을 갖고 있음.

PostService - 단건 삭제

  • deleteById
    • Parameter : Long
    • Return : String (삭제 성공 여부)
@Service 
@RequiredArgsConstructor 
public class PostService {

    ...
    @Transactional
    public String deleteById(Long id) {
       try {
           postRepository.deleteById(id);
       } catch (EmptyResultDataAccessException e) {
           throw new NoSuchElementException("id를 확인해주세요!!");
       }
 
       return "ok";
    }
}

Controller 코드 작성

Controller

  • Repository 로부터 얻어온 데이터를 다시 가공하여, Controller 를 통해 사용자에게 보내준다.

  • 사용자가 Controller 에게 보내온 데이터를 가공하여, Repository를 통해 Database 에 저장, 수정, 또는 삭제한다.

gdsc.blog.controller.PostController.java

  • 사용자가 화면단(View)에서 입력 또는 이벤트를 발생시켰을 경우에, 해당 이벤트에 맞는 화면(View) 또는 비즈니스 로직(Model) 을 실행할 수 있도록 함
    • PostSerivce 의 메서드를 사용하기 위해, 객체를 생성 또는 입력 받아야 한다.
      Spring 에서는 이를 의존성 주입을 통해, 생성된 객체에 접근한다.
    • 필요 컨트롤러 : 단건 생성, 전체 조회, 단건 조회, 단건 수정, 단건 삭제
gdsc/blog/controller/PostController.java


// final 또는 @NotNull 이 붙은 필드의 생성자를 자동으로 생성해준다.
@RequiredArgsConstructor 
// CORS(교차 출처 자원 공유) 해결해주기.
@CrossOrigin(origins = "*", allowedHeaders = "*") 
// 공통적인 url은 class에 @RequestMapping으로 설정해준다.
@RequestMapping("/post") 
// @Controller + @ResponseBody (Java 객체를 HTTP 요청의 Body 내용으로 매핑하여 반환한다.)
@RestController 
public class PostController {

    private final PostService postService;

}

PostController - 단건 생성

  • save
    • HTTP Method : POST, URL : /post
    • RequestBody : WritePostReq
    • Response : Post (저장된 객체)
...
public class PostController {

    ...
    @ApiOperation(value = "게시글을 등록", notes = "게시글을 등록합니다.") // Swagger 설명 설정
    @PostMapping("")
    public ResponseEntity<Post> save(@RequestBody WritePostReq writePostReq) {
       return new ResponseEntity<>(postService.save(writePostReq), HttpStatus.CREATED);
    }

}

PostController - 전체 조회

  • findAll
    • HTTP Method : GET, URL : /post
    • Response : List
...
public class PostController {

    ...
    @ApiOperation(value = "게시글 전체 조회", notes = "게시글을 전체 조회합니다.")
    @GetMapping("")
    public ResponseEntity<List<Post>> findAll() {
       return new ResponseEntity<>(postService.findAll(), HttpStatus.OK);
    }

}

PostController - 단건 조회

  • findById
    • HTTP Method : GET, URL : /post/{id}
    • PathVariable : Long
    • Response : Post
...
public class PostController {

    ...
    @ApiImplicitParam(name = "id", value = "게시글 아이디")
    @ApiOperation(value = "게시글 단건 조회", notes = "게시글 id를 이용하여 단건 조회합니다.")
    @GetMapping("/{id}")
    public ResponseEntity<Post> findById(@PathVariable Long id) {
       return new ResponseEntity<>(postService.findById(id), HttpStatus.OK);
    }
}

PostController - 단건 수정

  • updateById
    • HTTP Method : PUT, URL : /post/{id}
    • PathVariable : Long, RequestBody : WritePostReq
    • Response : Post
...
public class PostController {

    ...
    @ApiImplicitParam(name = "id", value = "게시글 아이디")
    @ApiOperation(value = "게시글 단건 수정", notes = "id에 해당하는 게시글을 수정합니다.")
    @PutMapping("/{id}")
    public ResponseEntity<Post> updateById(@PathVariable Long id, @RequestBody WritePostReq writePostReq) {
       return new ResponseEntity<>(postService.updateById(id, writePostReq), HttpStatus.OK);
    }
}

PostController - 단건 삭제

  • deleteById
    • HTTP Method : DELETE, URL : /post/{id}
    • PathVariable : Long
    • Response : String (삭제 성공 여부)
...
public class PostController {

    ...
    @ApiImplicitParam(name = "id", value = "게시글 아이디")
    @ApiOperation(value = "게시글 단건 삭제", notes = "id에 해당하는 게시글을 삭제합니다.")
    @DeleteMapping("/{id}")
    public ResponseEntity<String> deleteById(@PathVariable Long id) {
       return new ResponseEntity<>(postService.deleteById(id), HttpStatus.OK);
    }
}

예외 처리

@RestControllerAdvice

  • @ControllerAdvice + @ResponseBody
    예외 처리를 담당하는 @ControllerAdvice 와 HTTP Response Body 로 결과값을 전달하기 위한 @ResponseBody 가 합쳐진 Annotation 입니다.

  • @ControllerAdvice
    클래스에 선언하게 되면, 모든 Controller 에 대하여 전역적으로 발생할 수 있는 예외를 잡아서 처리해주는 방식 입니다.

  • @ExceptionHandler
    메서드 단에 선언하여, 특정 예외 클래스에 대한 예외 처리를 할 수 있습니다.

ErrorResponse - 예외 처리 DTO

gdsc.blog.dto.ErrorResponse.java

@Getter
@Builder
public class ErrorResponse {

   private String code;

   private String message;

}

NoSuchElementException 처리

gdsc.blog.advice.GlobalExceptionHandler.java

@RestControllerAdvice
public class GlobalExceptionHadler {

    // 특정 예외 클래스 발생시 실행되도록 설정
    @ExceptionHandler({NoSuchElementException.class})
    protected ResponseEntity<ErrorResponse> handleNoSuchElementFoundException(NoSuchElementException e) {
       final ErrorResponse errorResponse = ErrorResponse.builder()
               .code("Item Not Found")
               .message(e.getMessage()).build();

       return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
    }

}
profile
소통을 중요하게 여기며, 정보의 공유를 통해 완전한 학습을 이루어 냅니다.
post-custom-banner

0개의 댓글