일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 추상메서드
- 집합_SET
- exception
- 컬렉션프레임워크
- 대덕인재개발원
- 메소드오버로딩
- 제네릭
- 사용자예외클래스생성
- 예외처리
- 예외미루기
- 다형성
- 자바
- 한국건설관리시스템
- 참조형변수
- 생성자오버로드
- 어윈 사용법
- 자동차수리시스템
- oracle
- 객체 비교
- 인터페이스
- NestedFor
- 컬렉션 타입
- GRANT VIEW
- 정수형타입
- Java
- cursor문
- abstract
- 환경설정
- EnhancedFor
- 오라클
- Today
- Total
거니의 velog
(18) 상품 API 서버 구성하기 3 본문
4. 서비스 계층과 컨트롤러 연동
* 서비스 계층에서는 DTO와 엔티티 간의 변환에 주의하면서 기능들을 구현한다. service 패키지 내에 ProductService 인터페이스와 ProductServiceImpl 클래스를 추가한다.
(1) 목록 기능의 처리
* 선언된 클래스와 인터페이스에서는 가장 구현이 복잡한 목록 기능을 구현해 본다. ProductService 인터페이스 목록에 PageResponseDTO로 처리하는 getList()를 추가한다.
package com.unlimited.mallapi.service;
import org.springframework.transaction.annotation.Transactional;
import com.unlimited.mallapi.dto.PageRequestDTO;
import com.unlimited.mallapi.dto.PageResponseDTO;
import com.unlimited.mallapi.dto.ProductDTO;
/**
*
* @Transactional 어노테이션을 서비스 계층의 인터페이스 레벨에 추가하는 것은 특정 메서드가 아닌 해당 인터페이스에 대해 트랜잭션을
* 적용하려는 의도를 나타냅니다. 이는 일반적으로 특별한 상황에서만 사용되며, 보통은 메서드
* 레벨에서 @Transactional 어노테이션을 사용하는 것이 일반적입니다.
*
* 일반적으로 서비스 인터페이스 레벨에 @Transactional 어노테이션을 추가하는 것은 다음과 같은 상황에서
* 고려될 수 있습니다.
*
* 인터페이스의 모든 메서드가 트랜잭션에 참여하는 경우: 인터페이스 내의 모든 메서드가 트랜잭션 내에서 실행되어야
* 할 경우에 사용될 수 있습니다. 이는 특히 서비스 인터페이스가 여러 메서드를 정의하고 이를 구현하는 클래스에서
* 트랜잭션을 관리하기 어려운 경우에 유용할 수 있습니다.
*
* 모든 메서드가 동일한 트랜잭션 속성을 가져야 하는 경우: 인터페이스 내의 모든 메서드가 동일한 트랜잭션 속성을
* 가져야 하는 경우에 해당됩니다. 이는 특별한 경우에만 적용되어야 하며, 대부분의 경우 메서드 레벨에서 트랜잭션
* 속성을 명시하는 것이 더 유연하고 명시적입니다.
*
* 서비스 레이어는 일반적으로 비즈니스 로직을 처리하고 트랜잭션 경계를 설정하는 역할을 합니다. 메서드
* 레벨에서 @Transactional 어노테이션을 사용하는 것이 더 흔하며, 메서드마다 다른 트랜잭션 속성을
* 지정할 수 있어서 더 유연한 구현이 가능합니다. 인터페이스 레벨에서 @Transactional 어노테이션을
* 사용하는 것은 특별한 경우에만 필요하며, 주의해서 사용해야 합니다.
*/
@Transactional(rollbackFor = Exception.class)
public interface ProductService {
PageResponseDTO<ProductDTO> getList(PageRequestDTO pageRequestDTO);
}
* 실제 구현을 담당하는 ProductServiceImpl에서는 구현 과정이 조금 복잡하므로 미리 단계를 이해하고 코드를 작성한다.
- ProductRepository를 통해서 Page<Object[]> 타입의 결과 데이터를 가져온다.
- 각 Object[] 의 내용물은 Product 객체와 ProductImage 객체이다.
- 반복 처리로 Product와 ProductImage를 ProductDTO 타입으로 변환한다.
- 변환된 ProductDTO를 List<ProductDTO>로 처리하고, 전체 데이터의 개수를 이용해서 PageResponseDTO
타입으로 생성하고 반환한다.
* 위의 내용을 구현한 ProductServiceImpl 클래스 코드는 아래와 같다.
package com.unlimited.mallapi.service;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.unlimited.mallapi.domain.Product;
import com.unlimited.mallapi.domain.ProductImage;
import com.unlimited.mallapi.dto.PageRequestDTO;
import com.unlimited.mallapi.dto.PageResponseDTO;
import com.unlimited.mallapi.dto.ProductDTO;
import com.unlimited.mallapi.repository.ProductRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Service
@Log4j2
@RequiredArgsConstructor
@Transactional(rollbackFor = Exception.class)
public class ProductServiceImpl implements ProductService {
private final ProductRepository productRepository;
@Override
public PageResponseDTO<ProductDTO> getList(PageRequestDTO pageRequestDTO) {
log.info("getList 실행...");
// 페이지 및 정렬 조건을 설정합니다.
Pageable pageable = PageRequest.of(
pageRequestDTO.getPage() - 1, // 페이지 시작 번호가 0부터 시작하므로
pageRequestDTO.getSize(),
Sort.by("pno").descending());
// 상품 목록을 조회합니다.
Page<Object[]> result = productRepository.selectList(pageable);
// 조회 결과를 DTO로 변환합니다.
List<ProductDTO> dtoList = result.get().map(arr -> {
// 조회된 배열에서 상품과 이미지 정보를 추출합니다.
Product product = (Product) arr[0];
ProductImage productImage = (ProductImage) arr[1];
// 상품 정보를 DTO로 변환합니다.
ProductDTO productDTO = ProductDTO.builder()
.pno(product.getPno())
.pname(product.getPname())
.pdesc(product.getPdesc())
.price(product.getPrice())
.build();
// 이미지 파일 이름을 설정합니다.
String imageStr = productImage.getFileName();
productDTO.setUploadFileNames(List.of(imageStr));
return productDTO;
}).collect(Collectors.toList());
// 전체 상품 수를 가져옵니다.
long totalCount = result.getTotalElements();
// 페이지 응답 DTO를 생성하여 반환합니다.
return PageResponseDTO.<ProductDTO>withAll()
.dtoList(dtoList)
.totalCount(totalCount)
.pageRequestDTO(pageRequestDTO)
.build();
}
}
[서비스 목록 기능의 테스트]
* 기능의 구현이 복잡할 때는 항상 서비스 계층 역시 테스트를 진행해 보는 습관을 지니는 것이 좋다. test 폴더 내에 service 패키지를 활용해서 ProductServiceTests 클래스를 추가하고 목록 기능을 테스트한다.
package com.unlimited.mallapi.service;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.unlimited.mallapi.dto.PageRequestDTO;
import com.unlimited.mallapi.dto.PageResponseDTO;
import com.unlimited.mallapi.dto.ProductDTO;
import lombok.extern.log4j.Log4j2;
@SpringBootTest
@Log4j2
public class ProductServiceTests {
// ProductService를 주입받습니다.
@Autowired
ProductService productService;
// getList 메서드를 테스트합니다.
@Test
public void testList() {
// 1 페이지, 페이지 크기 10으로 설정한 PageRequestDTO를 생성합니다.
PageRequestDTO pageRequestDTO = PageRequestDTO.builder().build();
// ProductService의 getList 메서드를 호출하여 상품 목록을 조회합니다.
PageResponseDTO<ProductDTO> result = productService.getList(pageRequestDTO);
// 조회된 상품 목록을 출력합니다.
result.getDtoList().forEach(dto -> log.info(dto));
}
}
* testList()의 실행 결과는 아래와 같이 ProductDTO들이 출력되어야 하고, uploadFileNames 라는 속성 값으로 하나의 이미지 정보가 출력된다.
[컨트롤러와 연동 확인]
* 서비스 계층의 테스트가 완료되었다면 ProductController와 연동해서 최종적인 결과를 확인한다.
package com.unlimited.mallapi.controller;
import java.util.List;
import java.util.Map;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.unlimited.mallapi.dto.PageRequestDTO;
import com.unlimited.mallapi.dto.PageResponseDTO;
import com.unlimited.mallapi.dto.ProductDTO;
import com.unlimited.mallapi.service.ProductService;
import com.unlimited.mallapi.util.CustomFileUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
@RestController
@RequiredArgsConstructor
@Log4j2
@RequestMapping("/api/products")
public class ProductController {
private final CustomFileUtil fileUtil;
private final ProductService productService; // ProductService 주입
(...)
// 페이지 및 정렬 조건을 전달받아 상품 목록을 조회하는 메서드입니다.
@GetMapping("/list")
public PageResponseDTO<ProductDTO> list(PageRequestDTO pageRequestDTO) {
// 전달받은 페이지 요청 정보를 로그로 출력합니다.
log.info("list...." + pageRequestDTO);
// ProductService의 getList 메서드를 호출하여 상품 목록을 조회하고 반환합니다.
return productService.getList(pageRequestDTO);
}
}
* ProductController의 목록에 대한 테스트 작업은 Postman으로 확인한다.
* 결과 데이터는 JSON 형식으로 전달된다. 결과 데이터 내에서 화면에서 사용할 데이터들이 제대로 출력되는지 확인한다.
* 데이터가 충분하다면 페이지 번호(page)와 사이즈(size)를 쿼리스트링으로 지정해서 페이징 처리 결과를 확인할 수 있다
- http://localhost:8080/api/products/list?page=1&size=5
{
"dtoList": [
{
"pno": 110,
"pname": "상품 99",
"price": 9900,
"pdesc": "상품설명 99",
"delFlag": false,
"files": [],
"uploadFileNames": [
"2543907f-de57-4294-8de8-0eb917fd6641_IMAGE1.jpg"
]
},
{
"pno": 109,
"pname": "상품 98",
"price": 9800,
"pdesc": "상품설명 98",
"delFlag": false,
"files": [],
"uploadFileNames": [
"0b85d020-3ef3-452f-9b38-c6482039990a_IMAGE1.jpg"
]
},
{
"pno": 108,
"pname": "상품 97",
"price": 9700,
"pdesc": "상품설명 97",
"delFlag": false,
"files": [],
"uploadFileNames": [
"d549fc82-7430-4360-b399-b3e085976dbd_IMAGE1.jpg"
]
},
{
"pno": 107,
"pname": "상품 96",
"price": 9600,
"pdesc": "상품설명 96",
"delFlag": false,
"files": [],
"uploadFileNames": [
"5c356437-6578-4142-b665-c2373fbc197a_IMAGE1.jpg"
]
},
{
"pno": 106,
"pname": "상품 95",
"price": 9500,
"pdesc": "상품설명 95",
"delFlag": false,
"files": [],
"uploadFileNames": [
"e3967ba0-1ca5-47ce-be6f-59dff5b3c9a2_IMAGE1.jpg"
]
}
],
"pageNumList": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10
],
"pageRequestDTO": {
"page": 1,
"size": 5
},
"prev": false,
"next": true,
"totalCount": 109,
"prevPage": 0,
"nextPage": 11,
"totalPage": 10,
"current": 1
}
'SpringBoot_React 풀스택 프로젝트' 카테고리의 다른 글
(20) 상품 API 서버 구성하기 5 (0) | 2024.03.04 |
---|---|
(19) 상품 API 서버 구성하기 4 (0) | 2024.03.04 |
(17) 상품 API 서버 구성하기 2 (0) | 2024.02.29 |
(16) 상품 API 서버 구성하기 1 (0) | 2024.02.29 |
(15) 리액트와 API 서버 통신 5 (0) | 2024.02.29 |