반응형
DAO(Data Access Object)는 DB에 접근하기 위한 로직을 관리하기 위한 객체
- 비즈니스 로직의 동작 과정에서 데이터를 조작하는 기능은 DAO 객체가 수행
- but 스프링 데이터 JPA에서 DAO의 개념은 레포지토리가 대체
규모가 작은 서비스에서는 DAO를 별도로 설계하지 않고 바로 서비스 레이어에서 DB에 접근해서 구현하기도 한다.
- 단 실제로 업무에 필요한 비즈니스 로직을 개발하다 보면 데이터를 다루는 중간 계층을 다두는 것이 유지보수 측면에서 용이한 경우가 많다.
DAO 클래스 생성
- DAO 클래스는 일반적으로 ‘인터페이스-구현체’ 구성으로 생성
- DAO 클래스는 의존성 결합을 낮추기 위한 디자인 패턴
- 서비스 레이어에 DAO 객체를 주입받을 때 인터페이스를 선언하는 방식으로 구성
밑과 같이 data

기본적인 CRUD를 다루기 위해 다음과 같이 인터페이스에 메서드를 정의
import entity.Product;
// 예제 6.9
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에 접근하는 메서드는 리턴 값으로 데이터 객체를 전달
- 이때 데이터 객체를 엔티티 객체로 전달할지 DTO 객체로 전달할지에 대해서는 개발자마다 의견이 분분하다.
- 일반적인 설계 원칙에서 엔티티 객체는 DB에 접근하는 계층에서만 사용하도록 정의한다.
- 다른 계층으로 데이터를 전달할 때는 DTO 객체를 사용
이제 인터페이스의 구현체를 만들어야한다.
- 기능 구현을 위해 다음과 같은 코드 구현체 클래스 작성
- 빈에 등록하려면 → @Component or @Service 애너테이션 지정
import java.time.LocalDateTime;
import java.util.Optional;
import entity.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import repository.ProductRepository;
@Component
public class ProductDAOImpl implements ProductDAO {
private 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;
}
@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();
}
}
}
- 레포지토리에서는 단건조회를 위한 기본 메서드로 두 가지를 제공하는데 getById(), findById() 메서드이다.
- 두 메서드는 조회한다는 기능 측면에서는 동일하지만 세부 내용이 다르다.
@Override
public Product selectProduct(Long number) {
Product selectedProduct = productRepository.getById(number);
return selectedProduct;
}
- getById()
- 내부적으로 EntityManager의 getRefrence() 메서드를 호출
- getReference() 메서드를 호출하면 프락시 객체를 리턴
- 실제 쿼리는 프락시 객체를 통해 최초로 데이터에 접근하는 시점에 실행됨
- 이때 데이터가 존재하지 않을 경우 EntityNotFoundException이 발생
- findById()
- 내부적으로 EntityManager의 find() 메서드를 호출
- 이 메서드는 컨텍스트의 캐시에서 값을 조회한 후 영속성 컨텍스트에 값이 존재하지 않는다면 실제 DB에서 데이터 조회
@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에서 데이터의 값을 변경할 때 다른 메서드와 차이점이 있다.
- 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 (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
- Transactional으로 인해 작업을 마칠 경우 자동으로 flush() 메서드 실행
- 이 과정에서 변경이 감지 되면 대상 객체에 해당하는 DB의 레코드를 업데이트하는 쿼리 실행
delete
- findById해서 가져오고 객체 삭제
@Override
@Transactional
@SuppressWarnings("unchecked")
public void delete(T entity) {
Assert.notNull(entity, "Entity must not be null!");
if (entityInformation.isNew(entity)) {
return;
}
Class<?> type = ProxyUtils.getUserClass(entity);
T existing = (T) em.find(type, entityInformation.getId(entity));
// if the entity to be deleted doesn't exist, delete is a NOOP
if (existing == null) {
return;
}
em.remove(em.contains(entity) ? entity : em.merge(entity));
}
- 밑 코드에서 엔티티가 영속성 컨텍스트에 있는지 파악하고 해당 엔티티를 영속성 컨텍스트에 영속화하는 작업을 거쳐 DB의 레코드와 매핑
- 영속 객체를 대상으로 삭제 요청을 수행하는 메서드를 실행해 작업을 마치고 커밋(Commit) 단계에서 삭제 진행
if (entityInformation.isNew(entity)) {
return;
}
반응형
'Spring Boot > 데이터베이스 연동' 카테고리의 다른 글
| [Spring boot] DAO 연동을 위한 컨트롤러와 서비스 설계 (1) | 2025.08.17 |
|---|---|
| [Spring boot] 엔티티 설계 - 실습 (0) | 2025.08.17 |
| [Spring boot] DB 연동 - 실습 (0) | 2025.08.17 |
| [Spring boot] 영속성 컨텍스트 (3) | 2025.08.17 |
| [Spring boot] JPA (1) | 2025.08.17 |