Project Trouble Shooting 4 : Custom Error Message Isn't Working Properly, missing @ExceptionHandler
#Problem : MEMBER_ALREADY_REGISTERED isn't working
: MEMBER_ALREADY_REGISTERED
should've worked, but, INTERNER_SERVER_ERROR
occured. Both are methods in GlobalExceptionRestAdvice Class and Custom Error Message that i set for this specific project. Even though both are custom error message, each of them has to play a different role, the problem is that I can't call the message where i want to use it.
Even though I added MemberAlreadyRegisteredException
class, INTERNAL_SERVER_ERROR
has occured, which means the customer error message is working but not properly. Eventually, it turned out to be a very simple problem,,, that is called a human error. I will make a document to remind myself of this.
GlobalExceptionRestAdvice Source Code (modified)
import com.FC.SharedOfficePlatform.global.util.ResponseDTO;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.NestedExceptionUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionRestAdvice {
@ExceptionHandler(ApplicationException.class)
public ResponseEntity<ResponseDTO<Void>> applicationException(ApplicationException e) {
log.error(e.getMessage(), e);
return ResponseEntity
.status(e.getErrorCode().getHttpStatus())
.body(ResponseDTO.error(e.getErrorCode()));
}
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ResponseDTO<Void>> httpMessageNotReadableException(
HttpMessageNotReadableException e) {
log.error(e.getMessage(), e);
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(ResponseDTO.errorWithMessage(HttpStatus.BAD_REQUEST, e.getMessage()));
}
@ExceptionHandler(BindException.class)
public ResponseDTO<Void> bindException(BindException e) {
log.warn("[bindException] Message = {}",
NestedExceptionUtils.getMostSpecificCause(e).getMessage());
BindingResult bindingResult = e.getBindingResult();
List<FieldError> fieldErrors = getSortedFieldErrors(bindingResult);
String response = fieldErrors.stream()
.map(fieldError -> String.format("%s (%s=%s)",
fieldError.getDefaultMessage(),
fieldError.getField(),
fieldError.getRejectedValue()
)
).collect(Collectors.joining(", "));
return ResponseDTO.errorWithMessage(HttpStatus.BAD_REQUEST, response);
}
private List<FieldError> getSortedFieldErrors(BindingResult bindingResult) {
List<String> declaredFields = Arrays.stream(
Objects.requireNonNull(bindingResult.getTarget()).getClass().getDeclaredFields())
.map(Field::getName)
.toList();
return bindingResult.getFieldErrors().stream()
.filter(fieldError -> declaredFields.contains(fieldError.getField()))
.sorted(Comparator.comparingInt(fe -> declaredFields.indexOf(fe.getField())))
.collect(Collectors.toList());
}
@ExceptionHandler(DataAccessException.class)
public ResponseEntity<ResponseDTO<Void>> dbException(DataAccessException e) {
log.error(e.getMessage(), e);
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ResponseDTO.errorWithMessage(HttpStatus.INTERNAL_SERVER_ERROR, "디비 에러!"));
}
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<ResponseDTO<Void>> serverException(RuntimeException e) {
log.error(e.getMessage(), e);
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ResponseDTO.errorWithMessage(HttpStatus.INTERNAL_SERVER_ERROR, "서버 에러!"));
}
}
#Leaving Trace
- This
signup
API uses email as unique identifier. So, when it's requested by same email,Member_Already_Registered
should've occurred, but it throws"INTERNAL_SERVER_ERROR"("errorMessage" : "서버 에러!")
Why did this happen?????????????????????
: At first, I thought I developed SignUp API in a wrong way, so I tried to fix it.
but,, as you see below, validateRegisteredEmail method throws MemberAlreadyRegisteredException()...
@Service
@RequiredArgsConstructor
//@Transactional(readOnly = true)
@Slf4j
public class MemberService {
private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
// signup
public SignUpMemberResponse registerNewMember(SignUpMemberRequest signUpMemberRequest) {
log.info("{} ::: {}", getClass().getSimpleName(), "registerNewMember");
// 중복 이메일 검증
validateRegisteredEmail(signUpMemberRequest.email());
// 패스워드 인코딩
Member member = signUpMemberRequest.toEntity(signUpMemberRequest.password());
member.setEncodedPassword(passwordEncoder.encode(signUpMemberRequest.password()));
// 패스워드 인코딩한 유저 등록 (save())
Member savedUser = memberRepository.save(member);
SignUpMemberResponse signUpMemberResponse = SignUpMemberResponse.from(savedUser);
return signUpMemberResponse;
}
// 중복 이메일 검증 메서드 (이미 등록된 유저 확인)
private void validateRegisteredEmail(String email) {
log.info("{} ::: {}", getClass().getSimpleName(), "validateRegisteredEmail");
if (memberRepository.existsByEmail(email)) {
throw new MemberAlreadyRegisteredException();
}
}
}
With @Transactional, Failed again.
: I am ashamed of this, but I have to admit that I thought about @Transactional
, yes, I also thought it's not related to @Transactional
..but i did.
b.Transaction Management: Since you're using Spring Data JPA, transactions are managed automatically by default. However, it's worth confirming that transactions are properly managed during the signup process. Ensure that the@Transactional
annotation (commented out in your code) is applied appropriately. If it's supposed to be read-only, ensure it's not inadvertently preventing write operations.Transactional Behavior: Depending on your requirements, you might need to adjust the transactional behavior. For example, if a signup process involves multiple database operations that need to be atomic (all or nothing), you should ensure that the entire process is wrapped within a single transaction.
#Solution : add @ExceptionHandler(applicationException.class)
In conclusion, this was a very simple solution that I shouldn't have made mistakes,. Anyway, thanks to this, I could look into this GlobalExceptionRestAdvice (GlobalExceptionhandler...........)
When facing errors, we need to try to think about it first, Solution isn't always complicate, we could make a human error too.....................Try simple approach first.
Fixed With @ExceptionHandler anntation
"errorMessage" : "이미 가입된 회원입니다." (MemberAlreadyRegistered Exception)
#In the end, I felt that
: Taking your enough time rather than just trying.
As a result, I've tried to fix signUp API
. But, nothing was successful, while I didn't feel it's a problem, all approaches that i did had no choice but fail....... like it's kind of forcing my foots into small shoes.
I've found myself struggling to figure out what the problem is,,,,,,I think, when facing specific problem, we'd better take away from keyboard, and then delve into it with enough time.................Just trying can't guarantee your success.....................................Even though it can be seen as the fastest way, taking your enough time for you to find out "reason" can be a shortcut.............................................
: Take a test with various scenarios (When API dev is done)
When taking a test for your developed API, I need to request for the data of various scenarios, promoting your to prevent missing specific exception conditions.
: Copying Code can be a good sequence if you understand it well. Read the code.
Once you understand how the code works, copying code can be a good option. Don't cross the line between right and wrong. What's more, Copying code can prevent human errors. Depending on your usage, it can be a good option that can give you effectiveness.