[12.13] 내일배움캠프[Spring] TIL-31

박상훈·2022년 12월 13일
0

내일배움캠프[TIL]

목록 보기
31/72

[12.13] 내일배움캠프[Spring] TIL-31

1. 페이징, 정렬 처리

  • 지난 포스팅 : JWT 방식을 사용하여, 등록된 User의 인식 시, Secret Key로 복호화가 가능한 Token을 클라이언트에게 제공하여, 다음 작업의 요청 시 요청 데이터와, 토큰을 같이 Header에 전송하여 서버에서 검증단계를 거쳐 로그인의 상태에서 할 수 있는 작업을 할 수 있게 했다.
  • 오늘은 이렇게 검증된 회원이 담은 데이터를 html에서 보여줄 때, 1, 2, 3이런식으로 페이징 처리를 할 수 있게한다.
  • 최저가, 이름순 , 오름차순, 내림차순등으로 필터링 정렬을 가능하게 한다.
  • 사용자 별로 폴더를 생성할 수 있으며, 폴더에 따른 상품을 담을 수 있다.
    -> 폴더의 생성시 중복을 제거 해야하며, 한 상품은 같은 폴더에 중복으로 담길 수 없다.
    -> 어떤 유저의 폴더인지 구별하여 폴더 마다의 상품을 보여줄 수 있어야한다.

페이징

  • page : 조회할 페이지 번호( 1번부터 )
  • size : 한 페이지에 보여줄 컨텐츠 개수

정렬

  • sortBy ( 정렬 항목 )
    1) id : Product 테이블의 id
    2) title : 상품명
    3) lprice : 최저가
  • isAsc( 오름차순 ? )
    1) true : 오름차순 (asc)
    2) false : 내림차순 (desc)

Spring - Page 구현체

  • number : 조회된 페이지 번호 ( 0부터 시작 )
  • content : 조회된 컨텐츠 정보
  • size : 한 페이지에서 보여줄 컨텐츠 개수
  • NumberOfElements : 실제 조회된 컨텐츠의 개수
  • totalElements : 전체 상품의 개수( 회원이 등록한 모든 상품의 개수 )
  • totalPage : 전체 페이지의 개수
  • first : 첫번째 페이지인가? (boolean)
  • last : 마지막 페이지인가? (boolean)
totalPages = totalElement / size 결과를 소수점 올림
1 / 10 = 0.1 =>1 페이지
9 / 10 = 0.9 =>1페이지
10 / 10 = 1 =>1페이지
11 / 10 => 1.1 =>2페이지

Product API 변경하기

기능MethodURLRequest
메인 페이지GET/api/shop--
키워드로 상품 검색하고 그 결과를 목록으로 보여주기GET/api/search?query=검색어--
관심 상품 등록하기POST/api/productsHeader
Authorization : Bearer <JWT>
{
"title" : String,
”image” : String,
"link" : String,
"lprice" : int
}
관심 상품 조회하기GET/api/products?sortBy=String&isAsc=boolean&size=int&page=intHeader
Authorization : Bearer<JWT>
관심 상품 최저가 등록하기PUT/api/products/{id}Header
Authorization : Bearer <JWT>{
"myprice" : int
}

Server - ProductController

		// 관심 상품 조회하기
    @GetMapping("/products")
    public Page<Product> getProducts(
            @RequestParam("page") int page,
            @RequestParam("size") int size,
            @RequestParam("sortBy") String sortBy,
            @RequestParam("isAsc") boolean isAsc,
            HttpServletRequest request
    ) {
        // 응답 보내기
        return productService.getProducts(request, page-1, size, sortBy, isAsc);
    }

Server - ProductService

@Transactional(readOnly = true)
    public Page<Product> getProducts(HttpServletRequest request,
                                              int page, int size, String sortBy, boolean isAsc) {
        // 페이징 처리
        Sort.Direction direction = isAsc ? Sort.Direction.ASC : Sort.Direction.DESC;
        Sort sort = Sort.by(direction, sortBy);
        Pageable pageable = PageRequest.of(page, size, sort);

        // Request에서 Token 가져오기
        String token = jwtUtil.resolveToken(request);
        Claims claims;

        // 토큰이 있는 경우에만 관심상품 조회 가능
        if (token != null) {
            // Token 검증
            if (jwtUtil.validateToken(token)) {
                // 토큰에서 사용자 정보 가져오기
                claims = jwtUtil.getUserInfoFromToken(token);
            } else {
                throw new IllegalArgumentException("Token Error");
            }

            // 토큰에서 가져온 사용자 정보를 사용하여 DB 조회
            User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
                    () -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
            );

            // 사용자 권한 가져와서 ADMIN 이면 전체 조회, USER 면 본인이 추가한 부분 조회
            UserRoleEnum userRoleEnum = user.getRole();
            System.out.println("role = " + userRoleEnum);

            Page<Product> products;

            if (userRoleEnum == UserRoleEnum.USER) {
                // 사용자 권한이 USER일 경우
                products = productRepository.findAllByUserId(user.getId(), pageable);
            } else {
                products = productRepository.findAll(pageable);
            }

            return products;

        } else {
            return null;
        }
    }

Server - ProductRepository

Page<Product> findAllByUserId(Long userId, Pageable pageable);
Page<Product> findAll(Pageable pageable);

Pageable?

  • GET/api/products?sortBy=String&isAsc=boolean&size=int&page=int
    -> 이런 요청을 Pageable 객체로 담아 JPA Repository에게 보낸다.
    -> JPA Repository는 원하는 페이지 만큼의 product의 정보를 보내주는 것.
    -> 참고 : https://tecoble.techcourse.co.kr/post/2021-08-15-pageable/

2. 폴더 설계

폴더 테이블(Entity의 설계)

  • 회원의 폴더는 그 회원에게만 보여야한다.
  • 회원 - 폴더의 관계는 회원 - 상품의 관계와 같다.

기존 설계의 한계점

@Entity
public class Folder {
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Id
    private Long id;

		@Column(nullable = false)
		private String name;

    @Column(nullable = false)
    private Long userId;
}

  • Folder가 가지고 있는 userId가 유저의 id라는 것은 개발을 진행한 사람만 알 수 있음!!
  • 객체와 DB는 서로의 관계를 파악할 수 없음
  • 회원 -> 폴더 조회시 문제점
// 1. 로그인한 회원 (user1) 의 id 를 조회
Long userId = user1.getId();
// 2. userId 로 저장된 모든 folder 조회
List<Folder> folders = folderRepository.findAllByUserId(userId);
public interface FolderRepository extends JpaRepository<Folder, Long> {
    List<Folder> findAllByUserId(Long userId);
}
  • 폴더 -> 회원 조회시 문제점
// 1. folder1 의 userId 를 조회
Long userId = folder1.getUserId();
// 2. userId 로 저장된 회원 조회
User user = userRepository.findById(userId);

Jpa 연관 관계를 이용한 테이블 설계

회원 1명이 여러 폴더를 가질 수 있음

  • @OneToMany
public class User {
    @OneToMany
    private List<Folder> folders;
}
  • 이렇게 저장 시 회원의 폴더를 조회한다면
List<Folder> folders = user.getFolders();
  • 기존의 방식은 User.getId()를 해와서 RepositoryfindById(UserId)를 해줘야 했음.

폴더 여러개를 1명의 회원이 가질 수 있음

  • @OneToMany
public class Folder{
    @ManyToOne
    private User user;
}
  • 폴더를 소유한 유저를 조회
folder.getUser();

객체의 연관 관계를 맺었을 때 모습

  • 폴더를 소유한 UserId가 아닌 User객체를 저장

  • 외래키를 통한 관계 설정

Jpa 연관관계 설정방법

@ManyToOne
@JoinColumn(name = "USER_ID", nullable = false)
private User user;
  • @JoinColumn
    1) name : 외래키 명
    2)nullable : false(default) -> 예) 폴더는 회원에 의해서만 만들어짐. user 값이 필수, true -> 예) 공용폴더의 경우, 폴더의 user 객체를 null 로 설정하기로 함

폴더의 생성 및 조회

API 설계

기능MethodURLRequestResponse
회원의 폴더 생성POST/api/folders{
folderNames: [String, …]
}
--
회원의 폴더 조회GET/api/user-folder--index.htmlmodel 추가 → folders

User Entity Add

    @OneToMany
    List<Folder> folders = new ArrayList<>();

Folder Entitiy

package com.sparta.myselectshop.entity;

import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@Getter
@Entity
@NoArgsConstructor
public class Folder {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false)
    private String name;

    @ManyToOne
    @JoinColumn(name = "USER_ID", nullable = false)
    private User user;

    public Folder(String name, User user) {
        this.name = name;
        this.user = user;
    }
}

FolderRequestDto

package com.sparta.myselectshop.dto;

import lombok.Getter;

import java.util.List;

@Getter
public class FolderRequestDto {
    List<String> folderNames;
}

FolderController

package com.sparta.myselectshop.controller;

import com.sparta.myselectshop.dto.FolderRequestDto;
import com.sparta.myselectshop.entity.Folder;
import com.sparta.myselectshop.service.FolderService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class FolderController {

    private final FolderService folderService;

    @PostMapping("/folders")
    public List<Folder> addFolders(
            @RequestBody FolderRequestDto folderRequestDto,
            HttpServletRequest request
    ) {

        List<String> folderNames = folderRequestDto.getFolderNames();

        return folderService.addFolders(folderNames, request);
    }
}

FolderService

package com.sparta.myselectshop.service;

import com.sparta.myselectshop.entity.Folder;
import com.sparta.myselectshop.entity.User;
import com.sparta.myselectshop.jwt.JwtUtil;
import com.sparta.myselectshop.repository.FolderRepository;
import com.sparta.myselectshop.repository.UserRepository;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;

@Service
@RequiredArgsConstructor
public class FolderService {

    private final FolderRepository folderRepository;
    private final UserRepository userRepository;
    private final JwtUtil jwtUtil;


    // 로그인한 회원에 폴더들 등록
    @Transactional
    public List<Folder> addFolders(List<String> folderNames, HttpServletRequest request) {

        // Request에서 Token 가져오기
        String token = jwtUtil.resolveToken(request);
        Claims claims;

        // 토큰이 있는 경우에만 관심상품 조회 가능
        if (token != null) {

            // Token 검증
            if (jwtUtil.validateToken(token)) {
                // 토큰에서 사용자 정보 가져오기
                claims = jwtUtil.getUserInfoFromToken(token);
            } else {
                throw new IllegalArgumentException("Token Error");
            }

            // 토큰에서 가져온 사용자 정보를 사용하여 DB 조회
            User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
                    () -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
            );

            List<Folder> folderList = new ArrayList<>();

            for (String folderName : folderNames) {
                Folder folder = new Folder(folderName, user);
                folderList.add(folder);
            }

            return folderRepository.saveAll(folderList);
        } else {
            return null;
        }
    }

    // 로그인한 회원이 등록된 모든 폴더 조회
    public List<Folder> getFolders(HttpServletRequest request) {

        // 사용자의 정보를 가져온다
        // Request에서 Token 가져오기
        String token = jwtUtil.resolveToken(request);
        Claims claims;

        // 토큰이 있는 경우에만 관심상품 조회 가능
        if (token != null) {
            // Token 검증
            if (jwtUtil.validateToken(token)) {
                // 토큰에서 사용자 정보 가져오기
                claims = jwtUtil.getUserInfoFromToken(token);
            } else {
                throw new IllegalArgumentException("Token Error");
            }

            // 토큰에서 가져온 사용자 정보를 사용하여 DB 조회
            User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
                    () -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
            );

            return folderRepository.findAllByUser(user);

        } else {
            return null;
        }
    }

}

FolderRepository

package com.sparta.myselectshop.repository;

import com.sparta.myselectshop.entity.Folder;
import com.sparta.myselectshop.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface FolderRepository extends JpaRepository<Folder, Long> {

    List<Folder> findAllByUser(User user);
}

ShopController ( 회원이 저장한 폴더 조회 )

package com.sparta.myselectshop.controller;

import com.sparta.myselectshop.service.FolderService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;

@Controller
@RequiredArgsConstructor
@RequestMapping("/api")
public class ShopController {

    private final FolderService folderService;

    @GetMapping("/shop")
    public ModelAndView shop() {
        return new ModelAndView("index");
    }

    // 로그인 한 유저가 메인페이지를 요청할 때 가지고있는 폴더를 반환
    @GetMapping("/user-folder")
    public String getUserInfo(Model model, HttpServletRequest request) {

        model.addAttribute("folders", folderService.getFolders(request));

        return "/index :: #fragment";
    }

}

관심상품에 폴더 추가 - 이해 부족으로 다음 포스팅에 올릴 예정.

profile
기록하는 습관

0개의 댓글