Spring AOP, ํธ๋์ญ์ , ์์ธ ์ฒ๋ฆฌ
์คํ๋ง AOP, ํธ๋์ญ์ , ์์ธ ์ฒ๋ฆฌ
์คํ๋ฅดํ ์ฝ๋ฉ ํด๋ฝ์ Spring ์ฌํ๋ฐ 5์ฃผ ์ฐจ ๋ด์ฉ์ธ ์คํ๋ง AOP, ํธ๋์ญ์
, ์์ธ ์ฒ๋ฆฌ
๋ฅผ ์ ๋ฆฌํ๋ค.
ํต์ฌ ๋ด์ฉ
- AOP์ ๊ฐ๋ ์ดํด์ ์ฌ์ฉ๋ฐฉ๋ฒ
- DB ํธ๋์ญ์ ์ดํด
- ์คํ๋ง ์์ธ ์ฒ๋ฆฌ๋ฐฉ๋ฒ
AOP์ ๊ฐ๋ ์ดํด์ ์ฌ์ฉ๋ฐฉ๋ฒ
AOP (Aspect Oriented Programming)
AOP : ๊ด์ ์งํฅ ํ๋ก๊ทธ๋๋ฐ
AOP์ ์ฌ์ฉ ์ด์
ํต์ฌ ๊ธฐ๋ฅ
๊ณผ ๋ถ๊ฐ๊ธฐ๋ฅ
์ ๊ฐ๋ฐํ๋ค๊ณ ํ์.
ํต์ฌ ๊ธฐ๋ฅ์ ๋ง ๊ทธ๋๋ก API๊ฐ ์ํํด์ผ ํ๋ ํต์ฌ์ ์ธ ๊ธฐ๋ฅ์ด๊ณ , ๋ถ๊ฐ๊ธฐ๋ฅ์ ๋ถ๊ฐ์ ์ผ๋ก ๊ตฌํํด์ผ ๋ ๋ก๊น
, ์ธ์ฆ๊ณผ ๊ฐ์ ๊ธฐ๋ฅ์ด๋ค.
๋ชจ๋ ํต์ฌ ๊ธฐ๋ฅ์ ๋ถ๊ฐ๊ธฐ๋ฅ์ ์ถ๊ฐํด์ผ ๋๋ค๊ณ ์๊ฐํ ๋, ํต์ฌ ๊ธฐ๋ฅ์ ์๊ฐ ๋๋ฌด ๋ง์ผ๋ฉด ๊ทธ ํต์ฌ ๊ธฐ๋ฅ์ ํ๋ํ๋ ๋ถ๊ฐ๊ธฐ๋ฅ์ ๋ฃ์ด์ผ ํ๊ณ , ์ด๋ ์ค์๋ฅผ ํ ๊ฐ๋ฅ์ฑ๊ณผ ์ถํ์ ๋ถ๊ฐ๊ธฐ๋ฅ์ ์ ์ง ๋ณด์ํจ์ ์์ด์ ์ด๋ ค์์ด ์๊ธด๋ค.
์ด๋ AOP
๋ ๋ค๋ฅธ ๊ด์ (Aspect)์ ๊ฐ์ง ๋ถ๊ฐ๊ธฐ๋ฅ
๊ณผ ํต์ฌ ๊ธฐ๋ฅ
์ ๋ถ๋ฆฌํ์ฌ ๋ถ๊ฐ๊ธฐ๋ฅ ์ค์ฌ์ผ๋ก ์ค๊ณ, ๊ตฌํํ๋๋ก ๋์์ค๋ค.
OOP(๊ฐ์ฒด์งํฅ ํ๋ก๊ทธ๋๋ฐ) VS AOP
OOP๋ ํต์ฌ ๊ธฐ๋ฅ์ ๋ชจ๋ํ ํ๋ ํ๋ก๊ทธ๋๋ฐ
AOP๋ ๋ถ๊ฐ๊ธฐ๋ฅ(๋ก๊น , ํธ๋์ญ์ ...)์ ๋ชจ๋ํํ๋ ํ๋ก๊ทธ๋๋ฐ
๋ฐ๋ผ์ AOP๋ OOP๋ฅผ ๋์ฒดํด ์ฃผ๋ ๊ฒ์ด ์๋ ๋ณด์ ํด์ฃผ๋ ํ๋ก๊ทธ๋๋ฐ์ด๋ค.
์ด๋๋ฐ์ด์ค, ํฌ์ธํธ ์ปท
- ์ด๋๋ฐ์ด์ค : ๋ถ๊ฐ๊ธฐ๋ฅ
- ํฌ์ธํธ ์ปท : ๋ถ๊ฐ๊ธฐ๋ฅ์ ์ ์ฉํ ์์น
๋์ ์๋ฆฌ
์คํ๋ง(๋์คํจ์ณ ์๋ธ๋ฆฟ)์ด ํต์ฌ ๊ธฐ๋ฅ
์ ์ ๊ทผํ๊ธฐ ์ ์, ํ๋ก์ ๊ฐ์ฒด
๋ฅผ ์ค๊ฐ์ ์ฝ์
ํ๋ ์๋ฆฌ๋ก AOP๊ฐ ๋์ํ๋ค.
์์ค ์์
@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);
}
}
}
}
@Around
์ด๋
ธํ
์ด์
์ ํตํด AOP๋ฅผ ์ ์ฉํ ๋ฒ์๋ฅผ ์ ํด์ค๋ค.joinPoint.proceed();
๋ก ํต์ฌ ๊ธฐ๋ฅ์ ์ํํ๋ค.
DB ํธ๋์ญ์ ์ ์ดํด
ํธ๋์ญ์
ํธ๋์ญ์ : ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๋ฐ์ดํฐ์ ๋ํ ํ๋์ ๋ ผ๋ฆฌ์ ์คํ๋จ๊ณ
ACID (์์์ฑ, ์ผ๊ด์ฑ, ๊ณ ๋ฆฝ์ฑ, ์ง์์ฑ)๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํธ๋์ญ์ ์ด ์์ ํ๊ฒ ์ํ๋๋ค๋ ๊ฒ์ ๋ณด์ฅํ๊ธฐ ์ํ ์ฑ์ง์ ๊ฐ๋ฆฌํค๋ ์ฝ์ด
์ถ์ฒ: ์ํค๋ฐฑ๊ณผ
ํธ๋์ญ์ ์ ํน์ง์ ๋ค์๊ณผ ๊ฐ๋ค.
- ๋ ์ด์ ์ชผ๊ฐค ์ ์๋ ์ต์๋จ์์ ์์
- ๋ชจ๋ ์ ์ฅ๋๊ฑฐ๋, ์๋ฌด๊ฒ๋ ์ ์ฅ๋์ง ์๊ฑฐ๋๋ฅผ ๋ณด์ฅํ๋ค.
ํธ๋์ญ์ ๊ณผ AOP
ํธ๋์ญ์
์ ์ ์ฉํ๊ธฐ ์ํด์ ํธ๋์ญ์
๋งค๋์ ๊ฐ์ฒด๋ฅผ ํตํด ํธ๋์ญ์
์ ๋ง๋ค์ด์ค ๋ค, try, catch๋ฌธ์ผ๋ก ํธ๋์ญ์
์ด ์ฑ๊ณตํ๋ฉด commit
, ์คํจํ๋ฉด rollback
์ ์ ์ฉํด ์ค์ผ ํ๋ค.
ํ์ง๋ง ์คํ๋ง์์ @Transactional
์ด๋ผ๋ ์ด๋
ธํ
์ด์
์ ์ ์ธํด ์ฃผ๋ฉด, ํธ๋์ญ์
์ ๋ํ ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์๋์ผ๋ก ๋ง๋ค์ด์ฃผ์ด ๋ฐ๋ก ํธ๋์ญ์
์ ๋ํ ์ฝ๋๋ฅผ ๊ตฌํํ์ง ์์๋ ํธ๋์ญ์
์ด ์ ์ฉ๋๋ค.
ํ์ ์์์ DB ์ด์ ๋ฐฉ์
- ์ฐ๊ธฐ ์ ์ฉ DB
(Primary)
์ ์ฝ๊ธฐ ์ ์ฉ DB(Replica)
๋ฅผ ๊ตฌ๋ถ - Primary๋ ์ฐ๊ธฐ ์ ์ฉ์ผ๋ก์จ
@Transactional(readOnly = false)
(๊ธฐ๋ณธ๊ฐ์ด false) - Replica๋ ์ฝ๊ธฐ ์ ์ฉ์ผ๋ก์จ
@Transactional(readOnly = true)
- Primary์ ๋ฌธ์ ๊ฐ ์๊ฒผ์ ๋, Replica ์ค 1๊ฐ๊ฐ Primary๊ฐ ๋์ด DB๊ฐ ์ ์ ์ด์๋๋ค.
์คํ๋ง ์์ธ ์ฒ๋ฆฌ ๋ฐฉ๋ฒ
ํด๋์ค ์์ฑ
- ํน์ ์์ธ๋ฅผ ์์๋ฐ๋ ํด๋์ค๋ฅผ ๋ง๋ ๋ค, ์์ธ์ ๋ฉ์ธ์ง๋ฅผ
super()
๋ก ๋ฐ์์ค๋ค. @RestControllerAdvice
(๋ ์คํธ ์ปจํธ๋กค๋ฌ์๋ง ์ ์ฉ) ์ด๋ ธํ ์ด์ ์ ๋ถ์ธ ํด๋์ค๋ฅผ ๋ง๋ค์ด ํน์ ์์ธ ์ฒ๋ฆฌ๋ฅผ ์ ์ญ์ ์ผ๋ก ๊ด๋ฆฌํด ์ค๋ค.- ์์ธ ์ฒ๋ฆฌํ ์์ธ ํด๋์ค๋ฅผ
@ExceptionHandler
์ด๋ ธํ ์ด์ ์ value์ ๋ฃ์ด์ฃผ๊ณ ๋ฉ์๋๋ฅผ ๋ง๋ค์ด์ค๋ค.
์์
public class ApiRequestException extends IllegalArgumentException {
public ApiRequestException(String message) {
super(message);
}
public ApiRequestException(String message, Throwable cause) {
super(message, cause);
}
}
@RestControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(value = { ApiRequestException.class })
public ResponseEntity<Object> handleApiRequestException(ApiRequestException ex) {
ApiException apiException = new ApiException(
ex.getMessage(),
// HTTP 400 -> Client Error
HttpStatus.BAD_REQUEST
);
return new ResponseEntity<>(
apiException,
// HTTP 400 -> Client Error
HttpStatus.BAD_REQUEST
);
}
}