2022/07/31
프로젝트 진행중 유니크 제약 조건에 대한 예외처리에 대해 고민하고 알아보다 알아낸 것들을 정리해보자
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_id", unique = true, nullable = false, updatable = false)
private Long id;
@Column(name = "email", length = 40, unique = true, nullable = false)
private String email;
@Column(name = "nick_name", length = 30, unique = true, nullable = false)
private String nickName;
@Column(name = "profile_image_url", length = 150, unique = true, nullable = false)
private String profileImageUrl;
@Transactional
public Member signup(MemberDto memberDto, TemporaryMember temporaryMember) {
Member member = null;
try{
member = memberRepository.save(Member.builder()
.email(memberDto.email())
.nickName(memberDto.nickname())
.field(memberDto.field())
.career(memberDto.career())
.profileImageUrl(temporaryMember.getImageUrl())
.mbti(memberDto.MBTI())
.memberRole(MemberRole.ROLE_MEMBER)
.build());
}catch (SQLIntegrityConstraintViolationException ex){
//처리
}
return member;
}
@Transactional
public Member signup(MemberSaveRequest memberSaveRequest, TemporaryMember temporaryMember) {
Member member = null;
try{
member = memberRepository.save(MemberConverter.toMember(memberSaveRequest, temporaryMember));
}catch (DataIntegrityViolationException ex){
memberUniqueException(ex);
//처리
}
return member;
}
@Transactional
public Member signup(MemberDto memberDto, TemporaryMember temporaryMember) throws DataIntegrityViolationException{
Member member = memberRepository.save(Member.builder()
.email(memberDto.email())
.nickName(memberDto.nickname())
.field(memberDto.field())
.career(memberDto.career())
.profileImageUrl(temporaryMember.getImageUrl())
.mbti(memberDto.MBTI())
.memberRole(MemberRole.ROLE_MEMBER)
.build());
return member;
}
try{
member = memberService.signup(memberDto, temporaryMember);
}catch (DataIntegrityViolationException ex){
throw SqlDuplicatedException.SQL_DUPLICATED_EXCEPTION.get();
}
public class SqlDuplicatedException extends BusinessException {
public SqlDuplicatedException(ErrorCode errorCode, String message) {
super(errorCode, message);
}
public static final Supplier<InputValueException> SQL_DUPLICATED_EXCEPTION = () -> {
throw new InputValueException(ErrorCode.SQL_DUPLICATED_EXCEPTION, "unique 위반");
};
}
이부분은 레이어드 아키텍처 관점에서 생각해 볼 요소라 따로 지우지 않았습니다. 만약 checked Exception 을 서비스 계층이 아닌 컨트롤러 계층까지 전파시킨다면 발생할 수 있는 문제가 많습니다(테스트 독립성이 깨짐, 단일 책임 원칙 위반 등)
@Transactional
public MemberResponse signupMember(MemberSignUpRequest memberSignUpRequest) {
checkDuplicatedEmail(memberSignUpRequest.email());
checkDuplicatedNickName(memberSignUpRequest.nickname());
Member member = memberRepository.save(MemberConverter.toMember(memberSignUpRequest));
return MemberConverter.toMemberResponse(member);
}
private void checkDuplicatedEmail(String email) {
memberRepository.findByEmail(email).ifPresent(member -> {
throw MemberException.emailDuplication(email);
});
}
https://okky.kr/article/444878
좋은 내용 감사합니다 .
내용과 별개로 궁금한게 있습니다. 6번 throws 후 외부 예외 처리 후 custom 처리에 첨부해주신 코드에서 signup메소드의 경우 memberRepository.save(member객체)만 호출하고 있어 원자성은 이미 보장되어 있는것 같은데 @Transactional을 달아둔 이유가 있나요? 컨버터 때문에 달아둔걸까요?
취준하며 공부 중이라 질문이 가벼워도 이해 부탁드립니다:)!