Jin's Dev Story

[스프링 부트 쇼핑몰 프로젝트 with JPA] 9. 메인화면 본문

Web & Android/스프링 부트 쇼핑몰 프로젝트 with JPA

[스프링 부트 쇼핑몰 프로젝트 with JPA] 9. 메인화면

woojin._. 2023. 10. 16. 19:09
이 내용은 스프링 부트 쇼핑몰 프로젝트 with JPA 책을 학습한 내용입니다.

1. MainItemDto

  • 메인화면에 출력할 데이터를 위한 DTO 객체
  • 사용자에게 보여질 내용만 포함하고 있음 (등록날짜, 수정날짜, 등록자 등 제외)
import com.querydsl.core.annotations.QueryProjection;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class ItemMainDto {

    private Long id;

    private String itemNm;

    private String itemDetail;

    private String imgUrl;

    private Integer price;

    @QueryProjection
    public ItemMainDto(Long id, String itemNm, String itemDetail, String imgUrl, Integer price) {
        this.id = id;
        this.itemNm = itemNm;
        this.itemDetail = itemDetail;
        this.imgUrl = imgUrl;
        this.price = price;
    }
}

2. @QueryProjection

  • Entity 객체를 DTO 객체로 바로 반환하도록 지원하는 어노테이션
  • @QueryProjection을 이용하여 상품 조회 시 DTO 객체로 결과 값을 받는 방법
  • @QueryProjection을 이용하면 Item 객체로 값을 받은 후 DTO 클래스로 변환하는 과정 없이 바로 DTO 객체 뽑아낼 수 있음

3. 사용자 정의 인터페이스 리포지토리 생성

  • 기존의 ItemRepositoryCustom 클래스에 조회문 추가
import kr.spring.item.dto.ItemMainDto;
import kr.spring.item.dto.ItemSearchDto;
import kr.spring.item.entity.Item;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface ItemRepositoryCustom {

    // 메인 페이지에 보여줄 상품 리스트를 가져오는 메소드
    Page<ItemMainDto> getItemMainPage(ItemSearchDto itemSearchDto, Pageable pageable);
}
  • ItemRepositoryCustomImpl 클래스에서 getMainItemPage() 메소드 구현
// 검색어가 포함된 상품 조회 조건 BooleanExpression
    private BooleanExpression itemNmLike(String searchQuery) {
        return StringUtils.isEmpty(searchQuery) ? null : item.itemNm.like("%" + searchQuery + "%");
    }
@Override
    public Page<ItemMainDto> getItemMainPage(ItemSearchDto itemSearchDto, Pageable pageable) {

        QItem item = QItem.item;
        QItemImg itemImg = QItemImg.itemImg;

        QueryResults<ItemMainDto> results = queryFactory
								// MainItemDto 객체를 반환
								// 멤버변수 초기화는 조회된 결과값에서 MainItemDto 객체 생성자를 통해 지정
								// 즉, db 조회 결과는 itemImg-item 조인된 결과가 반환되지만 그 중 일부만 사용
                .select(
                        new QItemMainDto(
                                item.id,
                                item.itemNm,
                                item.itemDetail,
                                itemImg.imgUrl,
                                item.price)
                        )
                .from(itemImg)
								// itemImg 테이블의 item 필드가 참조하는 item 테이블 조인
                .join(itemImg.item, item)
								// 5개의 이미지중에서 대표사진만을 조회
                .where(itemImg.repimgYn.eq("Y"))
                .where(itemNmLike(itemSearchDto.getSearchQuery()))
                .orderBy(item.id.desc())
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetchResults();

        List<ItemMainDto> content = results.getResults();
        long total = results.getTotal();
        return new PageImpl<>(content, pageable, total);

    }
  • ItemService 에 getMainItemPage() 메소드 수행 로직 추가
@Transactional(readOnly = true)
    public Page<ItemMainDto> getItemMainDto(ItemSearchDto itemSearchDto, Pageable pageable) {
        return itemRepository.getItemMainPage(itemSearchDto, pageable);
    }

4. Main Controller

  • 화면 header Navbar 안에 있는 "search" 부분에서 검색 조건 입력QueryString 넘어오는 searchQuery 변수를 ItemSearchDto 객체의 멤버변수에 초기화
  • QueryString 으로 넘어오는 "page" 변수를 page 파라미터에 넘김
@Controller
@RequiredArgsConstructor // @Autowired로도 쓸 수 있음
public class MainController {

    // @Autowired ->  @RequiredArgsConstructor를 써도 됨 (final 붙여야함)
    private final ItemService itemService;

    @GetMapping("/")
    public String main(ItemSearchDto itemSearchDto, Optional<Integer> page, Model model) {
        Pageable pageable = PageRequest.of(page.isPresent() ? page.get() : 0, 6);
        Page<ItemMainDto> items = itemService.getItemMainDto(itemSearchDto, pageable);

        model.addAttribute("items", items);
        model.addAttribute("itemSearchDto", itemSearchDto);
        model.addAttribute("maxPage", 5);

        return "main";
    }
}

5. Main Page

  • 배너는 BootStrap Carousel 컴포넌트 사용
  • 상품은 BootStrap Card 컴포넌트 사용
  • 상품 이미지를 요청하는 src URL 은 상품 이미지 정보에 담긴 imgUrl 로 지정
<img th:src="${item.imgUrl}" class="card-img-top" th:alt="${item.itemNm}" height="300">

6. 상품 이미지 파일 불러오기 동작 원리

  • application.properties 파일
    • 로컬 내에 파일이 저장된 경로를 uploadPath로 지정
# 리소스 업로드 경로
uploadPath=file:///D:/shop/
  • WebMvcConfigurer 클래스
    • "/images/"로 시작하는 URL 패턴의 요청이 들어오면 uploadPath를 기준으로 파일 탐색
// 이미지 업로드 파일 경로
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

//파일 올리고 이미지 올리고 이럴 때 웹이 로컬을 막 건들면 안되니까 설정 파일을 하나 만들어줌
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    // 이미지/파일을 업로드하는 진짜 경로를 application properties에 uploadPath로 저장해놨는데 그걸 가져오게 하기 위함
    @Value(value = "${uploadPath}")
    private String uploadPath;  // 해당 경로를 사용할 수 있음

    @Override
    // registry를 등록해서 쓸 수 있게 함
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
         registry.addResourceHandler("/images/**") // 접근을 이 주소로 하게 함
                 .addResourceLocations(uploadPath); // 실제로는 여기인데 (위에 처럼함)
    }
}
  • SecurityConfig 클래스
    • "/images/"로 시작하는 URL 패턴의 요청은 모두 허용
// 메모리를 미리 올려놔야 하기 때문에 bean 붙이기
    @Bean
    // http 요청에 대한 보안 설정. 페이지 권한, 로그인 페이지, 로그아웃 메소드 설정 예정
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http.authorizeHttpRequests()  // 인증 여부 확인 -> 스프링 3.0 이하 버전은 authorizeRequests()로 설정
                // 스프링 3.0 이하 버전은 antMatchers(), mvcMatchers(), regexMatchers()으로 사용
 
                .requestMatchers("/", "/member/**", "/item/**", "/images/**").permitAll() // 아무나 페이지에 들어올 수 있고, member, item 밑에 있는 애들은 모두 permit 허용
  • 동작 순서

① src = "images/item/파일명"으로 이미지 파일 요청

② WebConfigurer 에 지정된 핸들러대로 uploadPath를 기준으로 요청 처리

③ "///D:/shop/item/상품명"을 수행하여 일치하는 파일 반환