ManagerRegistLogService
@Transactional(propagation = Propagation.REQUIRES_NEW)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
public void ManagerRegistLog(ManagerRegistLogEvent managerRegistLogEvent) {
ManagerRegistLog managerRegistLog = ManagerRegistLog.createManagerRegistLog(
managerRegistLogEvent.getTodoTitle(),
managerRegistLogEvent.getUserEmail(),
managerRegistLogEvent.getManagerEmail(),
managerRegistLogEvent.getManagerRegistLogEnum()
);
managerRegistLogRepository.save(managerRegistLog);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
트랜잭션 전파 옵션 중 기존 트랜잭션이 있으면 새로운 트랜잭션을 만드는 REQUIRES_NEW 옵션을 선택
→ 매니저 등록과는 별개로 로그가 찍히는 것을 계획
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
TransactionalEventListener 의 phase 옵션에서 AFTER_COMPLETION 을 선택해 지정한 매니저 등록 트랜잭션이 commit 을 성공하든, 실패하든 ManagerRegistLog 메서드가 실행되도록 설정
+ AFTER_COMPLETION, AFTER_COMMIT, AFTER_ROLLBACK, BEFORE_COMMIT 등의 옵션이 있다.
→ 매니저 등록 트랜잭션이 commit 성공할 때와 실패할 때의 로직이 기존에는 일부 다른 부분이 있었지만, 동일한 로직으로 구현이 가능하게끔 커스터마이징했다.
ManagerRegistLogEvent
@Getter
public class ManagerRegistLogEvent {
private final String todoTitle;
private final String userEmail;
private final String managerEmail;
private final ManagerRegistLogEnum managerRegistLogEnum;
public ManagerRegistLogEvent(String todoTitle, String userEmail, String managerEmail, ManagerRegistLogEnum managerRegistLogEnum) {
this.todoTitle = todoTitle != null? todoTitle : "Unknown Title";
this.userEmail = userEmail != null? userEmail : "Unknown Email";
this.managerEmail = managerEmail != null? managerEmail : "Unknown Email";
this.managerRegistLogEnum = managerRegistLogEnum;
}
}
commit 성공할 때와 다르게 실패할 때에는 데이터들의 조회를 실패해 예외처리가 발생해서 commit 실패를 하는 것이기 때문에 ManagerRegistLogEvent 를 만들어서 데이터가 null 일 시에 "Unknown Title" 과 같이 직접적으로 문자열을 넣어주었다.
→ Log 를 저장할 시에 하나라도 null 이 있으면 데이터베이스에 저장이 안되는 에러가 발생했었다.
→ 위처럼 삼항연산자를 이용해 null 값을 없애주면 commit 성공, 실패시 구현되는 로직을 동일하게 가져갈 수 있었다.
ManagerService
@Transactional
public ManagerSaveResponse saveManager(AuthUser authUser, long todoId, ManagerSaveRequest managerSaveRequest) {
// 일정을 만든 유저
User user = User.fromAuthUser(authUser);
Todo todo = todoRepository.findById(todoId)
.orElseThrow(() -> {
applicationEventPublisher.publishEvent(new ManagerRegistLogEvent(null, null, null, REGIST_LOG_FAIL__NOT_FOUND_TODO));
return new InvalidRequestException("Todo not found");
});
if (todo.getUser() == null || !ObjectUtils.nullSafeEquals(user.getId(), todo.getUser().getId())) {
applicationEventPublisher.publishEvent(new ManagerRegistLogEvent(todo.getTitle(), todo.getUser().getEmail(), null, REGIST_LOG_FAIL_DUBLICATED_USER));
throw new InvalidRequestException("담당자를 등록하려고 하는 유저가 유효하지 않거나, 일정을 만든 유저가 아닙니다.");
}
User managerUser = userRepository.findById(managerSaveRequest.getManagerUserId())
.orElseThrow(() -> {
applicationEventPublisher.publishEvent(new ManagerRegistLogEvent(todo.getTitle(), todo.getUser().getEmail(), null, REGIST_LOG_FAIL_NOT_FOUND_MANAGER));
return new InvalidRequestException("등록하려고 하는 담당자 유저가 존재하지 않습니다.");
});
if (ObjectUtils.nullSafeEquals(user.getId(), managerUser.getId())) {
applicationEventPublisher.publishEvent(new ManagerRegistLogEvent(todo.getTitle(), todo.getUser().getEmail(), managerUser.getEmail(), REGIST_LOG_FAIL_DUBLICATED_USER));
throw new InvalidRequestException("일정 작성자는 본인을 담당자로 등록할 수 없습니다.");
}
Manager newManagerUser = new Manager(managerUser, todo);
Manager savedManagerUser = managerRepository.save(newManagerUser);
// 매니저 등록 성공시 TransactionalEventListener 을 이용해 로그 저장
applicationEventPublisher.publishEvent(new ManagerRegistLogEvent(todo.getTitle(), todo.getUser().getEmail(), managerUser.getEmail(), REGIST_LOG_SUCCESS));
return new ManagerSaveResponse(
savedManagerUser.getId(),
new UserResponse(managerUser.getId(), managerUser.getEmail(), managerUser.getNickname())
);
}
매니저가 등록될 때 사용되는 saveManager 메서드는 기존에 각 각의 상황에 따라 예외처리가 발생했다.
나는 그 각 각의 상황에 맞게 커스터마이징해서 Log 데이터베이스에 저장시키고 싶었기 때문에 해당 예외처리가 발생하기 전에 이벤트리스너 로직이 작동되도록 상황에 맞는 상수를 넣어 각 각 다르게 넣어줬다.
* LazyInitializationException 발생 문제
예외처리가 되면 세션이 종료된다.
하지만 나는 이후에 트랜잭션을 하나 더 열어서 예외처리된 문구를 넣고싶었기 때문에, 세션이 종료되면 지연로딩 처리된 데이터를 미리 가져오지 않으면 데이터를 가져올 수가 없었다.
원래 ManagerRegistLogEvent 에서 Todo, Manager 를 가져왔었는데, 이렇게 되면 지연로딩 설정이 되어있기 때문에
LazyInitializationException 이 발생한다.
그래서 내가 찍고 싶었던 로그에서 정확히 필요한 Title, UserName 등을 String 으로 다이렉트로 받아오게했고,
해당 연관 엔터티를 즉시로딩으로 수정하기보다 아예 연관되지 않게 지워서 해결했다.
ManagerRegistLogEnum
@Getter
@AllArgsConstructor
public enum ManagerRegistLogEnum {
REGIST_LOG_SUCCESS("등록 성공"),
REGIST_LOG_FAIL_DUBLICATED_USER("등록 실패 - 본인을 담당자로 등록 요청"),
REGIST_LOG_FAIL_NOT_FOUND_MANAGER("등록 실패 - 등록하려는 유저 조회 실패"),
REGIST_LOG_FAIL__NOT_FOUND_USER("등록 실패 - 로그인 유저 조회 실패"),
REGIST_LOG_FAIL__NOT_FOUND_TODO("등록 실패 - 할 일 조회 실패");
private final String message;
}
이처럼 이후에 로그 문구라든지 변경이 필요할 때 간단하게 할 수 있도록 enum 으로 만들어서 관리했다.
// 매니저 등록 성공시 TransactionalEventListener 을 이용해 로그 저장
applicationEventPublisher.publishEvent(new ManagerRegistLogEvent(todo.getTitle(), todo.getUser().getEmail(), managerUser.getEmail(), REGIST_LOG_SUCCESS));
매니저 등록에 성공하면 역시 트랜잭션 이벤트 리스너가 작동하도록 설정했다.
'컴퓨터 프로그래밍 > Spring' 카테고리의 다른 글
[Spring] 페이지네이션 정리 코드 (0) | 2024.10.23 |
---|---|
[Spring] Discord 알림 구현 (0) | 2024.10.16 |
[Spring] Projection 및 예시코드 설명 (0) | 2024.10.07 |
[Spring] Spring Security (0) | 2024.10.04 |
[Spring] QueryDSL (1) | 2024.10.03 |