ํ์ ๊ธฐ๋ฅ ๊ตฌํ
ํ์ ๊ด๋ จ API ์ค๊ณ
| ๊ธฐ๋ฅ | Method | URL | ์ค๋ช |
| ๋ก๊ทธ์ธ ํ์ด์ง | GET | /api/user/login-page | ๋ก๊ทธ์ธ ํ์ด์ง ํธ์ถ |
| ํ์๊ฐ์ ํ์ด์ง | GET | /api/user/signup | ํ์๊ฐ์ ํ์ด์ง ํธ์ถ |
| ํ์๊ฐ์ | POST | /api/user/signup | ํ์๊ฐ์ |
| ํ์ ์ ๋ณด ์์ฒญ | GET | /api/user-info | ํ์ ๊ด๋ จ ์ ๋ณด ๋ฐ๊ธฐ |
ํ์๊ฐ์ API ๊ตฌํ
- ํ์ Entity์ Enum ์ถ๊ฐ: entity > User, UserEnum
@Getter
@Setter
@NoArgsConstructor
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
@Enumerated(value = EnumType.STRING)
private UserRoleEnum role;
public User(String username, String password, String email, UserRoleEnum role) {
this.username = username;
this.password = password;
this.email = email;
this.role = role;
}
}
package com.sparta.myselectshop.entity;
public enum UserRoleEnum {
USER(Authority.USER), // ์ฌ์ฉ์ ๊ถํ
ADMIN(Authority.ADMIN); // ๊ด๋ฆฌ์ ๊ถํ
private final String authority;
UserRoleEnum(String authority) {
this.authority = authority;
}
public String getAuthority() {
return this.authority;
}
public static class Authority {
public static final String USER = "ROLE_USER";
public static final String ADMIN = "ROLE_ADMIN";
}
}
- controller > UserController ์ถ๊ฐ
@Slf4j
@Controller
@RequiredArgsConstructor
@RequestMapping("/api")
public class UserController {
private final UserService userService;
@GetMapping("/user/login-page")
public String loginPage() {
return "login";
}
@GetMapping("/user/signup")
public String signupPage() {
return "signup";
}
@PostMapping("/user/signup")
public String signup(@Valid SignupRequestDto requestDto, BindingResult bindingResult) {
// Validation ์์ธ์ฒ๋ฆฌ
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
if(fieldErrors.size() > 0) {
for (FieldError fieldError : bindingResult.getFieldErrors()) {
log.error(fieldError.getField() + " ํ๋ : " + fieldError.getDefaultMessage());
}
return "redirect:/api/user/signup";
}
userService.signup(requestDto);
return "redirect:/api/user/login-page";
}
// ํ์ ๊ด๋ จ ์ ๋ณด ๋ฐ๊ธฐ
@GetMapping("/user-info")
@ResponseBody
public UserInfoDto getUserInfo(@AuthenticationPrincipal UserDetailsImpl userDetails) {
String username = userDetails.getUser().getUsername();
UserRoleEnum role = userDetails.getUser().getRole();
boolean isAdmin = (role == UserRoleEnum.ADMIN);
return new UserInfoDto(username, isAdmin);
}
}
- dto > SignupRequestDto, UserInfoDto ์ถ๊ฐ
@Getter
@Setter
public class SignupRequestDto {
@NotBlank
private String username;
@NotBlank
private String password;
@Email
@NotBlank
private String email;
private boolean admin = false;
private String adminToken = "";
}
@Getter
@AllArgsConstructor
public class UserInfoDto {
String username;
boolean isAdmin;
}
- service > UserService ์์ฑ
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
// ADMIN_TOKEN
private final String ADMIN_TOKEN = "AAABnvxRVklrnYxKZ0aHgTBcXukeZygoC";
public void signup(SignupRequestDto requestDto) {
String username = requestDto.getUsername();
String password = passwordEncoder.encode(requestDto.getPassword());
// ํ์ ์ค๋ณต ํ์ธ
Optional<User> checkUsername = userRepository.findByUsername(username);
if (checkUsername.isPresent()) {
throw new IllegalArgumentException("์ค๋ณต๋ ์ฌ์ฉ์๊ฐ ์กด์ฌํฉ๋๋ค.");
}
// email ์ค๋ณตํ์ธ
String email = requestDto.getEmail();
Optional<User> checkEmail = userRepository.findByEmail(email);
if (checkEmail.isPresent()) {
throw new IllegalArgumentException("์ค๋ณต๋ Email ์
๋๋ค.");
}
// ์ฌ์ฉ์ ROLE ํ์ธ
UserRoleEnum role = UserRoleEnum.USER;
if (requestDto.isAdmin()) {
if (!ADMIN_TOKEN.equals(requestDto.getAdminToken())) {
throw new IllegalArgumentException("๊ด๋ฆฌ์ ์ํธ๊ฐ ํ๋ ค ๋ฑ๋ก์ด ๋ถ๊ฐ๋ฅํฉ๋๋ค.");
}
role = UserRoleEnum.ADMIN;
}
// ์ฌ์ฉ์ ๋ฑ๋ก
User user = new User(username, password, email, role);
userRepository.save(user);
}
}
- repository > UserRepository ์์ฑ
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
}
JWT ๋ก๊ทธ์ธ ์ธ์ฆ ์ฒ๋ฆฌ(Filter)
- ์ด์ ๊น์ง๋ JWT๋ฅผ ์์ฑํ ์๋ฒ์์ ์ฟ ํค๋ฅผ ์ง์ ์์ฑํด Client๋ก ์ ๋ฌํจ → ์๋ต Header์ Set-Cookie๋ก ๊ฐ์ด ์ ๋ฌ๋์ด, Client์์ ์ฟ ํค๋ฅผ ์ง์ ์ฟ ํค ์ ์ฅ์์ ์ ์ฅํ์ง ์์๋ ์๋์ผ๋ก ๊ฐ์ด ์ ์ฅ๋จ
- ๋ํ, Client์์ ๋ฐ๋ก Header์ JWT๋ฅผ ์ง์ ๋ด์์ ๋ณด๋ด์ง ์๊ณ ์๋ฒ์์ Request์ ๋ด๊ธด ์ฟ ํค ๊ฐ๋ค ์ค JWT์ ํด๋นํ๋ ๊ฐ์ ๊ฐ์ ธ์ ์ฌ์ฉํจ
- Client์ Server๊ฐ JWT๋ฅผ ์ฃผ๊ณ ๋ฐ๋ ๋ฐฉ์์ ๊ฐ๋ฐ์๊ฐ ์ ํํจ
- ์ด๋ฒ์๋ Client์ Server ๋ชจ๋ JWT๋ฅผ ์ง์ HTTP Header์ ๋ด์์ ์ ๋ฌ๋ฐ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํ
JWT ์ฌ์ฉ ํ๋ฆ
1. Client๊ฐ username๊ณผ password๋ก ๋ก๊ทธ์ธ ์ฑ๊ณต์,
1-1) ์๋ฒ์์ "๋ก๊ทธ์ธ ์ ๋ณด"๋ฅผ JWT๋ก ์ํธํ(Secret Key ์ฌ์ฉ)
1-2) JWT๋ฅผ Client ์๋ต Header์ ์ ๋ฌ
Authorization: Bearer <JWT>
1-3) Client์์ JWT ์ ์ฅ(์ฟ ํค)
2. Client์์ JWT ํตํด ์ธ์ฆ ๋ฐฉ๋ฒ
2-1) JWT๋ฅผ API ์์ฒญ ์๋ง๋ค Header์ ํฌํจ
ex) HTTP Headers
Content-Type: application/json
Authorization: Bearer <JWT>
...
2-2) Server
- JWT ์์กฐ ์ฌ๋ถ ๊ฒ์ฆ(Secret Key ์ฌ์ฉ)
- JWT ์ ํจ๊ธฐ๊ฐ์ด ์ง๋์ง ์์๋์ง ๊ฒ์ฆ
- ๊ฒ์ฆ ์ฑ๊ณต์, JWT์์ ์ฌ์ฉ์ ์ ๋ณด ๊ฐ์ ธ์ ํ์ธ
๋ก๊ทธ์ธ ์ธ์ฆ ์ฒ๋ฆฌ ๊ตฌํ
jwt > JwtUtil ์์ฑ
@Slf4j(topic = "JwtUtil")
@Component
public class JwtUtil {
// Header KEY ๊ฐ
public static final String AUTHORIZATION_HEADER = "Authorization";
// ์ฌ์ฉ์ ๊ถํ ๊ฐ์ KEY
public static final String AUTHORIZATION_KEY = "auth";
// Token ์๋ณ์
public static final String BEARER_PREFIX = "Bearer ";
// ํ ํฐ ๋ง๋ฃ์๊ฐ
private final long TOKEN_TIME = 60 * 60 * 1000L; // 60๋ถ
@Value("${jwt.secret.key}") // Base64 Encode ํ SecretKey
private String secretKey;
private Key key;
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
@PostConstruct
public void init() {
byte[] bytes = Base64.getDecoder().decode(secretKey);
key = Keys.hmacShaKeyFor(bytes);
}
// ํ ํฐ ์์ฑ
public String createToken(String username, UserRoleEnum role) {
Date date = new Date();
return BEARER_PREFIX +
Jwts.builder()
.setSubject(username) // ์ฌ์ฉ์ ์๋ณ์๊ฐ(ID)
.claim(AUTHORIZATION_KEY, role) // ์ฌ์ฉ์ ๊ถํ
.setExpiration(new Date(date.getTime() + TOKEN_TIME)) // ๋ง๋ฃ ์๊ฐ
.setIssuedAt(date) // ๋ฐ๊ธ์ผ
.signWith(key, signatureAlgorithm) // ์ํธํ ์๊ณ ๋ฆฌ์ฆ
.compact();
}
// header ์์ JWT ๊ฐ์ ธ์ค๊ธฐ
public String getJwtFromHeader(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)) {
return bearerToken.substring(7);
}
return null;
}
// ํ ํฐ ๊ฒ์ฆ
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (SecurityException | MalformedJwtException | SignatureException e) {
log.error("Invalid JWT signature, ์ ํจํ์ง ์๋ JWT ์๋ช
์
๋๋ค.");
} catch (ExpiredJwtException e) {
log.error("Expired JWT token, ๋ง๋ฃ๋ JWT token ์
๋๋ค.");
} catch (UnsupportedJwtException e) {
log.error("Unsupported JWT token, ์ง์๋์ง ์๋ JWT ํ ํฐ ์
๋๋ค.");
} catch (IllegalArgumentException e) {
log.error("JWT claims is empty, ์๋ชป๋ JWT ํ ํฐ ์
๋๋ค.");
}
return false;
}
// ํ ํฐ์์ ์ฌ์ฉ์ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
public Claims getUserInfoFromToken(String token) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}
}
security > UserDetailsImpl๊ณผ UserDetailsServiceImpl ์์ฑ
public class UserDetailsImpl implements UserDetails {
private final User user;
public UserDetailsImpl(User user) {
this.user = user;
}
public User getUser() {
return user;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
UserRoleEnum role = user.getRole();
String authority = role.getAuthority();
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(authority);
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(simpleGrantedAuthority);
return authorities;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
๋ก๊ทธ์ธ ์ ์ฌ์ฉํ Dto ์์ฑ, dto > LoginRequestDto
@Setter
@Getter
public class LoginRequestDto {
private String username;
private String password;
}
์ธ์ฆ/์ธ๊ฐ Filter ์์ฑ, security > JwtAuthenticationFilter, JwtAuthorizationFilter ์ถ๊ฐ
@Slf4j(topic = "๋ก๊ทธ์ธ ๋ฐ JWT ์์ฑ")
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final JwtUtil jwtUtil;
public JwtAuthenticationFilter(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
setFilterProcessesUrl("/api/user/login");
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
LoginRequestDto requestDto = new ObjectMapper().readValue(request.getInputStream(), LoginRequestDto.class);
return getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(
requestDto.getUsername(),
requestDto.getPassword(),
null
)
);
} catch (IOException e) {
log.error(e.getMessage());
throw new RuntimeException(e.getMessage());
}
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) {
String username = ((UserDetailsImpl) authResult.getPrincipal()).getUsername();
UserRoleEnum role = ((UserDetailsImpl) authResult.getPrincipal()).getUser().getRole();
String token = jwtUtil.createToken(username, role);
response.addHeader(JwtUtil.AUTHORIZATION_HEADER, token);
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
response.setStatus(401);
}
}
@Slf4j(topic = "JWT ๊ฒ์ฆ ๋ฐ ์ธ๊ฐ")
public class JwtAuthorizationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final UserDetailsServiceImpl userDetailsService;
public JwtAuthorizationFilter(JwtUtil jwtUtil, UserDetailsServiceImpl userDetailsService) {
this.jwtUtil = jwtUtil;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain) throws ServletException, IOException {
String tokenValue = jwtUtil.getJwtFromHeader(req);
if (StringUtils.hasText(tokenValue)) {
if (!jwtUtil.validateToken(tokenValue)) {
log.error("Token Error");
return;
}
Claims info = jwtUtil.getUserInfoFromToken(tokenValue);
try {
setAuthentication(info.getSubject());
} catch (Exception e) {
log.error(e.getMessage());
return;
}
}
filterChain.doFilter(req, res);
}
// ์ธ์ฆ ์ฒ๋ฆฌ
public void setAuthentication(String username) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication = createAuthentication(username);
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
}
// ์ธ์ฆ ๊ฐ์ฒด ์์ฑ
private Authentication createAuthentication(String username) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}
}
config ์ค์ , config > WebSecurityConfig
@Configuration
@EnableWebSecurity // Spring Security ์ง์์ ๊ฐ๋ฅํ๊ฒ ํจ
@RequiredArgsConstructor
public class WebSecurityConfig {
private final JwtUtil jwtUtil;
private final UserDetailsServiceImpl userDetailsService;
private final AuthenticationConfiguration authenticationConfiguration;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil);
filter.setAuthenticationManager(authenticationManager(authenticationConfiguration));
return filter;
}
@Bean
public JwtAuthorizationFilter jwtAuthorizationFilter() {
return new JwtAuthorizationFilter(jwtUtil, userDetailsService);
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// CSRF ์ค์
http.csrf((csrf) -> csrf.disable());
// ๊ธฐ๋ณธ ์ค์ ์ธ Session ๋ฐฉ์์ ์ฌ์ฉํ์ง ์๊ณ JWT ๋ฐฉ์์ ์ฌ์ฉํ๊ธฐ ์ํ ์ค์
http.sessionManagement((sessionManagement) ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
http.authorizeHttpRequests((authorizeHttpRequests) ->
authorizeHttpRequests
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // resources ์ ๊ทผ ํ์ฉ ์ค์
.requestMatchers("/").permitAll() // ๋ฉ์ธ ํ์ด์ง ์์ฒญ ํ๊ฐ
.requestMatchers("/api/user/**").permitAll() // '/api/user/'๋ก ์์ํ๋ ์์ฒญ ๋ชจ๋ ์ ๊ทผ ํ๊ฐ
.anyRequest().authenticated() // ๊ทธ ์ธ ๋ชจ๋ ์์ฒญ ์ธ์ฆ์ฒ๋ฆฌ
);
http.formLogin((formLogin) ->
formLogin
.loginPage("/api/user/login-page").permitAll()
);
// ํํฐ ๊ด๋ฆฌ
http.addFilterBefore(jwtAuthorizationFilter(), JwtAuthenticationFilter.class);
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}

ํ์๋ณ ์ํ API ๊ตฌํ
์ํ๊ณผ ํ์์ ๊ด๊ณ
- ๊ด์ฌ ์ํ ๋ฑ๋ก ์, ๋ฑ๋ก์ ์์ฒญํ "ํ์ ์ ๋ณด" ์ถ๊ฐ ํ์
- ์ํ : ํ์ = N : 1

- ํ์ ๊ฐ์ฒด์์ ์ํ ๊ฐ์ฒด๋ฅผ ์กฐํํ๋ ๊ฒฝ์ฐ๊ฐ ์๊ธฐ ๋๋ฌธ์, ์ํ๊ณผ ํ์์ N : 1 ๋จ๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ๋ก ์ค์
Product ์ํฐํฐ์ User Entity์์ ๊ด๊ณ ์ค์ ์ถ๊ฐ
@Entity // JPA๊ฐ ๊ด๋ฆฌํ ์ ์๋ Entity ํด๋์ค ์ง์
@Getter
@Setter
@Table(name = "product") // ๋งคํํ ํ
์ด๋ธ์ ์ด๋ฆ์ ์ง์
@NoArgsConstructor
public class Product extends Timestamped {
...
@ManyToOne(fetch = FetchType.LAZY) // Product ์กฐํํ ๋๋ง๋ค ํ์ ์ ๋ณด๊ฐ ์ ๋ถ ํ์ํ๊ฑด ์๋๊ธฐ ๋๋ฌธ์ ์ง์ฐ๋ก๋ฉ ์ค์
@JoinColumn(name = "user_id", nullable = false)
private User user;
...
}
ProductController ๋ด์ ๊ด์ฌ ์ํ ๋ฑ๋ก API์ ๊ด์ฌ ์ํ ์กฐํ API์ ํ์ ์ ๋ณด ์ถ๊ฐ
@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
public class ProductController {
private final ProductService productService;
// ์ํ ๋ฑ๋ก
@PostMapping("/products")
public ProductResponseDto createProduct(@RequestBody ProductRequestDto requestDto, @AuthenticationPrincipal UserDetailsImpl userDetails) {
return productService.createProduct(requestDto, userDetails.getUser());
}
...
// ์ํ ์กฐํ
@GetMapping("/products")
public List<ProductResponseDto> getProducts(@AuthenticationPrincipal UserDetailsImpl userDetails) {
return productService.getProducts(userDetails.getUser());
}
}
- Product ์ํฐํฐ ์์ฑ์์ User ์ ๋ณด ์ถ๊ฐ
@Getter
@Setter
@Table(name = "product") // ๋งคํํ ํ
์ด๋ธ์ ์ด๋ฆ์ ์ง์
@NoArgsConstructor
public class Product extends Timestamped {
...
@ManyToOne(fetch = FetchType.LAZY) // Product ์กฐํํ ๋๋ง๋ค ํ์ ์ ๋ณด๊ฐ ์ ๋ถ ํ์ํ๊ฑด ์๋๊ธฐ ๋๋ฌธ์ ์ง์ฐ๋ก๋ฉ ์ค์
@JoinColumn(name = "user_id", nullable = false)
private User user;
public Product(ProductRequestDto requestDto, User user) {
this.title = requestDto.getTitle();
this.image = requestDto.getImage();
this.link = requestDto.getLink();
this.lprice = requestDto.getLprice();
this.user = user;
}
...
}
@Service
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository productRepository;
public static final int MIN_MY_PRICE = 100;
public ProductResponseDto createProduct(ProductRequestDto requestDto, User user) {
Product product = productRepository.save(new Product(requestDto, user));
return new ProductResponseDto(product);
}
...
public List<ProductResponseDto> getProducts(User user) {
List<Product> productList = productRepository.findAllByUser(user);
List<ProductResponseDto> responseDtoList = new ArrayList<>();
for (Product product : productList) {
responseDtoList.add(new ProductResponseDto(product));
}
return responseDtoList;
}
...
}
Admin ๊ณ์ ์ ๋ชจ๋ ํ์์ด ๋ฑ๋กํ ๋ชจ๋ ์ํ ์กฐํ API ๊ตฌํ
public class ProductController {
private final ProductService productService;
...
@GetMapping("/admin/products")
public List<ProductResponseDto> getAllProducts() {
return productService.getAllProducts();
}
}
public class ProductService {
private final ProductRepository productRepository;
...
public List<ProductResponseDto> getAllProducts() {
List<Product> productList = productRepository.findAll();
List<ProductResponseDto> responseDtoList = new ArrayList<>();
for (Product product : productList) {
responseDtoList.add(new ProductResponseDto(product));
}
return responseDtoList;
}
}



์ํ ํ์ด์ง ๋ฐ ์ ๋ ฌ
ํ์ด์ง ๋ฐ ์ ๋ ฌ ์ค๊ณ
Client → Server
- ํ์ด์ง
- page: ์กฐํํ ํ์ด์ง ๋ฒํธ(1๋ถํฐ ์์)
- size: ํ ํ์ด์ง์ ๋ณด์ฌ์ค ์ํ ๊ฐ์(10๊ฐ๋ก ๊ณ ์ )
- ์ ๋ ฌ
- sortBy(์ ๋ ฌ ํญ๋ชฉ)
- id: Product ํ ์ด๋ธ์ id
- title: ์ํ๋ช
- lprice: ์ต์ ๊ฐ - isAsc(์ค๋ฆ์ฐจ์)
- true: ์ค๋ฆ์ฐจ์(ASC)
- false: ๋ด๋ฆผ์ฐจ์(DESC)
- sortBy(์ ๋ ฌ ํญ๋ชฉ)
Server → Client
- number: ์กฐํ๋ ํ์ด์ง ๋ฒํธ(0๋ถํฐ ์์)
- content: ์กฐํ๋ ์ํ ์ ๋ณด(๋ฐฐ์ด)
- size: ํ ํ์ด์ง์ ๋ณด์ฌ์ค ์ํ ๊ฐ์
- numberOfElements: ์ค์ ์กฐํ๋ ์ํ ๊ฐ์
- totalElements: ์ ์ฒด ์ํ ๊ฐ์(ํ์์ด ๋ฑ๋กํ ๋ชจ๋ ์ํ์ ๊ฐ์)
- totalPages: ์ ์ฒด ํ์ด์ง ์
- totalPages = totalElements / size ๊ฒฐ๊ณผ๋ฅผ ์์์ ์ฌ๋ฆผ - first: ์ฒซ ํ์ด์ง์ธ์ง?(boolean)
- last: ๋ง์ง๋ง ํ์ด์ง์ธ์ง?(boolean)
๏นกSpring Data์์ ํ์ด์ง ๋ฐ ์ ๋ ฌ ๊ธฐ๋ฅ์ ์ ๊ณตํ๊ธฐ ๋๋ฌธ์ ์์ฝ๊ฒ ํ์ด์ง, ์ ๋ ฌ์ ๊ตฌํํ ์ ์์
- Pageable์ ์์ฝ๊ฒ ํ์ด์ง, ์ ๋ ฌ ์ฒ๋ฆฌ๋ฅผ ํ๊ธฐ ์ํด ์ ๊ณต๋๋ ์ธํฐํ์ด์ค์ด๋ฉฐ, PageRequest๋ ํด๋น ์ธํฐํ์ด์ค์ ๊ตฌํ์ฒด์
- ํ๋ผ๋ฏธํฐ๋ก (ํ์ฌ ํ์ด์ง(0 ์์), ๋ฐ์ดํฐ ๋ ธ์ถ ๊ฐ์, ์ ๋ ฌ ๋ฐฉ๋ฒ(ASC, DESC))
- ์์ฑ๋ Pageable ๊ตฌํ ๊ฐ์ฒด๋ฅผ Spring Data JPA์ Query Method ํ๋ผ๋ฏธํฐ์ ํจ๊ป ์ ๋ฌํ๋ฉด ํ์ด์ง ๋ฐ ์ ๋ ฌ ์ฒ๋ฆฌ๊ฐ ์๋ฃ๋ ๋ฐ์ดํฐ๋ฅผ Page ํ์ ์ผ๋ก ๋ฐํํจ
- Page ํ์ ์๋ Client์ ์ ๋ฌํด์ผํ ๋ฐ์ดํฐ์ธ totalPages, totalElements ๋ฑ์ ๋ฐ์ดํฐ๊ฐ ํจ๊ป ํฌํจํ๊ณ ์์
ํ์ด์ง ๋ฐ ์ ๋ ฌ ๊ตฌํ
public class ProductController {
private final ProductService productService;
...
@GetMapping("/products")
public Page<ProductResponseDto> getProducts(
@RequestParam("page") int page,
@RequestParam("size") int size,
@RequestParam("sortBy") String sortBy,
@RequestParam("isAsc") boolean isAsc,
@AuthenticationPrincipal UserDetailsImpl userDetails) {
return productService.getProducts(userDetails.getUser(),
page-1, size, sortBy, isAsc);
}
}
public class ProductService {
private final ProductRepository productRepository;
...
public Page<ProductResponseDto> getProducts(User user, int page, int size, String sortBy, boolean isAsc) {
Sort.Direction direction = isAsc ? Sort.Direction.ASC : Sort.Direction.DESC;
Sort sort = Sort.by(direction, sortBy); // Sort ๊ฐ์ฒด = (direction ๋ฐฉํฅ, ์ ๋ ฌ ํญ๋ชฉ)
Pageable pageable = PageRequest.of(page, size, sort);
// ํ์ฌ ๋ค์ด์จ User์ ๊ถํ์ด Admin์ธ์ง User์ธ์ง ํ์ธ
UserRoleEnum userRoleEnum = user.getRole();
Page<Product> productList;
if(userRoleEnum == UserRoleEnum.USER) {
productList = productRepository.findAllByUser(user, pageable);
} else {
productList = productRepository.findAll(pageable);
}
return productList.map(ProductResponseDto::new);
}
...
}
