DB에 접근하기 위한 로직을 관리하기 위한 객체
비즈니스 로직의 동작 과정에서 데이터를 조작하는 기능은 DAO객체가 수행
다만, Spring Data JPA에서 DAO의 개념은 Repository
가 대체
규모가 작은 서비스 -> DAO를 별도로 설계하지 않고 바로 서비스 레이어
에서 DB에 접근해서 구현하기도 함
여기선 DAO를 서비스 레이어
와 리포지토리의 중간 계층
을 구성하는 역할로 사용
-> 유지 보수에 용이
객체 지향적 설계에서는 서비스
와 비즈니스 레이어
를 분리해서 서비스 레이어에서는 서비스 로직 수행, 비즈니스 레이어에서는 비즈니스 로직을 수행해야 한다는 의견
도메인(엔티티) 객체를 중심으로 다뤄지는 로직 -> 비즈니스 로직
기존의 스프링 Framework이나 스프링 MVC 사용자는 Repository 보다는 DAO 객체로 DB에 접근
일반적으로 인터페이스-구조체
구성으로 생성
-> 의존성 결합을 낮추기 위한 패턴, 서비스 레이어에서 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를 사용해 의존성 주입을 받야함
@Override
public Product insertProduct(Product product) {
Product savedProduct = productRepository.save(product);
return savedProduct;
}
Repository 생성 시 Interface에서 따로 메서드를 구현하지 않아도 JPA에서 기본 메서드를 제공
하므로 save 메서드 활용 가능
@Override
public Product selectProduct(Long number) {
Product selectedPoint = productRepository.getById(number);
return selectedPoint;
}
리포지토리에서 단건 조회를 위한 기본 메서드 -> getById(), findById()
두 메서드 다 조회한다는 기능은 같지만 세부 내용이 다름
내부적으로 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);
}
내부적으로 EntityManager의 find()
메서드 호출
이 메서드는 영속성 컨텍스트의 캐시에서 값을 조회한 후 영속성 컨텍스트에 값이 존재하지 않는다면 실제 DB에서 데이터 조회
-> 리턴값으로 Optional 객체 전달
비즈니스 로직을 구현하는데 적합한 방법을 선정해 활용하자!
@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에서는 더티 체크라고 하는 변경 감지 수행
@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의 레코드를 업데이트 하는 쿼리가 생성
됨!!!
@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 메서드로 해당 객체를 삭제
@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 단계에서 삭제 진행