JPA 기반 데이터베이스 엑세스 계층 구현

jungseo·2023년 6월 23일
0

Spring

목록 보기
10/23

구분

1. JPA

  • Jakarta Persistence API 또는 Java Persistence API

  • 이름은 API지만 Java 애플리케이션에서 관계형 데이터베이스를 사용하기 위해 정해놓은 표준 스펙

    • 기술에 대한 설명과 사용 방법에 대한 명세

2. Hibernate ORM

  • JPA라는 표준 스펙을 구현한 구현체
  • 실제 사용 가능한 API

3. Spring Data JPA

  • JPA 스펙을 구현한 구현체의 API(Hibernate ORM)를 더 쉽게 사용할 수 있게 해주는 모듈

구현 과정

1. 엔티티 클래스를 Spring Data JPA에 맞게 수정

  • 엔티티 매핑

2. 레포지토리 인터페이스 구현

  • Spring Data JDBC와 유사
  • JpaRepository 인터페이스 상속
  • 상속된 인터페이스를 타고 올라가보면 CrudRepository가 있어 기존 사용하던 save(), findBy() 등의 메서드를 사용할 수 있다.

Cascade

  • @OneToMany, @ManyToOne 애너테이션에 매핑된 엔티티의 영속성 관리를 위해 사용
  • CascadeType.ALL: 부모 엔티티의 상태 변화(추가, 수정, 삭제 등)가 자식 엔티티에도 적용
  • CascadeType.PERSIST: 부모 엔티티가 저장될 때 연관된 자식 엔티티도 함께 저장
  • CascadeType.MERGE: 부모 엔티티가 병합될 때 연관된 자식 엔티티도 함께 병합
  • CascadeType.REMOVE: 부모 엔티티가 삭제될 때 연관된 자식 엔티티도 함께 삭제
  • CascadeType.REFRESH: 부모 엔티티가 리프레시될 때 연관된 자식 엔티티도 함께 리프레시
  • CascadeType.DETACH: 부모 엔티티가 분리(detach)될 때 연관된 자식 엔티티도 함께 분리

    부모 엔티티의 기본키를 외래키로 가지고 있는 엔티티가 자식 엔티티

JPQL

public interface CoffeeRepository extends JpaRepository<Coffee, Long> {
    Optional<Coffee> findByCoffeeCode(String coffeeCode);

//    @Query(value = "FROM Coffee c WHERE c.coffeeId = :coffeeId") // (1)
//    @Query(value = "SELECT * FROM COFFEE WHERE coffee_Id = :coffeeId", nativeQuery =true) // (2)
    @Query(value = "SELECT c FROM Coffee c WHERE c.coffeeId = :coffeeId") // (3)
    Optional<Coffee> findByCoffee(long coffeeId);
}
  • JPA는 JPQL이라는 객체 지향 쿼리로 데이터베이스 내 테이블 조회 가능
  • 테이블을 대상으로 조회를 하지 않고 엔티티 클래스의 객체를 대상으로 객체를 조회
  • JPQL 문법으로 객체를 조회하면 JPA가 내부적으로 JPQL을 분석해 SQL을 만들어 데이터베이스를 조회하고 조회한 결과를 엔티티 객체로 매핑 후 반환
  • 사용 방식
    • 객체를 대상으로 조회하기 때문에 COFFEE 테이블이 아닌 Coffee 클래스를 대상으로, coffee_id라는 열이 아닌 coffeeId 필드를 대상으로 지정
    • (3)의 Coffee는 클래스명, coffeeId는 필드명
    • 'c'는 Coffee 클래스의 별칭으로 “SELECT c FROM~” 와 같이 SQL에서 사용하는 ‘*’이 아니라 ‘c’로 모든 필드를 조회
    • ‘SELECT c’를 생략한 형태로 사용이 가능
    • (2)의 nativeQuery 애트리뷰트의 값을 ‘true’로 설정하면 value 애트리뷰트에 작성한 SQL 쿼리가 적용

3. 서비스 클래스 구현

  • Spring Data JDBC와 유사
  • Spring에서는 PSA(일관된 서비스 추상화)를 통해 일관된 코드 구현 방식을 유지하고 기술의 변경이 필요할 때 최소한의 변경만을 하도록 지원

OrderService

@Service
public class OrderService {
    private final MemberService memberService;
    private final CoffeeService coffeeService;
    private final OrderRepository orderRepository;

    public OrderService(MemberService memberService,
                        CoffeeService coffeeService, OrderRepository orderRepository) {
        this.memberService = memberService;
        this.coffeeService = coffeeService;
        this.orderRepository = orderRepository;
    }

    public Order createOrder(Order order) {
//        회원, 커피가 존재하는지 조회
        verifyOrder(order);

//        스템프 개수 업데이트
        updateStamp(order);

        return orderRepository.save(order);
    }

//        주문 업데이트
    public Order updateOrder(Order order) {
        Order findOrder = findVerifiedOrder(order.getOrderId());

        Optional.ofNullable(order.getOrderStatus())
                .ifPresent(orderStatus -> findOrder.setOrderStatus(orderStatus));
        findOrder.setModifiedAt(LocalDateTime.now());
        return orderRepository.save(findOrder);
    }

    public Order findOrder(long orderId) {
        return findVerifiedOrder(orderId);
    }

    public Page<Order> findOrders(int page, int size) {
        return orderRepository.findAll(PageRequest.of(page, size,
                Sort.by("orderId").descending()));
    }

    public void cancelOrder(long orderId) {
        Order findOrder = findVerifiedOrder(orderId);
        int step = findOrder.getOrderStatus().getStepNumber();

//        OrderStatus의 step이 2 이상일 경우(ORDER_CONFIRM)에는 주문 취소가 되지 않도록한다.
        if (step >= 2) {
            throw new BusinessLogicException(ExceptionCode.CANNOT_CHANGE_ORDER);
        }
        findOrder.setOrderStatus(Order.OrderStatus.ORDER_CANCEL);
        findOrder.setModifiedAt(LocalDateTime.now());
        orderRepository.save(findOrder);
    }
//    주문이 존재하는지 확인
    private Order findVerifiedOrder(long orderId) {
        Optional<Order> optionalOrder = orderRepository.findById(orderId);
        Order findOrder =
                optionalOrder.orElseThrow(() ->
                        new BusinessLogicException(ExceptionCode.ORDER_NOT_FOUND));
        return findOrder;
    }
    private void verifyOrder(Order order) {
//        회원이 존재하는지 확인
        memberService.findVerifiedMember(order.getMember().getMemberId());

//        커피가 존재하는지 확인
        order.getOrderCoffees().stream()
                .forEach(orderCoffee ->
                        coffeeService.findVerifiedCoffee(
                                orderCoffee.getCoffee().getCoffeeId()));
    }

    private void updateStamp(Order order) {
        Member member = order.getMember();
        int stampCnt = order.getOrderCoffees().stream()
                .map(orderCoffee -> orderCoffee.getQuantity())
                .mapToInt(el -> el)
                .sum();
        member.getStamp().setStampCount(member.getStamp().getStampCount() + stampCnt);
    }
}

블로깅 하기에 양이 많아 제일 중요하다 생각된 OrderService 클래스만 블로깅 하려 했는데 제대로 돌아가지 않는다.
post order 요청이 값을 넣어도 NullPointException이 발생한다..
요청값이 잘못되었으면 기존에 작성했던 GlovalControlAdvice에서 예외를 잡아 처리했을텐데 못하는 걸 보니 요청값이 넘어는 오는데 DTO 변환 과정에서 제대로 처리가 되지 않는 것 같다. 해당 부분은 수정해봐야 될 것 같다..
공부하면서 MapStruct와 같이 자동으로 ~ 해준다 라는 부분들이 너무 얼렁뚱땅 넘어가는 느낌이라 가장 이해하기 힘들고 찝찝하다. 시간 날때 상속받는 인터페이스, 클래스들을 파헤쳐 봐야겠다.

4. 기능 추가로 필요한 코드 수정

기타 구현 코드는 깃허브 참조

0개의 댓글