본문 바로가기
네이버클라우드/JAVA 웹 프로그래밍

JAVA 72일차 (2023-09-04) 자바 프로그래밍_73. 애노테이션을 사용하여 트랜잭션 제어하기_개인프로젝트 - 마트 관리 시스템

by prometedor 2023. 9. 4.
## 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 재실행

=>