
DAO는 데이터베이스에 접근하는 로직을 관리하는 객체입니다.
비즈니스 로직의 동작과정에서 데이터를 조작하는 기능은 DAO 객체가 수행합니다.
실제로 업무에 필요한 비즈니스 로직을 개발하다보면 데이터를 다루는 중간계층을 두는것이 유지 보수 측면에 용이한 경우가 많습니다.
DAO 클래스는 일반적으로 인터페이스- 구현체 구성으로 생성합니다.
DAO 클래스는 의존성 결합을 낮추기 위한 디자인 패턴이며, 서비스 레이어에 DAO 객체를 주입받을때 인터페이스를 선언하는 방식으로 구성할 수 있습니다.
폴더구조는 아래와 같이 구성한 뒤 ProductDAO 인터페이스와 ProductDAOImpl 클래스를 생성하겠습니다.

//data/dao/ProductDAO.java
public interface ProductDAO {
Product insertProduct(Product product);
Product selectProduct(Long Number);
Product updateProduct(Long Number, String name) throws Exception;
void deleteProduct(Long Number) throws Exception;
}
인터페이스의 설계를 마쳤다면 해당 인터페이스의 구현체를 만들어야합니다.
ProductDAOImpl 클래스를 스프링이 관리하는 빈을 등록하려면 @Component 또는 @Service 어노테이션을 지정해야합니다.
빈으로 등록된 객체는 다른 클래스가 인터페이스를 가지고 의존성을 주입받을때 이 구현체를 찾아 주입하게 됩니다.
DAO 객체에서도 데이터베이스에 접근하기 위해 리포지토리 인터페이스를 사용하여 의존성 주입을 받야하기 때문에
리포지토리를 정의하고 생성자를 통하여 의존성 주입을 합니다.
//data/dao/impl/ProductDAOImpl.java
@Component
public class ProductDAOImpl implements ProductDAO {
private final ProductRepository productRepository;
@Autowired
public ProductDAOImpl(ProductRepository productRepository){
this.productRepository = productRepository;
}
product 엔티티를 데이터베이스에 저장하는 기능을 수행합니다.
리포지토리를 생성할때 인터페이스에서 따로 메소드를 구현하지 않아도 JPA에서 기본 메소드를 제공하므로 save 메소드를 아래와 같이 활용할 수 있습니다.
//data/dao/impl/ProductDAOImpl.java
@Override
public Product insertProduct(Product product){
Product savedProduct = productRepository.save(product);
return savedProduct;
}
selectProduct 메소드가 사용한 리포지토리의 메소드는 단건 조회를 위한 기본 메소드로 두 가지를 제공하는데, getById() 메소드와 findById() 메소드입니다.
getById()
내부적으로 EntityManager의 getReference() 메소드를 호출합니다.
해당 메소드를 호출하면 엔티티를 직접 반환하는게 아니라 프록시 객체를 리턴합니다.
실제로 사용하기 전까지는 DB에 접근하지 않습니다.
findById()
내부적으로 EntityManager의 find() 메소드를 호출합니다.
영속성 컨텍스트의 값이 있는지 먼저 확인하고 데이터가 없으면 실제 DB 에 데이터를 조회합니다.
//data/dao/impl/ProductDAOImpl.java
@Override
public Product selectProduct(Long Number){
Product selectedProduct = productRepository.getById(Number);
return selectedProduct;
}
updateProductName 메소드는 Product 데이터의 상품명을 업데이트하는 기능을 구현합니다.
JPA에서 데이터 값을 변경할때는 다른 메소드와 다른 점이 있습니다.
JPA는 값을 갱신할때 update 키워드를 사용하지 않으며 영속성 컨텍스트를 활용해 값을 갱신하는데 find() 메소드를 통해 DB에서 값을 가져오면 가져온 값이 영속성 컨텍스트에 추가됩니다.
영속성 컨텍스트가 유지되는 상황에서 객체의 값을 변경하고 다시 save() 메소드를 실행하면 JPA에서는 더티체크라고 하는 변경감지를 수행합니다.
Transient 어노테이션이 지정되어 있으면 메소드내 작업을 마칠경우 자동으로 flush() 메소드를 실행하게 되는데 변경이 감지될때 대상 객체에 해당하는 데이터베이스의 레코드를 업데이트하는 쿼리가 실행됩니다.
//data/dao/impl/ProductDAOImpl.java
@Transient
@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;
}
DB의 레코드를 삭제하기 위한 메소드이며 삭제하고자 하는 레코드와 매핑된 영속 객체를 영속성 컨텍스트에서 가져와야합니다.
findById() 메소드를 통해 객체를 가져오고 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();
}
}
//data/dao/impl/ProductDAOImpl.java
@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;
}
@Transient
@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;
}
@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();
}
}
}