[Spring] DAO(Data Access Object) 설계

WOOK JONG KIM·2022년 10월 26일
0
post-thumbnail

DAO

DB에 접근하기 위한 로직을 관리하기 위한 객체

비즈니스 로직의 동작 과정에서 데이터를 조작하는 기능은 DAO객체가 수행

다만, Spring Data JPA에서 DAO의 개념은 Repository가 대체

규모가 작은 서비스 -> DAO를 별도로 설계하지 않고 바로 서비스 레이어에서 DB에 접근해서 구현하기도 함

여기선 DAO를 서비스 레이어리포지토리의 중간 계층을 구성하는 역할로 사용
-> 유지 보수에 용이

객체 지향적 설계에서는 서비스비즈니스 레이어를 분리해서 서비스 레이어에서는 서비스 로직 수행, 비즈니스 레이어에서는 비즈니스 로직을 수행해야 한다는 의견

도메인(엔티티) 객체를 중심으로 다뤄지는 로직 -> 비즈니스 로직

기존의 스프링 Framework이나 스프링 MVC 사용자는 Repository 보다는 DAO 객체로 DB에 접근


DAO클래스 생성

일반적으로 인터페이스-구조체 구성으로 생성
-> 의존성 결합을 낮추기 위한 패턴, 서비스 레이어에서 DAO 객체를 주입받을 때 인터페이스를 선언하는 방식으로 구성 가능

ProductDao

package com.example.jpa.data.dao;

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

public interface ProductDao {

    Product insertProduct(Product product);

    Product selectProduct(Long number);

    Product updateProductName(Long number, String name) throws Exception;

    void deleteProduct(Long number) throws Exception;
}

일반적으로 DB에 접근하는 메서드는 리턴 값으로 데이터 객체 전달

이 때 데이터 객체로 Entity를 전달할지, DTO 객체로 전달할지는 의견 분분
-> 보통 DB접근하는 계층에서만 Entity로 전달하고, 다른 계층으로 전달 시DTO 객체 사용

ProductDAO 인터페이스의 구현체 클래스

@Component
public class ProductDAOImpl implements ProductDao {
    
    private final ProductRepository productRepository;
    
    @Autowired
    public ProductDAOImpl(ProductRepository productRepository){
        this.productRepository = productRepository;
    }
    
    @Override
    public Product insertProduct(Product product) {
        return null;
    }

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

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

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

    }
}

ProductDAOImpl 클래스를 스프링이 관리하는 빈으로 등록하려면 @Component 또는 @Service 어노테이션 지정해야 함

빈으로 등록된 객체는 다른 클래스가 인터페이스를 가지고 의존성을 주입받을 때 이 구현체를 찾아서 주입하게 됨

-> DAO 객체에서도 DB에 접근하기 위해 Repository Interface를 사용해 의존성 주입을 받야함

insert 메서드 구현

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

Repository 생성 시 Interface에서 따로 메서드를 구현하지 않아도 JPA에서 기본 메서드를 제공하므로 save 메서드 활용 가능

select 메서드 구현

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

    return selectedPoint;
}

리포지토리에서 단건 조회를 위한 기본 메서드 -> getById(), findById()

두 메서드 다 조회한다는 기능은 같지만 세부 내용이 다름

getById()

내부적으로 EntityManager의 getReference() 메서드 호출
-> getReference()는 프락시 객체 return

실제 쿼리는 프락시 객체를 통해 최초로 데이터를 접근하는 시점에 실행

데이터가 존재하지 않을 시 EntityNotFoundException 발생

JpaRepository의 실제 구현체인 SimpleJpaRepository의 getById() 메서드 코드

@Override
public T getById(ID id){
	Assert.notNull(id, ID_MUST_NOT_BE_NULL);
    return em.getReference(getDomainClass(), id);
}

findById()

내부적으로 EntityManager의 find()메서드 호출

이 메서드는 영속성 컨텍스트의 캐시에서 값을 조회한 후 영속성 컨텍스트에 값이 존재하지 않는다면 실제 DB에서 데이터 조회
-> 리턴값으로 Optional 객체 전달

비즈니스 로직을 구현하는데 적합한 방법을 선정해 활용하자!

Update 메서드 구현

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

    Product updatedProduct;
    if(selectedProduct.isPresent()){
       Product product = selectedProduct.get();
            
       product.setName(name);
       product.setUpdatedAt(LocalDateTime.now());
            
       updatedProduct = productRepository.save(product);
    } else{
            throw new Exception();
    }
  	 return updatedProduct;
}

JPA에서는 값을 갱신할 때 다른 메서드와 다르게 update라는 키워드 사용 X

위 코드에서는 영속성 컨텍스트를 활용하여 값 갱신, find() 메서드를 통해 DB에서 값을 가져오면 가져온 객체가 영속성 컨텍스트에 추가

영속성 컨텍스트가 유지되는 상황에서 객체의 값을 변경하고 다시 save()를 실행하면 JPA에서는 더티 체크라고 하는 변경 감지 수행

save 메서드

@Transactional
@Override
public <S extends T> S save(S entity) {
    Assert.notNull(entity, "Entity must not be null.");
    
    if (this.entityInformation.isNew(entity)) {
        this.em.persist(entity);
        return entity;
    } else {
        return this.em.merge(entity);
    }
}

@Transactional이 지정되어 있으면 메서드 내 작업을 마칠 때 자동으로 flush() 호출

-> 이 과정에서 변경이 감지되면 대상 객체에 해당하는 DB의 레코드를 업데이트 하는 쿼리가 생성됨!!!

Delete 메서드 구현

@Override
public void deleteProduct(Long number) throws Exception {
    Optional<Product> selectedProduct = productRepository.findById(number);
        
    if(selectedProduct.isPresent()){
        Product product = selectedProduct.get();
            
        productRepository.delete(product);
    } else{
        throw new Exception();
    }
}

DB의 레코드 삭제를 위해선 삭제 하고자 하는 레코드매핑된 영속 객체를 영속성 컨텍스트에 가져와야 함
-> findById를 통해 객체를 가져오고 delete 메서드로 해당 객체를 삭제

delete()

@Transactional
public void delete(T entity) {
    Assert.notNull(entity, "Entity must not be null!");
    
    if (!this.entityInformation.isNew(entity)) {
        Class<?> type = ProxyUtils.getUserClass(entity);
        T existing = this.em.find(type, this.entityInformation.getId(entity));
        
        if (existing != null) {
                this.em.remove(this.em.contains(entity) ? entity : this.em.merge(entity));
        }
    }
}

delete 메서드는 전달받은 엔티티가 영속성 컨텍스트에 있는지 파악
-> 이후 해당 엔티티를 영속성 컨텍스트에 영속화 하는 작업을 거쳐 DB의 레코드와 매핑

매핑 된 영속 객체를 대상으로 삭제 요청을 수행하는 메서드를 실행해 작업을 마치고 Commit 단계에서 삭제 진행

profile
Journey for Backend Developer

0개의 댓글