Skip to content

Commit

Permalink
User JPA 적용, BindException 처리 (f-lab-edu#26)
Browse files Browse the repository at this point in the history
* User - JPA 적용

* User - ApiResult 적용 및 Controller 테스트 작성

(cherry picked from commit 4239842)

* Validation 체크 및 ControllerExceptionAdvice 생성

(cherry picked from commit e38b6f6)

* updateUserInfo - Patch로 변경

* Validation 체크 예외를 MethodArgumentNotValidException으로 변경

* ApiResult - ResultCode.CREATED 추가
  • Loading branch information
yeonkyungJoo authored and 조찬희 committed Feb 22, 2023
1 parent 36a105f commit dec634b
Show file tree
Hide file tree
Showing 11 changed files with 504 additions and 89 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.bbaemin.config.advice;

import org.bbaemin.config.response.ApiResult;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.List;

@RestControllerAdvice
public class ControllerExceptionAdvice {

@ExceptionHandler(MethodArgumentNotValidException.class)
public ApiResult<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
BindingResult bindingResult = e.getBindingResult();
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
return ApiResult.badRequest(fieldErrors);
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
package org.bbaemin.config.response;


import lombok.Getter;
import org.springframework.http.HttpStatus;

import static org.bbaemin.config.response.ApiResult.ResultCode.CREATED;
import static org.bbaemin.config.response.ApiResult.ResultCode.FAIL;
import static org.bbaemin.config.response.ApiResult.ResultCode.SUCCESS;

@Getter
public class ApiResult<T> {

private static final ApiResult<Void> OK = new ApiResult<>(SUCCESS);

enum ResultCode {
SUCCESS(200), FAIL(500);
SUCCESS(200),
CREATED(201),
FAIL(500);

private int code;

Expand All @@ -20,6 +25,7 @@ enum ResultCode {
}
}

@Getter
public static class Error<R> {
private HttpStatus httpStatus;
private R cause;
Expand Down Expand Up @@ -58,6 +64,10 @@ public static ApiResult<Void> ok() {
return ApiResult.OK;
}

public static <T> ApiResult<T> created(T result) {
return new ApiResult<>(CREATED, result);
}

public static <T> ApiResult<T> ok(T result) {
return new ApiResult<>(SUCCESS, result);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
package org.bbaemin.user.controller;

import lombok.RequiredArgsConstructor;
import org.bbaemin.config.response.ApiResult;
import org.bbaemin.user.controller.request.JoinRequest;
import org.bbaemin.user.controller.request.UpdateUserInfoRequest;
import org.bbaemin.user.controller.response.UserResponse;
import org.bbaemin.user.service.UserService;
import org.bbaemin.user.vo.User;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
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 java.util.List;
import java.util.stream.Collectors;
Expand All @@ -20,21 +28,22 @@ public class UserController {

// 회원 리스트
@GetMapping
public List<UserResponse> listUser() {
return userService.getUserList().stream()
public ApiResult<List<UserResponse>> listUser() {
List<UserResponse> userList = userService.getUserList().stream()
.map(UserResponse::new).collect(Collectors.toList());
return ApiResult.ok(userList);
}

// 회원 조회
@GetMapping("/{userId}")
public UserResponse getUser(@PathVariable Long userId) {
public ApiResult<UserResponse> getUser(@PathVariable Long userId) {
User user = userService.getUser(userId);
return new UserResponse(user);
return ApiResult.ok(new UserResponse(user));
}

// 회원 등록
@PostMapping
public UserResponse join(@RequestBody JoinRequest joinRequest) {
public ApiResult<UserResponse> join(@Validated @RequestBody JoinRequest joinRequest) {
User user = userService.join(
User.builder()
.email(joinRequest.getEmail())
Expand All @@ -43,25 +52,23 @@ public UserResponse join(@RequestBody JoinRequest joinRequest) {
.password(joinRequest.getPassword())
.phoneNumber(joinRequest.getPhoneNumber())
.build());
return new UserResponse(user);
return ApiResult.created(new UserResponse(user));
}

// 회원정보 수정
@PutMapping("/{userId}")
public UserResponse updateUserInfo(@PathVariable Long userId, @RequestBody UpdateUserInfoRequest updateUserInfoRequest) {
User user = userService.updateUserInfo(
User.builder()
.userId(userId)
.nickname(updateUserInfoRequest.getNickname())
.image(updateUserInfoRequest.getImage())
.phoneNumber(updateUserInfoRequest.getPhoneNumber())
.build());
return new UserResponse(user);
@PatchMapping("/{userId}")
public ApiResult<UserResponse> updateUserInfo(@PathVariable Long userId, @Validated @RequestBody UpdateUserInfoRequest updateUserInfoRequest) {
User user = userService.updateUserInfo(userId,
updateUserInfoRequest.getNickname(),
updateUserInfoRequest.getImage(),
updateUserInfoRequest.getPhoneNumber());
return ApiResult.ok(new UserResponse(user));
}

// 회원 탈퇴
@PatchMapping("/{userId}")
public void quit(@PathVariable Long userId) {
@PatchMapping("/{userId}/quit")
public ApiResult<Void> quit(@PathVariable Long userId) {
userService.quit(userId);
return ApiResult.ok();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,23 @@
import lombok.Builder;
import lombok.Getter;

import javax.validation.constraints.NotBlank;

@Getter
public class JoinRequest {

@NotBlank
private String email;
@NotBlank
private String nickname;
private String image;

// TODO - validation check : password.equals(passwordConfirm)
@NotBlank
private String password;
private String passwordConfirm;

@NotBlank
private String phoneNumber;

@Builder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
import lombok.Builder;
import lombok.Getter;

import javax.validation.constraints.NotBlank;

@Getter
public class UpdateUserInfoRequest {

@NotBlank
private String nickname;
private String image;
@NotBlank
private String phoneNumber;

@Builder
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package org.bbaemin.user.controller.response;

import lombok.Getter;
import org.bbaemin.user.vo.User;

@Getter
public class UserResponse {

private Long userId;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,68 +1,12 @@
package org.bbaemin.user.repository;

import org.bbaemin.user.vo.User;
import org.springframework.stereotype.Component;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@Transactional(readOnly = true)
@Repository
public interface UserRepository extends JpaRepository<User, Long> {

@Component
public class UserRepository {

private static final Map<Long, User> map = new ConcurrentHashMap<>();
private static final AtomicLong id = new AtomicLong(0L);

public static AtomicLong getId() {
return id;
}

static {
map.put(getId().incrementAndGet(), User.builder()
.userId(getId().get())
.email("user1@email.com")
.nickname("user1")
.image(null)
.phoneNumber("010-1234-5678")
.build());
map.put(getId().incrementAndGet(), User.builder()
.userId(getId().get())
.email("user2@email.com")
.nickname("user2")
.image(null)
.phoneNumber("010-1111-2222")
.build());
}

public static void clear() {
map.clear();
}

public List<User> findAll() {
return new ArrayList<>(map.values());
}

public User findById(Long userId) {
return map.get(userId);
}

public User insert(User user) {
Long userId = getId().incrementAndGet();
user.setUserId(userId);
map.put(userId, user);
return user;
}

public User update(User user) {
map.put(user.getUserId(), user);
return user;
}

public void updateUserDeleted(Long userId) {
User user = map.get(userId);
user.setDeleted(true);
map.put(userId, user);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
import org.bbaemin.user.repository.UserRepository;
import org.bbaemin.user.vo.User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.NoSuchElementException;

@Transactional(readOnly = true)
@Service
@RequiredArgsConstructor
public class UserService {
Expand All @@ -18,19 +22,31 @@ public List<User> getUserList() {
}

public User getUser(Long userId) {
return userRepository.findById(userId);
return userRepository.findById(userId)
.orElseThrow(() -> new NoSuchElementException("userId : " + userId));
}

@Transactional
public User join(User user) {
return userRepository.insert(user);
return userRepository.save(user);
}

public User updateUserInfo(User user) {
return userRepository.update(user);
@Transactional
public User updateUserInfo(Long userId, String nickname, String image, String phoneNumber) {
// TODO - CHECK : update할 컬럼을 명시적으로 나타내주는 게 좋은가? 변경사항이 많은 경우에는?
// updateUserInfo(User user) vs updateUserInfo(Long userId, String nickname, String image, String phoneNumber)
User user = getUser(userId);
user.setNickname(nickname);
user.setImage(image);
user.setPhoneNumber(phoneNumber);
return user;
}

@Transactional
public void quit(Long userId) {
userRepository.updateUserDeleted(userId);
User user = getUser(userId);
user.setDeleted(true);
user.setDeletedAt(LocalDateTime.now());
}

}
39 changes: 37 additions & 2 deletions bbaemin-api/src/main/java/org/bbaemin/user/vo/User.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,42 @@
package org.bbaemin.user.vo;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.time.LocalDateTime;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;

@Column(nullable = false, unique = true, updatable = false)
private String email;

@Column(nullable = false, unique = true)
private String nickname;
private String image;

@Column(nullable = false)
private String password;

@Column(nullable = false)
private String phoneNumber;

@Column(nullable = false, columnDefinition = "boolean default false")
private boolean deleted = false;
private LocalDateTime deletedAt;

@Builder
private User(Long userId, String email, String nickname, String image, String password, String phoneNumber) {
Expand All @@ -25,11 +48,23 @@ private User(Long userId, String email, String nickname, String image, String pa
this.phoneNumber = phoneNumber;
}

public void setUserId(Long userId) {
this.userId = userId;
public void setNickname(String nickname) {
this.nickname = nickname;
}

public void setImage(String image) {
this.image = image;
}

public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}

public void setDeleted(boolean deleted) {
this.deleted = deleted;
}

public void setDeletedAt(LocalDateTime deletedAt) {
this.deletedAt = deletedAt;
}
}
Loading

0 comments on commit dec634b

Please sign in to comment.