관심쟁이 영호
[Spring Boot] Form 객체를 분리하여 검증 로직 다르게 적용 본문
이번 기록은 Form 객체를 분리를 통해서 검증 로직을 서로 다르게 적용하는 것이다.
이 말이 무슨 뜻인가!?
다음과 같은 상황으로 이해해보자.
"회원 가입 시에는 id의 길이가 최대 10, 회원 정보 수정에는 id의 길이가 최대 15로 수정이 가능하게 해 주세요."
위와 같은 상황에 Form 객체를 분리하여 검증 로직을 다르게 적용한다는 말이다!
목차
- Form 객체 분리란?
- Form 객체 분리하기
- 분리에 따른 후 처리
- 번외 - 겪은 에러
Form 객체 분리란?
Form 객체로 분리한다는 말이 무슨 뜻일까?
코드로 살펴보자.
User.java
@Data
@Entity(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
Long sequenceId;
@NotNull
@NotBlank
@Column(length = 25, nullable = false)
String userId;
@NotNull
@NotBlank
@Column(nullable = false)
String password;
};
SignupController.java
@PostMapping("/signup")
public String signupForm(@Validated @ModelAttribute User user, BindingResult bindingResult, RedirectAttributes redirectAttributes){
log.info("userid ={}", user.getUserId());
if(bindingResult.hasErrors()){
log.info("errors ={}", bindingResult);
return "/signup";
}
signUpService.signup(user);
return "redirect:/";
}
이전에는 Post Mapping에서 user라는 객체를 사용한 것을 볼 수 있다.
이제는 user를 사용하지 않고 user를 가지고 있는
User sign Up Form, User Modify Form 객체로 나누어
따로따로 어노테이션을 설정할 것이다.
Form 객체 분리하기
두 가지의 객체를 생성해보자!
먼저 검증 로직은 처음에 있던
"회원 가입 시에는 id의 길이가 10, 회원 정보 수정에는 id의 길이 15로 수정이 가능하게 해 주세요."
을 적용해보자!
UserSignUpForm.java
@Data
public class UserModifyForm {
String beforeId;
String beforePassword;
@NotNull
@NotBlank
@Size(max = 15)
String userId;
@NotNull
@NotBlank
String password;
};
UserModifyForm.java
@Data
public class UserSignUpForm {
@NotNull
@NotBlank
@Size(max=10)
String userId;
@NotNull
@NotBlank
String password;
};
코드 설명 :
User객체를 UserModifyForm과 UserSignUpForm객체로 나눈 이유는, 각각 다른 검증을 해주기 위해서이다.
Group을 사용하여 묶을 수도 있지만 그거 또한 한계가 있어서 차라리 관리하기 쉽게 두 가지의 폼으로 나누어 관리하자!
먼저 JPA에 사용될 어노테이션은 모두 제거해주었다.
그리고 회원 가입과 회원 수정에 각각 맞는 검증 어노테이션을 추가했다. (max=10, max=15)
분리에 따른 후처리
분리가 되었으니 제각각 코드를 바꿔주어야 할 것이다!
먼저 어떤 코드를 바꿔야 할지 생각해보자.
- Controller에 넘어오는 @ModelAttribute 부분의 객체를 바꿔주어야 한다.
- Service 부분에서 Form객체로 되어있는 값을 User 객체로 변환해주어야 한다.
이렇게 두 가지의 작업이 추가되었을 것이다.
컨트롤러와 서비스를 보자!
SignUpController
@Controller
@Slf4j
@RequiredArgsConstructor
public class SignUpContoller {
@Autowired
private final SignUpService signUpService;
@GetMapping("/signup")
public String signup(Model model){
model.addAttribute("user", new User());
return "/signup";
}
@PostMapping("/signup")
public String signupForm(@Validated @ModelAttribute UserSignUpForm user, BindingResult bindingResult, RedirectAttributes redirectAttributes){
log.info("userid ={}", user.getUserId());
if(bindingResult.hasErrors()){
log.info("errors ={}", bindingResult);
return "/signup";
}
User user2 = new User();
user2.setUserId(user.getUserId());
user2.setPassword(user.getPassword());
signUpService.signup(user2);
return "redirect:/";
}
};
ModifyUserController
@Controller
@RequiredArgsConstructor
@Slf4j
public class ModifyUserController {
private final ModifyUserService modifyUserService;
@GetMapping("/modify")
public String ModifyUser(){return "/modify";}
@PostMapping("/modify")
public String ModifyUser(@Validated @ModelAttribute UserModifyForm userModifyForm, BindingResult bindingResult){
if(bindingResult.hasErrors()){
return "/modify";
}
modifyUserService.modifyUser(userModifyForm);
return "redirect:/";
}
};
컨트롤러 코드 설명:
- signup에서는 UserSignUpForm 객체가 넘어온다.
- modify에서는 UserModifyForm 객체가 넘어온다.
다른 점이 두 가지가 있는데 signup은 controller에서 user로 변환하였고, modify는 서비스에서 user로 변환을 해주었다!
당연히 서비스에서 하는 것이 올바르겠지!?
그럼 각각의 서비스를 보자!
SignUpService
@Service
@RequiredArgsConstructor
public class SignUpService {
@Autowired
private final UserRepository userRepository;
public void signup(User user){
userRepository.save(user);
}
};
ModifyUserService
@Service
@RequiredArgsConstructor
@Slf4j
public class ModifyUserService {
private final UserRepository userRepository;
public void modifyUser(UserModifyForm userModifyForm){
User user = userRepository.finduser(userModifyForm.getBeforeId(), userModifyForm.getBeforePassword());
user.setUserId(userModifyForm.getUserId());
user.setPassword(userModifyForm.getPassword());
userRepository.save(user);
}
};
이제 완벽히 수행되는 것을 볼 수 있었다.
번외 - 겪은 문제
1. JPA와 연동하면서 JPA Repository에 구현되어 있는 내용을 보니까 Long id(PK)로 getOne, findById가 수행되는 것을 볼 수 있었다.
사용자들은 분명 자신의 id와 password를 입력할 건데, "JpaRepository"의 구현체에는 sequence로 등록된 id만 인자로 받는다.. 그럼 어떻게 조회를 해야 하나 ㅠ
일단은 아래와 같이 인터페이스를 확장했다.
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select m from user m where m.userId = ?1")
User finduser(String id, String password);
};
해결 :
Spring Data JPA를 사용하면 정해진 규칙대로 인터페이스에 등록하면 간단한 쿼리문은 자동 구현된다!
해당 구현 메서드명 규칙은 아래 글을 참고하자!
https://minkwon4.tistory.com/130
[JPA] Spring Data JPA (2) - 메소드 네임 쿼리, @Query
메소드 네임 쿼리 JPA는 기본적으로 JpaRepository<엔티티 타입, 식별자(PK)타입>을 상속 받음으로써 기본적인 CRUD를 할 수 있는 쿼리를 제공한다. 또한 이것과 별게로 추가적으로 규칙에 의한 메소드
minkwon4.tistory.com
그래서 변경된 코드!
public interface UserRepository extends JpaRepository<User, Long> {
//@Query("select m from user m where m.userId = ?1")
//User finduser(String id, String password);
User findByUserId(String id);
};
public void modifyUser(UserModifyForm userModifyForm){
//User user = userRepository.finduser(userModifyForm.getBeforeId(), userModifyForm.getBeforePassword());
User user = userRepository.findByUserId(userModifyForm.getBeforeId());
user.setUserId(userModifyForm.getUserId());
user.setPassword(userModifyForm.getPassword());
userRepository.save(user);
}