본문 바로가기
Programming

스프링 부트 SpringBoot 웹 애플리케이션 개발 #3 CRUD 구현하기

by 하하호호 2022. 3. 31.
반응형

 

CRUD는 웹 서비스의 가장 기본적인 서비스인 생성(Create), 검색(Retrieve), 수정(Update), 삭제(Delete)의 약자다. 사용자의 요청에 따라서 Controller 로직은 URI를 매핑하게 되고, Service 로직에서는 Repository 레이어에 필요한 데이터를 요청하고 반환받는다. 최종적으로 사용자의 요청은 DB의 데이터를 반환받게 된다.

 

스프링 로그 어노테이션 


웹 서버 애플리케이션에서 로그는 필수적이다. info, debug,  warn, error 등의 심각한 정보들은 디버깅을 해주는게 상식적이다. 터미널에 System.out.println()으로 출력을 할 수 있지만 서버 성능에 상당한 부담을 주는 기능이다. 스프링에서는 로그를 사용하기 위해 Slf4j(Simple Logging Facade for Java) 라이브러리를 사용한다. 스프링은 기본적으로 Logback 로그 라이브러리를 사용한다.

 

CRUD #1 Create 로직 구현


퍼시스턴스 구현

JpaRepository를 상속받고, <Entity, Id를 받기 위한 문자열> 파라미터의 Repository를 생성해준다. 엔티티를 저장하기 위해서는 JpaRepository 중 save() 메소드를 사용하게 된다.

package com.example.damo.persistence;

import com.example.damo.model.CrudEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CrudRepository extends JpaRepository<CrudEntity, String> {

}

 

퍼시스턴스 레이어에서 사용할 데이터 클래스 CrudEntity.java

package com.example.damo.model;


import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table
public class CrudEntity {
    @Id
    @GeneratedValue(generator="system-uuid")
    @GenericGenerator(name="system-uuid", strategy = "uuid")
    private String id;
    private String userId;
    private String name;
    private boolean done;
}

 

서비스 구현

로그를 남기기 위해서 Slf4j 어노테이션을 사용한다. 검증 로직은 서비스 레이어로 넘어온 Entity가 유효한 데이터인지 확인한다.  save() 메소드는 JpaRepository 인터페이스에서 제공하는 메소드다. findByUserId() 메소드는 저장된 Entity를 포함하고 있는 새로운 리스트를 반환한다.

 

CrudService.java

package com.example.damo.service;


import com.example.damo.model.CrudEntity;
import com.example.damo.model.TodoEntity;
import com.example.damo.persistence.CrudRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Slf4j
@Service
public class CRUDService {
    @Autowired
    private CrudRepository crudRepository;

    private void validate(final CrudEntity entity){
        // 검증 로직
        if(entity == null){
            log.warn("Entity cannot be null");
            throw new RuntimeException("Entity cannot be null");
        }

        if(entity.getUserId() == null){
            log.warn("Unkwon user");
            throw new RuntimeException("Unkown user");
        }
    }


    public List<CrudEntity> create(final CrudEntity entity){
        // 검증 로직
        validate(entity);

        // DB Create
        crudRepository.save(entity);

        log.info("Entity Id : {} is saved", entity.getId());

        return crudRepository.findByUserId(entity.getUserId());

    }
}

 

 

컨트롤러 구현

클라이언트로 부터 요청받은 HTTP 반환값을 캡슐화 하거나 추가적인 정보를 더 태우기 위해서 DTO(Data Transfer Object)를 사용한다. 사용자에게 넘겨받은 DTO를 비즈니스 로직에서 처리하기 위해 Entity로 변환해야 한다. 

 

CrudDTO.java

package com.example.damo.dto;

import com.example.damo.model.CrudEntity;
import com.example.damo.model.TodoEntity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CrudDTO {
    private String id;
    private String name;
    private boolean done;

    public CrudDTO(final CrudEntity entity){
        this.id = entity.getId();
        this.name = entity.getName();
        this.done = entity.isDone();
    }

    public static CrudEntity toEntity(final CrudDTO dto){
        return CrudEntity.builder().id(dto.getId()).name(dto.getName()).done(dto.isDone()).build();
    }
}

 

 

ResponseDTO.java

package com.example.damo.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class ResponseDTO<T> {
    private String error;
    private List<T> data;
}

 

CrudController.java

매개변수를 받는 방법으로 @RequestBody를 사용했다. String, int 보다는 조금더 복잡한 DTO 오브젝트를 매개변수로 받기 위함이다. 사용자로 부터 넘겨받은 DTO는 즉시 Entity로 변환되어 비즈니스 로직을 작동시킨다. 여기서 반환된 Entity 타입의 리스트들은 DTO 타입의 리스트로 변환되고, 이 DTOs 리스트를 가지고 ResponseDTO를 초기화 하여 최종 클라이언트에게 반환한다.

package com.example.damo.controller;

import com.example.damo.dto.CrudDTO;
import com.example.damo.dto.ResponseDTO;
import com.example.damo.model.CrudEntity;
import com.example.damo.service.CRUDService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.stream.Collectors;

@RestController
@RequestMapping("crud")
public class CRUDController {

    @Autowired
    private CRUDService crudService;

    @GetMapping("/create")
    public String createService(@RequestBody CrudDTO crudDTO){
        List<CrudEntity> entity= crudService.create(CrudDTO.toEntity(crudDTO));
        return "";
    }

    @PostMapping
    public ResponseEntity<?> create(@RequestBody CrudDTO dto){
        try{
            // 임시 사용자 UserId를 생성한다.
            String tempUserId = "temporary-user";

            // 사용자에게 받은 DTO를 Entity로 변환한다.
            CrudEntity entity = CrudDTO.toEntity(dto);

            // 생성 당시에는 id가 없어야 한다. null로 초기화 한다.
            entity.setId(null);

            // 임시 UserId로 설정한다.
            entity.setUserId(tempUserId);

            // 변환된 Entity로 비즈니스 로직을 작동한다.
            // 반환값은 <CrudEntity> 타입의 배열이다.
            List<CrudEntity> entities = crudService.create(entity);

            // 자바 스트림을 사용한다.
            // 반환된 Entity 배열을 DTO 배열로 변환한다.
            List<CrudDTO> dtos = entities.stream().map(CrudDTO::new).collect(Collectors.toList());

            // 반환된 DTO를 사용하여 ResponseDTO를 초기화 한다.
            ResponseDTO<CrudDTO> response = ResponseDTO.<CrudDTO>builder().data(dtos).build();
            return ResponseEntity.ok().body(response);
        }catch(Exception e){
            String error = e.getMessage();
            ResponseDTO<CrudDTO> response = ResponseDTO.<CrudDTO>builder().error(error).build();
            return ResponseEntity.badRequest().body(response);

        }
    }

}

 

퍼시스턴스, 서비스 로직, 컨트롤러를 모두 구성하고 PostMan으로 PUT 메소드에 BODY에는 'name' : 'Developer Blog'를 태워서 요청하게 되면 정상적으로 반환값이 돌아오는 것을 확인할 수 있다.

 

 

In-Memory 형식의 데이터베이스에 실제 테이블이 생성되는데 성공한 것이다.

 

 

CRUD #2 Retrieve 로직 구현


퍼시스턴스 구현

Repository는 Create 로직에서 사용한 코드를 그대로 사용한다.

package com.example.damo.persistence;

import com.example.damo.model.CrudEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface CrudRepository extends JpaRepository<CrudEntity, String> {

    List<CrudEntity> findByUserId(String userId);
}

 

 

서비스 구현

Repository에 정의된 findByUserId() 메소드를 사용해서 retrieve() 메소드를 작성한다. 다른 로직은 없다. repository에서 Entity의 'userID'를 가지고 데이터를 검색 후 반환한다. retrieve() 메소드가 받고 있는 매개변수는 String 데이터 타입의 'userId'다. 컨트롤러에서 넘겨주는 매개변수를 의미한다.

public List<CrudEntity> retrieve(final String userId){
    return crudRepository.findByUserId(userId);
}

 

컨트롤러 구현

컨트롤러에서는 retrieve() 메소드를 구현한다. 클라이언트로 부터 받아오는 매개변수는 없다. 일단 임시적으로 만든 사용자 ID를 가지고 Entity를 검색 후 DTO로 변환하여 ReponseDTO로 반환하는 로직이다. HTTP 메소드는 'GET'을 사용한다. 

package com.example.damo.controller.CrudController;
                        .
                        .
                        .


@GetMapping
public ResponseEntity<?> retrieve(){
	// 임시 사용자 ID를 생성한다.
    String tempUser = "temporary-user";
    
    // Service에 임시사용자 ID를 넘겨서 entity 리스트를 만든다.
    List<CrudEntity> entities = crudService.retrieve(tempUser);
    
    // Entity 리스트를 DTO 리스트로 변환한다.
    List<CrudDTO> dtos = entities.stream().map(CrudDTO::new).collect(Collectors.toList());
    
    // DTO 리스트로 ResponseDTO를 초기화 한다.
    ResponseDTO<CrudDTO> response = ResponseDTO.<CrudDTO>builder().data(dtos).build();
    
    // 클라이언트에게 ResponseDTO를 반환한다.
    return ResponseEntity.ok().body(response);
}

 

PostMan 테스팅

Create 로직에서 사용자를 먼저 생성한 후, GET 메소드로 Body에는 아무것도 태우지 않고 요청을 보낸다. 이미 생성되어 있는 사용자 정보를 리턴하고 있다.

 

CRUD #3 Update 로직 구현


퍼시스턴스 구현

Repository는 Retrieve 로직에서 사용한 코드를 그대로 사용한다.

package com.example.damo.persistence;

import com.example.damo.model.CrudEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface CrudRepository extends JpaRepository<CrudEntity, String> {

    List<CrudEntity> findByUserId(String userId);
}

 

서비스 구현

Entity를 넘겨받아서 Entity의 'userID' 값을 기준으로 Repository에 해당 데이터가 존재하는지 여부를 먼저 확인한다. 만약 존재한다면 매개변수로 받은 Entity의 값으로 database를 update를 하고 이미 구현해 놓은 retrieve() 메소드를 사용해 해당 Entity데이터를 컨트롤러로 반환한다.

public List<CrudEntity> update(final CrudEntity entity){
    // 검증 로직
    validate(entity);

    // 매개변수로 받은 ID를 가지고 CrudEntity를 Repository에서 찾아온다.
    // 존재하지 않는 Entity는 업데이트가 불가능하기 때문에 Optional Class를 이용한다.
    final Optional<CrudEntity> original = crudRepository.findById(entity.getId());

    // 만약 Entity가 존재하면 매개변수로 받은 Entity의 값으로 UPDATE를 진행한다.
    original.ifPresent(crud -> {
        crud.setName(entity.getName());
        crud.setDone(entity.isDone());
        // DB UPDATE를 진행한다.
        crudRepository.save(crud);
    });

    return retrieve(entity.getUserId());
}

 

컨트롤러 구현DTO, Entity는 이전 로직과 동일하게 사용한다. Update 로직에서는 'tempUser'를 임시로 지정해주고, DTO를 매개변수로 받는다. 클라리언트로 부터 받는 DTO에는 id값과, update된 name이 포함되어 있다. DTO를 Entity로 변환해주고, Entity의 UserID를 임시 사용자 이름으로 지정해준다.

 

Service 로직에서 반환받은 List<Entity>를 초기화 하고, Stream() 을 사용해 DTO 타입의 리스트를 초기화 해준다. DTO 타입의 리스트를 가지고 ResponseDTO를 초기화 해주고 ResponseEntity를 반환한다. Update 로직에서는 HTTP PUT 메소드를 사용한다.

@PutMapping
public ResponseEntity<?> update(@RequestBody CrudDTO dto){
    String tempUser = "temporary-user";

    CrudEntity entity = CrudDTO.toEntity(dto);
    entity.setUserId(tempUser);

    List<CrudEntity> entities = crudService.update(entity);
    List<CrudDTO> dtos = entities.stream().map(CrudDTO::new).collect(Collectors.toList());
    ResponseDTO<CrudDTO> response = ResponseDTO.<CrudDTO>builder().data(dtos).build();

    return ResponseEntity.ok().body(response);
}

 

PostMan 테스트

Create 한 사용자가 이미 H2 데이터베이스에 저장되어 있다. 이제 'PUT' 메소드를 가지고, id값과 name값을 body에 태워서 request를 하면 update 된 'name'값을 반환받게 된다.

 

CRUD #4 Delete 로직 구현


퍼시스턴스 구현

Repository는 Update에서 사용했던 코드를 사용한다. Delete 로직에서는 delete() 메소드를 사용한다.

package com.example.damo.persistence;

import com.example.damo.model.CrudEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface CrudRepository extends JpaRepository<CrudEntity, String> {

    List<CrudEntity> findByUserId(String userId);
}

 

서비스 구현

서비스 로직에서 매개변수로 받은 entity를 검증한 후 try~catch문으로 예외처리를 해준다. Exception e는 중요 정보를 담고 있을 수 있기 때문에, 캡슐화를 위해서 RuntimeException을 만들어 던지는 방법을 사용한다.

public List<CrudEntity> delete(final CrudEntity entity){
    // 검증 로직
    validate(entity);

    try{
    	// Entity 삭제
        crudRepository.delete(entity);
    }catch(Exception e){
        log.error("Error while delete Entity", entity.getId(), e);
        // Exception e를 캡슐화 하기 위해서 RuntimeException을 새로 생성해서 컨트롤러로 보내준다.
		//throw new RuntimeException("Error while delete Entity"+ entity.getId());
    }

	// 삭제한 Entity를 검색해서 반환한다.
    return retrieve(entity.getUserId());
}

 

컨트롤러 구현

임시로 UserID를 만든다. DTO를 매개변수로 받아서 entity로 변환한 다음 임시로 지정한 UserID를 setting 해준다. 서비스 로직에 entity를 전달하여 repository에서 삭제하고 반환값을 response로 출력한다. delete로직의 메소드는 HTTP Delete를 사용한다.

 

만약 Entity를 삭제하는 과정에서 예외가 발생 할 경우 error 메세지를 확인해서 ResponseDTO를 초기화 한다. 서버의 응답코드는 400으로 반환된다.

@DeleteMapping
public ResponseEntity<?> delete(@RequestBody CrudDTO dto){
    try{
        String tempUser = "temporary-user";
        CrudEntity entity = CrudDTO.toEntity(dto);
        entity.setUserId(tempUser);

        List<CrudEntity> entities = crudService.delete(entity);
        List<CrudDTO> dtos = entities.stream().map(CrudDTO::new).collect(Collectors.toList());
        ResponseDTO<CrudDTO> response = ResponseDTO.<CrudDTO>builder().data(dtos).build();

        return ResponseEntity.ok().body(response);
    }catch(Exception e){
        String error = e.getMessage();
        ResponseDTO<CrudDTO> response = ResponseDTO.<CrudDTO>builder().error(error).build();
        return ResponseEntity.badRequest().body(response);
    }
}

 

PostMan 테스팅

Create 로직에서 생성한 데이터의 uuid와 name을 바디에 태워서 'Delete' 메소드로 서버에 전송하면 repository에서 해당 데이터가 정상적으로 삭제되는 것을 확인할 수 있다.

 

 

 

더 많은 콘텐츠

 

 

 

SpringBoot 웹 애플리케이션 개발 #2 백엔드 개발

백엔드 서비스 아키텍처란? 레이어드 아키텍처 패턴은 스프링 프로젝트를 분리해서 사용자와 서버 프로그램이 유기적인 소통이 가능하도록 한다. 프로젝트 내부에서 어떻게 코드를 관리할 것

incomeplus.tistory.com

 

 

 

 

 

 

 

 

 

 

 

반응형

댓글