## 73. 애노테이션을 사용하여 트랜잭션 제어하기
- 프록시 패턴 기술을 사용하여 트랜잭션 코드를 삽입하기
=>
ㄴ bitcamp 패키지에 util 이라는 이름의 새로운 패키지 생성
=>
=>
ㄴ bitcamp.util 패키지에 새로운 애노테이션 파일 추가
=>
Transactional.java
DefaultBoardService.java
=>
DefaultBoardService.java
ㄴ @Transactional 애노테이션을 이용하여 해당 메서드는 트랜잭션 상태에서 실행하라고 지정
=>
DefaultBoardService.java
ㄴ 해당 코드 제거
=>
DefaultBoardService.java
=>
ㄴ @Transactional 애노테이션을 이용하여 해당 메서드는 트랜잭션 상태에서 실행하라고 지정
=>
DefaultBoardService.java
ㄴ 해당 코드 제거
=>
DefaultBoardService.java
ㄴ @Transactional 애노테이션을 이용하여 해당 메서드는 트랜잭션 상태에서 실행하라고 지정
=>
DefaultBoardService.java
ㄴ 해당 코드 제거
=>
DefaultBoardService.java
ㄴ 해당 코드 제거
=>
DefaultBoardService.java
ㄴ 생성자 새로 만들기
=>
DefaultBoardService.java
ㄴ 해당 코드 제거
=>
DefaultBoardService.java
ㄴ @Transactional 애노테이션을 이용하여 해당 메서드는 트랜잭션 상태에서 실행하라고 지정
DefaultMemberService.java
ㄴ 해당 코드 제거
=>
DefaultMemberService.java
ㄴ 생성자 새로 만들기
=>
DefaultMemberService.java
ㄴ @Transactional 애노테이션을 추가
=>
DefaultMemberService.java
ㄴ 해당 코드 제거
=>
DefaultMemberService.java
ㄴ @Transactional 애노테이션을 추가
=>
DefaultMemberService.java
ㄴ 해당 코드 제거
=>
DefaultMemberService.java
ㄴ @Transactional 애노테이션을 추가
=>
DefaultMemberService.java
ㄴ 해당 코드 제거
ㄴ util 패키지에 TransactionProxyBuilder 라는 이름의 새로운 클래스를 생성하기
=>
TransactionProxyBuilder.java
=>
TransactionProxyBuilder.java
AppConfig.java
ㄴ TransactionProxyBuilder 객체를 생성하고, PlatformTransactionManager 객체를 주입받아 생성된 TransactionProxyBuilder에 설정하도록 함
=>
AppConfig.java
ㄴ txProxyBuilder.build(new DefaultBoardService(boardDao)) 코드를 사용하여 BoardService의 구현체를 생성하고, 이 구현체에 대한 트랜잭션 프록시를 생성하도록 함
=>
AppConfig.java
ㄴ txProxyBuilder.build(new DefaultMemberService(memberDao)) 코드를 사용하여 MemberService의 구현체를 생성하고, 이 구현체에 대한 트랜잭션 프록시를 생성하도록 함
DefaultBoardService.java
DefaultMemberService.java
=>
App 실행
=>
ㄴ 해당 에러가 발생함
TransactitonProxyBuilder.java
=>
TransactitonProxyBuilder.java
=>
TransactitonProxyBuilder.java
=>
TransactitonProxyBuilder.java
=>
TransactitonProxyBuilder.java
ㄴ PlatformTransactionManager -> TransactionTemplate 으로 변경
ㄴ 생성자 변경
=>
TransactitonProxyBuilder.java
package bitcamp.util;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class TransactionProxyBuilder {
TransactionTemplate txTemplate;
public TransactionProxyBuilder(PlatformTransactionManager txManager) {
this.txTemplate = new TransactionTemplate(txManager);
}
public Object build(Object originalWorker) {
// 1) 인터페이스 알아내기
Class<?> clazz = originalWorker.getClass().getInterfaces()[0];
// 2) 인터페이스 구현체를 만들어 리턴하다.
return Proxy.newProxyInstance(
this.getClass().getClassLoader(),
new Class[]{clazz},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 프록시 객체의 메서드를 호출할 때,
// @Transactional이 붙은 메서드를 호출할 때는 TranscationTemplate으로 처리하고
// @Transactionaldl이 안 붙은 메서드는 그냥 오리지널 객체의 메서드를 그대로 호출한다.
// 1) 프록시 객체의 메서드와 일치하는 오리지널 작업 객체의 메서드를 가져온다.
Method originalMethod = getOriginalMethod(originalWorker, method);
// 2) 오리지널 객체의 메서드에서 @Transactional 애노테이션을 추출한다.
Transactional transactional = originalMethod.getAnnotation(Transactional.class);
if (transactional != null) {
// @Transactional 애노테이션이 붙은 메서드라면 TransactionTemplate을 통해 실행하고
return txTemplate.execute(status -> {
try {
return originalMethod.invoke(originalWorker, args);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
} else {
// @Transactional 애노테이션이 안 붙은 메서드라면 그냥 호출한다.
return originalMethod.invoke(originalWorker, args);
}
}
});
}
public Method getOriginalMethod(Object originalWorker, Method method) throws Exception {
Method[] methods = originalWorker.getClass().getDeclaredMethods();
for (Method originalMethod : methods) {
if (originalMethod.getName().equals(method.getName()) && originalMethod.getParameterCount() == method.getParameterCount()) {
return originalMethod;
}
}
return null;
}
}
ㄴ TransactionProxyBuilder 클래스는 스프링의 PlatformTransactionManager와 TransactionTemplate을 사용하여 트랜잭션을 관리하는 데 사용
ㄴ build 메서드는 프록시 객체를 생성하고 반환하도록 하고 프록시 객체는 원본 객체와 동일한 인터페이스를 구현하며, 메서드 호출 시 트랜잭션 처리를 수행하도록 함
ㄴ getOriginalMethod 메서드는 프록시 메서드와 일치하는 원본 작업 객체의 메서드를 찾도록 하며, 메서드 이름과 매개변수 개수를 비교하여 일치하는 메서드를 찾도록 함
ㄴ InvocationHandler를 구현한 익명 클래스 내에서 메서드 호출이 처리됨
ㄴ @Transactional 애노테이션이 있는 메서드는 TransactionTemplate을 사용하여 트랜잭션 내에서 실행됨
ㄴ @Transactional 애노테이션이 없는 메서드는 원본 객체의 메서드가 직접 호출됨
=>
ㄴ App 재실행하여 오류 없이 실행됨을 확인
=>
TransactitonProxyBuilder.java
ㄴ 메서드가 트랜잭션을 경유하는지 직접 호출하는지 출력해보기 위해 출력코드 추가
=>
ㄴ App 재실행
=>
=>
ㄴ 로그인 정보 입력 후 [로그인] 선택
=>
=>
=>
ㄴ 회원 목록 보기
=>
=>
ㄴ 회원 정보 입력 후 [등록] 선택
=>
ㄴ add() 실행 시 트랜잭션 경유해야 하므로 코드 수정 필요
Transactional.java
ㄴ @Retention(RetentionPolicy.RUNTIME)은 Java 어노테이션(Annotation)의 유지 정책(Retention Policy) 중 하나
ㄴ 어노테이션을 언제까지 유지할 것인지를 나타냄
ㄴ @Retention 어노테이션은 보통 어노테이션 정의에 함께 사용되며, 해당 어노테이션이 어느 시점까지 유지되어야 하는지를 명시하는 데에 사용됨
ㄴ RetentionPolicy.RUNTIME은 가장 넓은 유효 범위를 가지며, 어노테이션 정보가 컴파일 이후에도 유지되고 런타임 시에도 사용 가능함을 의미함
ㄴ 즉, 프로그램 실행 중에 어노테이션 정보를 읽어와서 처리할 수 있음
ㄴ 이는 주로 리플렉션(reflection)을 사용하여 어노테이션 정보를 동적으로 분석하고 활용하는 데에 사용됨
=>
ㄴ App 재실행
=>
=>
ㄴ 로그인 정보 입력 후 [로그인] 선택
=>
ㄴ 로그인 정상적으로 실행됨
=>
=>
ㄴ 회원 목록 보기
=>
=>
ㄴ 회원 정보 입력 후 [등록] 선택
=>
ㄴ add() 실행 시 트랜잭션 경유함을 확인
=>
ㄴ 회원이 정상적으로 등록되며 회원 목록으로 이동됨을 확인
=>
DispatcherServlet.java
ㄴ 해당 코드 주석 풀어주기
=>
App 재실행
=>