@Valid를 이용한 유효성 검증
유효성 검사
ApiDTO
package com.apiservice.model;
import lombok.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import java.time.LocalDateTime;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ApiDTO {
public interface insertGroup{}
public interface updateGroup{}
private Integer memberNumber; //사용자 번호
@NotEmpty(message = "아이디를 입력해주세요" , groups = insertGroup.class)
@Email(message = "ID 는 Email 형식으로 입력해주세요" , groups = updateGroup.class)
private String memberId; // 사용자 ID
@NotEmpty(message = "비밀번호를 입력해주세요", groups = insertGroup.class)
private String memberPw; // 사용자 PW
@NotEmpty(message = "이름을 입력해주세요" , groups = insertGroup.class)
private String memberName; // 사용자 이름
@Pattern(regexp = "(\\\\d{2,3})-?(\\\\d{3,4})-?(\\\\d{4})$",message = "휴대폰 번호를 다시 확인해주세요 ",groups = {insertGroup.class,updateGroup.class})
private String memberPhone; // 사용자 휴대폰 번호
@Pattern(regexp ="N|Y",message = "광고 수신은 Y 거부는 N 를 입력해주세요 ",groups = {insertGroup.class,updateGroup.class})
private String advert; // 광고 수신
private LocalDateTime regDate;
private LocalDateTime updateDate;
}
새롭게 추가된 코드를 위에서부터 천천히 알아보도록 하겠습니다
- interface로 선언된 Group { } 들은 insert와 update 각각 상황에 적용해야 하는 상황이 다르기 때문에 따로 만들어 주었습니다
- @NotEmapty는 해당 값이 null이 아니고 , 빈 스트링(””)아닌지 검증 (” “은 가능함) 합니다
- @Email는 입력 값의 형식이 이메일 양식인지 검증합니다
- @Pattern는 regxp에 선언된 패턴과 일치하는지 검증합니다
insertGroup{ } | updateGroup{ } |
insert 할 때는 @NotEmpty로 설정된 값들이 반듯이 입력되어야 합니다 | update 할 때는 모든 사항을 수정하지 않는 경우도 있기 때문에 @NotEmpty를 빼주고 입력 형식만을 적용시켜준다 |
이제 Service 에서 각 상황에서 디테일을 추가해보자
입력 상황 register
@Override
public void register(ApiDTO apiDTO) {
log.info("service insert");
log.info(apiDTO);
//광고 수신 값이 null 이면 N 이 기본값으로 들어가게
String advert = apiDTO.getAdvert() == null ? "N" :apiDTO.getAdvert();
apiDTO.setAdvert(advert);
//DB 조회 (중복 ID 가 있는지 검사)
ApiVO getId = apiRepository.getId(apiDTO);
if (getId == null){
}else {
log.info("ID 중복");
throw new CustomException(ErrorCode.BAD_ARTICLE);
}
apiRepository.insert(apiDTO);
}
- 사용자가 advert에 값을 입력하지 않으면 기본적으로 ‘ N ’ 값이 들어가게 처리해주었습니다 <광고는 귀찮으니까요 >
- inset 할때 ID가 중복이 되면 안 되니 그걸 체크해 주고 있습니다
- ID 가 중복일 경우에는 커스텀으로 만든 에러를 보여주고 있다
여기서 새롭게 추가된 repository와 ErrorCode 보기
ApiRepository
package com.apiservice.repository;
import com.apiservice.model.ApiDTO;
import com.apiservice.model.ApiVO;
import com.apiservice.model.ListDTO;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ApiRepository {
void insert(ApiDTO apiDTO);
void update(ApiDTO apiDTO);
void delete(Integer memberNumber);
List<ApiVO>selectList(ListDTO listDTO);
int getTotal(ListDTO listDTO);
ApiVO getPk(ApiDTO apiDTO); //PK 체크
ApiVO getId(ApiDTO apiDTO); //ID 체크
}
- getPK는 update 로직에서 pk값이 있는지를 확인해주기 위한
- getId는 insert 할 때 중복된 ID 가 있는지 확인해주기 위한
ApiRepository. xml
<select id="getPk" resultType="com.apiservice.model.ApiVO">
select * from api
where member_number = #{memberNumber}
</select>
<select id="getId" resultType="com.apiservice.model.ApiVO">
select * from api
where member_id = #{memberId}
</select>
- SQL은 단순하게 PK인 memberNumber 그리고 id 인 memberId를 확인합니다
ErrorCode
package com.apiservice.model.Error;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum ErrorCode {
BAD_REQUEST(400 ,"Bad Request"),
BAD_ENTITY(401,"Bad ENTITY"),
BAD_ARTICLE(404,"article value"),
NOT_INFO(405,"없는 정보입니다"),
SYS_ERR(500,"시스템 에러 입니다.");
private final int status;
private final String message;
// Enum 클래스로 사용할 에러들을 적어준다.
// status 값과 error message 만 프론트에 넘겨줄 예정으로 두 개만 작성하였다.
// 재사용성을 생각해서 범위를 너무 좁게설정하지 말고 넓은 범위로 설정해보자
}
- 중복이거나 없는 정보를 처리할 BAD_ARTICLE , NOT_INFO
- 잡지 못한 에러 상황일 때는 시스템 에러를 띄우기 위해 SYS_ERR 만들었습니다
GlobalExceptionHandler
package com.apiservice.controller.Handler;
import com.apiservice.model.Error.ErrorDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.jdbc.support.MetaDataAccessException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.io.FileNotFoundException;
import static com.apiservice.model.Error.ErrorCode.*;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler({ BindException.class})
protected ResponseEntity handleBindException(BindException ex) {
FieldError fieldError = ex.getFieldError();
String message = fieldError.getDefaultMessage();
String param = fieldError.getField();
message = message + "(" + param + ")";
log.debug(" message : {}", message);
log.debug(" param : {}", param);
return new ResponseEntity(new ErrorDTO( BAD_REQUEST.getStatus(), message + " " + BAD_REQUEST.getMessage()+ ":" + param), HttpStatus.valueOf(BAD_REQUEST.getStatus()));
}
@ExceptionHandler({ Exception.class })
protected ResponseEntity handleServerException(Exception ex) {
ex.printStackTrace();
return new ResponseEntity(new ErrorDTO(SYS_ERR.getStatus(), SYS_ERR.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler({CustomException.class})
protected ResponseEntity handleCustomException(CustomException ex){
ex.getMessage();
return new ResponseEntity(new ErrorDTO(ex.getErrorCode().getStatus() , ex.getErrorCode().getMessage()),HttpStatus.valueOf(ex.getErrorCode().getStatus()));
}
}
- handleServerException에서는 가장 상위인 Exception으로 처리해서 특정 상황이 아닌 상황에는 SYS_ERR(500, "시스템 에러입니다.") 에러가 발생하게 했습니다
- handleCustomException는 코드를 짜며 특정한 상황에 각 상황에 맞는 에러 코드를 리턴하고 있습니다
이렇게 insert 하는 서비스 코드에 디테일을 조금 추가해보았습니다
수정 update
@Override
public void update(ApiDTO apiDTO) {
log.info("update service");
log.info(apiDTO);
//DB 조회 (PK 가 있는지 검사)
ApiVO getPk = apiRepository.getPk(apiDTO);
if(getPk == null){
log.info("없는 PK 입니다");
throw new CustomException(ErrorCode.NOT_INFO);
}
apiRepository.update(apiDTO);
}
- getCheck를 통해 수정할 데이터가 있는지 확인해주고 없다면 CustomException 이 발생하게 처리했습니다
❗❗우선 서비스 로직은 이렇게 마무리했습니다 로직을 만들면서 swagger-ui로 계속 테스트를 진행하면서 코드를 뿌슝빠슝 했습니다 하지만 분량상 이곳에서는 생략했습니다
😱문제 발생
- swagger-ui에서 update를 할 때 NumberFormatException 이 발생함…
- update 할때 수정하려고 하는 항목만 입력했을 때 입력 안 한 값들이 null로 DB에 들어가는 엄청난 상태….
swagger-ui에서 update를 할 때 NumberFormatException 이 발생
swagger-ui 에서 update를 할때 PK 값인 memberNumber을 확인해서 update 를 진행한다 그래서 controller 에서 PathVariable 로 memberNumber 을 받아서 처리하고 있는데 NumberFormatException 이 발생하고 있다 ….
@ApiOperation("api post update")
@PostMapping("/update/{memberNumber}")
public ResponseEntity update(@PathVariable("memberNumber") Integer memberNumber , @Validated(ApiDTO.updateGroup.class) ApiDTO apiDTO){
log.info("-----------------------");
log.info("update");
log.info("------------------");
apiService.update(apiDTO);
return new ResponseEntity(HttpStatus.OK);
}
에러
- 정확한 원인은 모르겠지만….. Integer 인 memberNumber이 들어올 때 String로 들어오는것 같다…. 그래서 받는 값을 Integer이 아닌 String 로 수정해서 문제는 해결했지는 왜? 라는 질문에는 답하지 못했다… String 로 들어오는 거 까지는 확인헀지만 왜? 들어오는지는 모르겠다… 아시면 댓글 달아주세요…
코드 수정
@ApiOperation("api post update")
@PostMapping("/update/{memberNumber}")
public ResponseEntity update(@PathVariable("memberNumber") String memberNumber , @Validated(ApiDTO.updateGroup.class) ApiDTO apiDTO){
log.info("-----------------------");
log.info("update");
log.info("------------------");
apiService.update(apiDTO);
return new ResponseEntity(HttpStatus.OK);
}
update 할 때 수정하려고 하는 항목만 입력했을 때 입력 안 한 값들이 null로 DB에 들어감
원래 update sql 문
<update id="update">
update api
set update_date = now()
, member_id = #{memberId}
, member_pw = #{memberPw}
, member_name = #{memberName}
, member_phone = #{memberPhone}
, advert = #{advert}
where member_number = #{memberNumber}
</update>
현재 DB 데이터
- 여기서 이제 swagger-ui로 id 만 수정하게 되면
DB 결과가…처참…😱
아니… 이게 무슨…. 하…. 수정할 때 모든 데이터를 입력하는 것이 아니면 기존의 데이터가 날아가버리는 상황이 발생했다….
이걸 수정하기 위해 고민한 것은 각 칼럼에 새로운 값이 안들어오면 기존의 값이 유지되게 해야하는데…… where 조건을 여러개를 주어야하는건가..? 컬럼에 And 조건 같은걸 걸어야 하나..? 여러 방법을 고민해보고 시도했지만 결과는 전부 꽝이었다…. 그러던 중 set 조건에 if문을 사용할 수 있다는 걸 알게 되었다 그래서 바로 시도해보았습니다
수정한 update sql문
<update id="update">
update api
set update_date = now()
<if test=" memberId != null and memberId != '' ">
, member_id = #{memberId}
</if>
<if test="memberPw != null and memberPw != '' ">
, member_pw = #{memberPw}
</if>
<if test="memberName != null and memberName != '' ">
, member_name = #{memberName}
</if>
<if test="memberPhone != null and memberPhone != '' ">
, member_phone = #{memberPhone}
</if>
<if test="advert != null and advert != '' ">
, advert = #{advert}
</if>
where member_number = #{memberNumber}
</update>
- 테스트를 위해 기존 DB에 null 들어간 데이터를 채워주고 다시 시도했습니다
swagger-ui로 id 만 수정하게 되면
DB 결과
크….. 바로 이거지….. 그래 이거야 ㅠ 이런 방법이 있었네요… 역시 아직 공부할게 너무너무 많다는 걸 느끼게 되는 것 같습니다
이렇게 기존 코드에서 유효성 검사와 부족한 부분들을 보완하는 리펙토링을 진행해보았습니다
'예제를 만들자 뚱땅뚱땅' 카테고리의 다른 글
API 만들기 연습 #8 추가 설계 (0) | 2022.09.01 |
---|---|
API 만들기 연습 #7 검색 (0) | 2022.08.31 |
API 만들기 연습 #5 CustomException (0) | 2022.08.29 |
API 만들기 연습 #4 페이징 (0) | 2022.08.26 |
API 만들기 연습 #3 update , delete (0) | 2022.08.25 |