본문 바로가기
Spring/- [시리즈] 간단한 CRUD 구현하기

CRUD 구현 6 - Service 구현

by 정구정구 2026. 5. 15.

Service란?

“비즈니스 로직을 담당하는 계층”

 

비즈니스 로직이란?

업무 규칙! 

  • "재고가 부족하면 주문 불가"는 DB 규칙이 아니라 비즈니스 규칙이다. 

 

Service의 역할

  • 비즈니스 로직 처리
  • 트랜잭션 관리
  • 여러 Repository에 접근
  • 중복되는 비즈니스 로직 정리하여 재사용 

 

프로젝트 구현

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class OrderService {

    private final OrderRepository orderRepository;
    private final ProductRepository productRepository;

    @Transactional
    public OrderResponse create(OrderRequest request) {

        Product product = productRepository.findById(request.productId())
                .orElseThrow(() -> new IllegalArgumentException("상품이 존재하지 않습니다."));

        product.decreaseStock(request.quantity());

        Order order = new Order(product, request.quantity());

        Order savedOrder = orderRepository.save(order);

        return OrderResponse.from(savedOrder);
    }

    public OrderResponse findById(Long id) {

        Order order = orderRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("주문이 존재하지 않습니다."));

        return OrderResponse.from(order);
    }

    public List<OrderResponse> findAll() {
        return orderRepository.findAllWithProduct()
                .stream()
                .map(OrderResponse::from)
                .toList();
    }
}
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ProductService {

    private final ProductRepository productRepository;

    @Transactional
    public ProductResponse create(ProductRequest request) {

        Product product = new Product(
                request.name(),
                request.price(),
                request.stock()
        );

        Product savedProduct = productRepository.save(product);

        return ProductResponse.from(savedProduct);
    }

    public ProductResponse findById(Long id) {

        Product product = productRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("상품이 존재하지 않습니다."));

        return ProductResponse.from(product);
    }

    public List<ProductResponse> findAll() {
        return productRepository.findAll()
                .stream()
                .map(ProductResponse::from)
                .toList();
    }

    @Transactional
    public ProductResponse update(Long id, ProductRequest request) {

        Product product = productRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("상품이 존재하지 않습니다."));

        product.update(
                request.name(),
                request.price(),
                request.stock()
        );

        return ProductResponse.from(product);
    }

    @Transactional
    public void delete(Long id) {
        productRepository.deleteById(id);
    }
}

 

 

코드 설명

@Transactional(readOnly = true)

 

해당 메서드에 트랜잭션 처리.

 

(readOnly = true)

  • 조회 전용 트랜잭션.
  • JPA는 영속성 관리, 지연 로딩, 캐시 사용 등의 이유로 조회에도 트랜잭션 안에서 실행함

참고 :

https://adjh54.tistory.com/378

 

[Java] @Transactional 어노테이션 상세 이해하기 : Checked, Unchecked Exception Rollback

해당 글에서는 @Transactional 어노테이션에 대해 상세한 이해를 돕기 위해 작성한 글입니다. 💡 [참고] 다양한 Annotation에 대해 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.구분링크Spring Web

adjh54.tistory.com

 

https://ssdragon.tistory.com/116

 

@Transactional은 조회만 할 때 있어야할까?

스프링 부트 프로젝트를 진행하면서 트랜잭션 어노테이션을 적지 않은곳에서 조회가 잘되고 있었다. 하지만 어느 곳에서는 읽기 전용 트랜잭션인 `@Transactional(readOnly = true)`를 적어 성능 최적화

ssdragon.tistory.com

 

https://velog.io/@jinyeong-afk/%EA%B8%B0%EC%88%A0-%EB%A9%B4%EC%A0%91-%EC%A6%89%EC%8B%9C-%EB%A1%9C%EB%94%A9%EA%B3%BC-%EC%A7%80%EC%97%B0-%EB%A1%9C%EB%94%A9%EC%9D%98-%EC%B0%A8

 

[기술 면접] JPA 즉시 로딩과 지연 로딩의 차이

JPA에서는 데이터를 조회할 때 즉시 로딩(EAGER)과 지연 로딩(LAZY) 두 가지 방식이 있다. 이 두 가지 방식을 간단하게 설명하면 즉시 로딩은 데이터를 조회할 때 연관된 데이터까지 한 번에 불러오

velog.io