Back-end/Spring

[SpringBoot] 10주차 스터디(AOP, 트랜잭션)

poppy 2021. 10. 23. 12:05
반응형

1. AOP (Aspect Oriented Programming)

- AOP는 Aspect Oriented Programming의 약자로 관점 지향 프로그래밍이라고 불린다. 관점 지향은 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화하겠다는 것이다. 따라서 AOP를 통해 부가 기능을 모듈화할 수 있다. AOP를 사용하면 핵심 기능과 분리하여 부가 기능 중심으로 설계, 구현이 가능해진다.

- 스프링에서 부가기능은 "어드바이스", 부가기능을 적용할 위치를 "포인트컷" 이라고 한다.

 

다음은 AOP를 통해 부가기능을 모듈화한 코드이다. 여기서 부가 기능은 사용자의 API 수행 시간을 측정하는 것이다. 부가 기능은 excute 메서드에 구현되어있다. @Around 로 포인트컷을 지정하면 된다.

@Component // 스프링 IoC 에 빈으로 등록
@Aspect
public class UserTimeAop {
    private final UserTimeRepository userTimeRepository;

    public UserTimeAop(UserTimeRepository userTimeRepository) {
        this.userTimeRepository = userTimeRepository;
    }

    @Around("execution(public * com.sparta.springcore.controller..*(..))") // 포인트컷(부가기능이 수행될 위치)
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        // 측정 시작 시간
        long startTime = System.currentTimeMillis();

        try {
            // 핵심기능 수행
            Object output = joinPoint.proceed();
            return output;
        } finally {
            // 측정 종료 시간
            long endTime = System.currentTimeMillis();
            // 수행시간 = 종료 시간 - 시작 시간
            long runTime = endTime - startTime;
            // 로그인 회원이 없는 경우, 수행시간 기록하지 않음
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
            if (auth != null && auth.getPrincipal().getClass() == UserDetailsImpl.class) {
                // 로그인 회원 -> loginUser 변수
                UserDetailsImpl userDetails = (UserDetailsImpl) auth.getPrincipal();
                User loginUser = userDetails.getUser();

                // 수행시간 및 DB 에 기록
                UserTime userTime = userTimeRepository.findByUser(loginUser);
                if (userTime != null) {
                    // 로그인 회원의 기록이 있으면
                    long totalTime = userTime.getTotalTime();
                    totalTime = totalTime + runTime;
                    userTime.updateTotalTime(totalTime);
                } else {
                    // 로그인 회원의 기록이 없으면
                    userTime = new UserTime(loginUser, runTime);
                }

                System.out.println("[User Time] User: " + userTime.getUser().getUsername() + ", Total Time: " + userTime.getTotalTime() + " ms");
                userTimeRepository.save(userTime);
            }
        }
    }
}

 

2. 트랜잭션 (Transaction)

- 트랜잭션은 데이터베이스에서 데이터에 대한 하나의 논리적 실행단계이다. ACID(원자성, 일관성, 고립성, 지속성)는 데이터베이스 트랜잭션이 안전하게 수행된다는 것을 보장하기 위한 성질을 가리키는 약어이다.

- 트랜잭션은 더 이상 쪼갤 수 없는 최소 단위의 작업이다. 따라서 작업이 모두 저장되거나 아무것도 저장되지 않는 것을 보장한다. 모든 작업이 성공적으로 완료되면 모든 작업이 저장되어 트랜잭션 Commit 이 일어난다. 작업 도중 에러가 발생하거나 중간에 작업에 실패할 경우 모든 작업이 저장되지 않아 트랜잭션 Rollback 이 일어난다.

 

다음은 트랜잭션을 적용한 코드이다. 스프링에서 @Transactional 어노테이션을 붙이면 간단하게 트랜잭션을 적용할 수 있다. 여기에서는 폴더 생성시에 트랜잭션을 적용하도록 되어있다. 폴더 생성시에 중복 폴더가 없고 모든 폴더가 성공적으로 생성된다면 트랜잭션 Commit이 일어난다. 폴더 생성시에 중복 폴더가 있다면 Exception이 발생되고 트랜잭션 Rollback이 일어난다.

@Transactional
public List<Folder> createFolders(List<String> folderNameList, User user) {
    List<Folder> folderList = new ArrayList<>();
    
    for (String folderName : folderNameList) {
        // 1) DB 에 폴더명이 folderName 인 폴더가 존재하는지?
        Folder folderInDB = folderRepository.findByName(folderName);
        if (folderInDB != null) {
            // DB 에 중복 폴더명 존재한다면 Exception 발생시킴
            throw new IllegalArgumentException("중복된 폴더명 (" + folderName +") 을 삭제하고 재시도해 주세요!");
        }

        // 2) 폴더를 DB 에 저장
        Folder folder = new Folder(folderName, user);
        folder = folderRepository.save(folder);

        // 3) folderList 에 folder Entity 객체를 추가
        folderList.add(folder);
    }

    return folderList;
}
반응형