Book/스프링부트 핵심가이드

DAO(Data Access Object) 개념과 클래스 구성

블로그 주인장 2023. 11. 3.

DAO 설계

데이터베이스 접근하기 위한 로직을 관리하는 객체인 DAO, 구성하는 방법에 대해 알아보겠습니다.


DAO(Data Access Object)


  • 데이터베이스에 접근하기 위한 로직을 관리하기 위한 객체이다.
  • 비즈니스 로직의 동작 과정에서 데이터를 조작하는 기능을 수행한다.
  • 하지만, Spring Data JPA에서 DAO의 개념은 리포지토리(Repository)가 대체한다.

 

DAO 클래스 생성


  • DAO 클래스는 일반적으로 '인터페이스-구현체' 구성으로 생성한다.
  • DAO 클래스는 의존성 결합을 낮추기 위한 디자인 패턴이며, 서비스 레이어에 DAO 객체를 주입받을 때 인터페이스를 선언하는 방식으로 구성할 수 있다.

ProductDao 구성

package com.springboot.jpa.data.dao;

import com.springboot.jpa.data.entity.Product;

public interface ProductDao {
    Product insertProduct(Product product);
    
    Product selectProduct(Product product);
    
    Product updateProductName(Long number, String name) throws Exception;
    
    void deleteProduct(Long number) throws Exception;
}
  • 일반적인 설계 원칙에서 엔티티 객체는 데이터베이스에 접근하는 계층에서만 사용하도록 정의한다.
  • 다른 계층으로 데이터를 전달 할 시에는 DTO 객체를 사용한다.
  • 사람마다 견해 차이가 있으므로 각자 정해진 원칙에 따라 진행하는 것이 좋다.

 

ProductDaoImpl 구성

@Component
@RequiredArgsConstructor
public class ProductDaoImpl implements ProductDao{

    private final ProductRepository productRepository;

    @Override
    public Product insertProduct(Product product) {
        return null;
    }

    @Override
    public Product selectProduct(Product product) {
        return null;
    }

    @Override
    public Product updateProductName(Long number, String name) throws Exception {
        return null;
    }

    @Override
    public void deleteProduct(Long number) throws Exception {

    }
}
  • ProductDaoImpl 클래스를 @Bean으로 등록하려면 @Componet 또는 @Service 어노테이션을 지정해야한다.
  • 빈으로 등록된 객체는 다른 클래스가 인터페이스를 가지고 의존성을 주입받을 때 이 구현체를 찾아 주입한다.
  • 리포지토리를 정의하고 생성자(@RequiredArgsConstructor)를 통해 의존성을 주입받으면 된다.

 

insertProduct() 메서드 구성

@Override
public Product insertProduct(Product product) {
    Product savedProduct = this.productRepository.save(product);
    return savedProduct;
}
  • JPA에서 기본 메서드를 제공하므로 save 메서드를 활용할 수 있다.

 

selectProduct() 메서드 구성

@Override
public Product selectProduct(Long number) {
    Product selectedProduct = this.productRepository.getById(number);
    return selectedProduct;
}
  • 리포지토리에서 단건 조회를 위한 기본 메서드
    • getById()
      1. 내부적으로 EntityManager의 getReference() 메서드를 호출한다.
      2. getReference() 메서드를 호출하면 프록시 객체를 리턴한다.
      3. 실제 쿼리는 프록시 객체를 통해 최초로 데이터에 접근하는 시점에 실행된다.
      4. 데이터가 존재하지 않는 경우, EntityNotFoundException이 발생한다.
    • findById()
      1. 내부적으로 EntityManager의 find() 메서드를 호출한다.
      2. 영속성 컨텍스트의 캐시에서 값을 조회한 후 존재하지 않는다면 실제 데이터베이스에서 데이터를 조회한다.
      3. 리턴 값으로 Optional 객체를 전달한다.

 

updateProductName() 메서드 구성

@Override
public Product updateProductName(Long number, String name) throws Exception {
    Optional<Product> productId = this.productRepository.findById(number);

    Product updatedProduct;
    if (productId.isPresent()) {
        Product product = productId.get();

        product.setName(name);
        product.setUpdatedAt(LocalDateTime.now());

        updatedProduct = this.productRepository.save(product);
    } else {
        throw new Exception();
    }
    return updatedProduct;
}
  • JPA는 값을 갱신할 때 update 키워드를 사용하지 않는다.
  • 영속성 컨텍스트를 활용해 값을 갱신하는데, find() 메서드를 통해 값을 가져오면 가져온 객체가 영속성 컨텍스트에 추가된다.
  • 영속성 컨텍스트가 유지되는 상황에서 객체의 값을 변경하고 다시 save()를 실행하면 JPA에서는 더티 체크(Dirty Check) 라고 하는 변경 감지를 수행한다.

 

[참고] SimpleJpaRepository의 save() 메서드

  • @Transactional 어노테이션이 선언되어 있다.
  • 메서드 내 작업을 마칠 경우 자동으로 flush() 메서드가 실행된다.
  • 이 과정에서 변경이 감지되면 대상 객체에 해당하는 데이터베이스 레코드를 업데이트하는 쿼리가 실행된다.

 

deleteProduct() 메서드 구성

@Override
public void deleteProduct(Long number) throws Exception {
    Optional<Product> selectedProduct = this.productRepository.findById(number);
    
    if (selectedProduct.isPresent()) {
        Product product = selectedProduct.get();
        this.productRepository.delete(product);
    } else {
        throw new Exception();
    }
}
  • 삭제하고자 하는 레코드와 매핑된 영속 객체를 영속성 컨텍스트에 가져와야한다.
  • findById() 메서드를 통해 객체를 가져오고, delete() 메서드를 통해 해당 객체 삭제 요청을 한다.

 

[참고] SimpleJpaRepository의 delete() 메서드

  • delete() 메서드로 전달받은 엔티티가 영속성 컨텍스트에 있는지 파악하고, 해당 엔티티를 영속화하는 작업을 거쳐 데이터베이스의 레코드와 매핑한다.
  • 매핑된 영속 객체를 대상으로 삭제 요청을 수행하는 메서드를 실행해 작업을 하고, 커밋(commit) 단계에서 삭제한다.

 

Summary


리포지토리를 활용하기 위한 DAO 클래스 구성하는 방법에 대해 알아보았다.

 

리포지토리 생성

  • Entity를 매개변수로 이용하여 save() 메서드를 이용한다.

리포지토리 조회

  • getById() : 프록시 객체를 리턴
  • findById() : Optional 객체를 리턴

리포지토리 수정

  • find() 메서드 활용 -> get() 메서드로 객체를 가져온다 -> 값 수정 -> save() 메서드 실행

리포지토리 삭제

  • find() 메서드 활용 -> get() 메서드로 객체를 가져온다 -> delete() 메서드 실행
반응형

댓글