스프링부트 구조 Controller, Service, DAO, Repository, DTO, Entity
개념 스터디

스프링부트 구조 Controller, Service, DAO, Repository, DTO, Entity

스프링부트의 구조와 요청 흐름은 이러하다. 위 사진처럼 회사나 개발 그룹 규정에 따라 서비스와 DAO 사이에서 엔티티로 데이터를 전달하는 것을, 아래 사진처럼 DTO로 전달하기도 한다

💡 Controller

컨트롤러는 클라이언트로부터 요청을 받고 해당 요청에 대해 서비스 레이어에 구현된 적절한 메소드를 호출해서 결괏값을 받는다. 클라이언트로부터 들어오는 HTTP 요청을 받아서 처리하고, 그에 따른 결과를 HTTP 응답으로 반환하는 역할을 한다.

@RestController
@RequestMapping("/product")
public class ProductController {
    private final ProductService productService;

    @Autowired
    public ProductController(ProductService productService){
        this.productService = productService;
    }

    @GetMapping()
    public ResponseEntity<ProductResponseDto> getProduct(Long number){
        ProductResponseDto productResponseDto = productService.getProduct(number);

        return ResponseEntity.status(HttpStatus.OK).body(productResponseDto);
    }
    @PostMapping()
    public ResponseEntity<ProductResponseDto> createProduct(@RequestBody ProductDto productDto){
        ProductResponseDto productResponseDto = productService.saveProduct(productDto);

        return ResponseEntity.status(HttpStatus.OK).body(productResponseDto);
    }
}

🔍 @RestController

Json 형태로 객체 데이터를 반환한다

🔍 @RequestMapping

요청 경로와 해당 요청을 처리하는 메소드를 매핑할 때 사용한다.

🔍@Autowired

@Autowired 어노테이션을 통해 의존성을 주입할 수 있다.

🔍 @GetMapping, PostMapping

각각 HTTP GET, POST 요청을 처리하는데 사용된다.

🔍 @RequestBody

요청의 피라미터를 받아오기 위해 사용하며 HTTP의 Body 내용을 해당 어노테이션이 지정된 객체에 매핑하는 역할을 한다. 이를 통해 클라이언트가 전달한 데이터를 추출하고, 해당 데이터를 처리할 수 있다.

💡 엔티티(Entity)

Spring Data JPA를 사용하면 데이터베이스에 테이블을 생성하기 위해 직접 쿼리를 작성할 필요가 없는데 이를 가능하게 하는 기능이 엔티티이다. JPA에서 엔티티는 데이터베이스의 테이블에 대응하는 클래스로, 데이터베이스에 쓰일 테이블과 칼럼을 생성하고 정의한다

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "product")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long number;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private Integer price;

    @Column(nullable = false)
    private Integer stock;

    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;


}

🔍 @Entity

해당 클래스가 엔티티임을 명시하기 위한 어노테이션. 클래스 자체는 테이블과 일대일로 매팅되며 해당 클래스의 인스턴스는 매핑되는 테이블에서 하나의 레코드를 의미한다.

🔍 @Table

클래스의 이름과 테이블의 이름을 다르게 지정해야 하는 경우에 사용한다. @Table 어노테이션을 명시하지 않으면 테이블의 이름과 클래스의 이름이 동일하다는 의미이다. 

🔍 @Id

@Id 어노테이션이 선언된 필드는 테이블의 기본값 역할로 사용된다. 모든 엔티티는 @Id 어노테이션이 꼭 있어야한다.

🔍 @GeneratedValue

@Id 어노테이션과 함께 사용된다. 해당 필드의 값을 어떤 방식으로 자동으로 생성할지 결정할 때 사용한다.

  • Auto: @GeneratedValue의 기본 설정밗, 기본값을 사용하는 데이터베이스에 맞게 자동 생성
  • IDENTITY: 기본값 생성을 데이터베이스에 위임하는 방식

🔍 @Column

@Column 어노테이션은 필드에 몇가지 설정을 더할 때 사용한다.

  • name: 데이터베이스의 칼럼명을 설정하는 속성, 명시하지 않으면 필드명으로 저장된다
  • nullable: 레코드를 생성할 때 칼럼 값에 null 처리가 가능한지를 명시하는 속성이다
  • length: 데이터베이스에 저장하는 데이터의 최대 길이를 설정한다
  • unique: 해당 칼럼을 유니크로 설정한다

💡 Repository

JPA를 직접 사용하는 곳은 리포지토리 계층이다. 여기서 엔티티 매니저를 사용해서 엔티티를 저장하고 조회한다. 엔티티가 생성한 데이터베이스에 접근하는데 사용된다. 다음 코드는 JpaRepository를 상속 받아서 엔티티를 Product로 설정하고 해당 엔티티의 @Id 필드 타입인 Long으로 설정한 코드이다. JpaRepository를 상속받으면 별도의 메소드 구현 없이도 많은 기능을 제공한다.

public interface ProductRepository extends JpaRepository<Product, Long> {

}

💡 DTO

DTO는 Data Transfer Object의 약자로, 다른 레이어 간의 데이터 교환에 활용된다. 즉, 각 클래스 및 인터페이스를 호출하면서 전달하는 매개변수로 사용되는 데이터 객체이다. DTO는 데이터를 교환하는 용도로만 사용하는 객체이기 때문에 별도의 로직이 포함되지 않고, 주로 Getter/Setter 메소드만을 갖는다.

public class ProductDto {

    private String name;
    private int price;
    private int stock;

    public ProductDto(String name, int price, int stock){
        this.name = name;
        this.price = price;
        this.stock = stock;
    }
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
    //Getter/Setter 메소드들
}

💡DAO

DAO(Data Access Object)는 데이터베이스에 접근하기 위한 로직을 관리하기 위한 객체이다. 비즈니스 로직의 동작 과정에서 데이터를 조작하는 기능을 DAO 객체가 수행한다. 스프링 데이터 JPA에서 DAO의 개념은 리포지토리가 대체하고 있다. 이 글에서는 DAO를 서비스 레이어와 리포지토리의 중간 계층을 구성하는 역할로 사용하였다. DAO 클래스는 일반적으로 '인터페이스-구현체' 구성으로 생성한다. 서비스 레이어에 DAO 객체를 주입받을 때 인터페이스를 선언하는 방식으로 구성할 수 있다.

@Component
public class ProductDAOImpl implements ProductDAO {

    private final ProductRepository productRepository;

    @Autowired
    public ProductDAOImpl(ProductRepository productRepository){
        this.productRepository = productRepository;
    }

    @Override
    public Product insertProduct(Product product){
        Product savedProduct = productRepository.save(product);

        return savedProduct;
    }

    @Override
    public Product selectProduct(Long number){
        Product selectedProduct = productRepository.getById(number);

        return selectedProduct;
    }
}

🔍 @Component

@Component 어노테이션을 통해 위 클래스를 스프링이 관리하는 빈으로 등록한다. 빈으로 등록된 객체는 다른 클래스가 인터페이스를 가지고 의존성을 주입받을 때 이 구현체를 찾아 주입하게 한다. @Controll, @Service, @Repository 등 보다 포괄적인 어노테이션이다.

private final ProductRepository productRepository;

    @Autowired
    public ProductDAOImpl(ProductRepository productRepository){
        this.productRepository = productRepository;
    }

이 코드는 DAO 객체에서 데이터베이스에 접근하기 위해 리포지토리 인터페이스를 사용해서 생성자를 통해 의존성을 주입받는 코드이다.

💡 Service

서비스 계층에는 비즈니스 로직이 있고 트랜잭션을 시작한다. 서비스 레이어에서는 도메인 모델을 활용해 애플리케이션에서 제공하는 핵심 기능을 제공한다. 서비스 객체는 DAO와 마찬가지로 추상화해서 구성한다. 서비스 인터페이스에서는 DAO에서 구현한 기능을 서비스 인터페이스에서 호출해서 결과값을 가져오는 작업을 수행하도록 설계하였다. 서비스에서는 클라이언트가 요청한 데이터를 적절하게 가공해서 컨트롤러에게 넘기는 역할을 한다. 

@Service
public class ProductServiceImpl implements ProductService {
    private final ProductDAO productDAO;

    @Autowired
    public ProductServiceImpl(ProductDAO productDAO){
        this.productDAO = productDAO;
    }

    @Override
    public ProductResponseDto getProduct(Long number){
        Product product = productDAO.selectProduct(number);

        ProductResponseDto productResponseDto = new ProductResponseDto();
        productResponseDto.setNumber(product.getNumber());
        productResponseDto.setName(product.getName());
        productResponseDto.setPrice(product.getPrice());
        productResponseDto.setStock(product.getStock());

        return productResponseDto;
    }
    @Override
    public ProductResponseDto saveProduct(ProductDto productDto){
        Product product = new Product();
        product.setName(productDto.getName());
        product.setPrice(productDto.getPrice());
        product.setStock(productDto.getStock());
        product.setCreatedAt(LocalDateTime.now());
        product.setUpdatedAt(LocalDateTime.now());

        Product savedProduct = productDAO.insertProduct(product);

        ProductResponseDto productResponseDto = new ProductResponseDto();
        productResponseDto.setNumber(savedProduct.getNumber());
        productResponseDto.setName(savedProduct.getName());
        productResponseDto.setPrice(savedProduct.getPrice());
        productResponseDto.setStock(savedProduct.getStock());

        return productResponseDto;
    }
}

아래 코드를 보면 DAO 인터페이스를 선언하고 @Autowired를 지정한 생성자를 통해 의존성을 주입받는다. 그리고 인터페이스에서 정의한 메소드를 오버라이딩한다.

    private final ProductDAO productDAO;

    @Autowired
    public ProductServiceImpl(ProductDAO productDAO){
        this.productDAO = productDAO;
    }

참고

도서 스프링부트 핵심 가이드-장정우 저

'개념 스터디' 카테고리의 다른 글

엔티티 매니저? 영속성 컨텍스트?  (0) 2024.01.21
JPA? ORM? 하이버네이트?  (0) 2024.01.21
MVC  (0) 2024.01.17
REST API  (0) 2024.01.15
레이어드 아키텍처  (0) 2024.01.14