TIL

플러스 주차 개인 과제 TIL 7.

phonebee 2025. 3. 18. 19:47

▶ LEVEL 3

▷ 11. Transaction 심화

● 문제의 요구사항

1. 매니저 등록 요청을 기록하는 로그 테이블을 제작

- DB테이블명: log

2. 매니저 등록과는 별개로 로그 테이블에는 항상 요청 로그가 남아야한다.

- 매니저 등록은 실패할 수 있지만, 로그는 반드시 저장되어야한다.

- 로그 생성 기간은 반드시 필요하다.

- 그 외 로그에 들어가는 내용은 원하는 정보를 자유롭게 넣어야한다.

 

먼저 로그 기록을 받기 위한 로그 엔티티를 제작하도록 하자

@Entity
@Getter
@NoArgsConstructor
@EntityListeners(AuditingEntityListener.class)
@Table(name = "log")
public class Log extends Timestamped {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Long managerId;

    private String actions;
    private String status;
    private String message;

    public Log(Long managerId, String actions, String status, String message) {
        this.managerId = managerId;
        this.actions = actions;
        this.status = status;
        this.message = message;
    }
}

log엔티티에서는 "log"인 이름인 테이블을 생성하며 해당 엔티티에서는 managerId, actions, status, message값을 가진다.

 

그리고 로그 저장을 위해 리포지토리를 제작한다.

public interface LogRepository extends JpaRepository<Log, Long> {
}

그리고 매니저 등록과 로그 저장을 독립적인 트랙잭션으로 처리한 서비스 로직을 제작한다.

@Service
@RequiredArgsConstructor
public class LogService {

    private final LogRepository logRepository;

    @Transactional(Transactional.TxType.REQUIRES_NEW)
    public void saveLog(Long managerId, String actions, String status, String message){
        Log log = new Log(managerId, actions, status, message);
        logRepository.save(log);
    }
}

여기서 @Transactional(propagation = Propagation.REQUIRES_NEW)를 사용하여 별도의 트랙잭션으로 저장되도록 설정했다.

 

이제 로그 서비스를 이용하여 manager가 생성되거나 삭제될 때 로그를 출력할 수 있도록 매니저 서비스에 구현한다.

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ManagerService {

    private final ManagerRepository managerRepository;
    private final UserRepository userRepository;
    private final TodoRepository todoRepository;
    private final LogService logService;

    @Transactional
    public ManagerSaveResponse saveManager(AuthUser authUser, long todoId, ManagerSaveRequest managerSaveRequest) {
        // 일정을 만든 유저
        User user = User.fromAuthUser(authUser);
        Todo todo = todoRepository.findById(todoId)
                .orElseThrow(() -> new InvalidRequestException("Todo not found"));

        if (todo.getUser() == null || !ObjectUtils.nullSafeEquals(user.getId(), todo.getUser().getId())) {
            throw new InvalidRequestException("담당자를 등록하려고 하는 유저가 유효하지 않거나, 일정을 만든 유저가 아닙니다.");
        }

        User managerUser = userRepository.findById(managerSaveRequest.getManagerUserId())
                .orElseThrow(() -> new InvalidRequestException("등록하려고 하는 담당자 유저가 존재하지 않습니다."));

        if (ObjectUtils.nullSafeEquals(user.getId(), managerUser.getId())) {
            throw new InvalidRequestException("일정 작성자는 본인을 담당자로 등록할 수 없습니다.");
        }

        Long managerId = null;
        try{
            Manager newManagerUser = new Manager(managerUser, todo);
            Manager savedManagerUser = managerRepository.save(newManagerUser);
            managerId = savedManagerUser.getId();

            logService.saveLog(managerId, "MANAGER_REGISTER", "SUCCESS", "매니저 등록 성공");

            return new ManagerSaveResponse(
                    savedManagerUser.getId(),
                    new UserResponse(managerUser.getId(), managerUser.getEmail())
            );
        }catch (Exception e){
            logService.saveLog(managerId, "MANAGER_REGISTER", "FAILURE", e.getMessage());
            throw e;
        }
    }

    public List<ManagerResponse> getManagers(long todoId) {
        Todo todo = todoRepository.findById(todoId)
                .orElseThrow(() -> new InvalidRequestException("Todo not found"));

        List<Manager> managerList = managerRepository.findByTodoIdWithUser(todo.getId());

        List<ManagerResponse> dtoList = new ArrayList<>();
        for (Manager manager : managerList) {
            User user = manager.getUser();
            dtoList.add(new ManagerResponse(
                    manager.getId(),
                    new UserResponse(user.getId(), user.getEmail())
            ));
        }
        return dtoList;
    }

    @Transactional
    public void deleteManager(AuthUser authUser, long todoId, long managerId) {
        User user = User.fromAuthUser(authUser);

        Todo todo = todoRepository.findById(todoId)
                .orElseThrow(() -> new InvalidRequestException("Todo not found"));

        if (todo.getUser() == null || !ObjectUtils.nullSafeEquals(user.getId(), todo.getUser().getId())) {
            throw new InvalidRequestException("해당 일정을 만든 유저가 유효하지 않습니다.");
        }

        Manager manager = managerRepository.findById(managerId)
                .orElseThrow(() -> new InvalidRequestException("Manager not found"));

        if (!ObjectUtils.nullSafeEquals(todo.getId(), manager.getTodo().getId())) {
            throw new InvalidRequestException("해당 일정에 등록된 담당자가 아닙니다.");
        }

        try{
            managerRepository.delete(manager);

            logService.saveLog(managerId, "MANAGER_DELETE", "SUCCESS", "매니저 삭제 성공");
        }catch (Exception e){
            logService.saveLog(managerId, "MANAGER_DELETE", "FAILURE", e.getMessage());
            throw e;
        }
    }
}

이렇게 제작했는데 여기서 로그 서비스는 매니저를 등록하거나 삭제할 때 로그를 출력하며 실패했을 때에도 오류 메세지와 함께 출력되게 된다.