본문 바로가기
  • 실행력이 모든걸 결정한다
Spring Series/Spring Framework

[Spring] 여러 AOP 및 트랜잭션 기법들

by 김코더 김주역 2022. 4. 1.
반응형

1. AOP란?

Aspect Oriented Programming

- 애플리케이션에 흩어져 있는 부가적인 공통 기능들을 독립적으로 모듈화하는 프로그래밍 모델이다. Aspect는 핵심기능에 부가되는 모듈을 의미한다.

- 핵심 기능과 공통 기능을 분리 시켜놓고, 핵심 기능들 중에서 공통 기능을 필요로 하는 것이 있을 때 사용된다. 핵심 기능은 프로그램의 특정 목적에 대해 사용되는 기능이고, 공통 기능은 일반 연산처럼 다양한 프로그램들에서 공통적으로 사용되는 기능이다.

 

 

2. 비즈니스 로직과 트랜잭션의 분리

1) 원리

- 비즈니스 로직과 트랜잭션 코드를 분리할 때, 동일한 인터페이스를 상속하는 트랜잭션용 구현체를 만들면 된다.

- 아래 코드처럼 UserServiceTx의 트랜잭션 메소드 안에서 UserServiceImpl의 동일한 이름의 메소드를 호출하면 된다.

 

UserServiceTx.upgradeLevels()

public void upgradeLevels() {
    TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
    try {
    	userService.upgradeLevels(); // userService <- UserServiceImpl 주입
        this.transactionManager.commit(status);
    } catch (RuntimeException e) {
    	this.transactionManager.rollback(status);
        throw e;
    }
}

 

UserServiceImpl.upgradeLevels()

public void upgradeLevels() {
    List<User> users = userDao.getAll();
    for(User user : users) {
    	if(canUpgradeLevel(user)) {
        	upgradeLevel(user);
        }
    }
}

 

 

3. 프록시 패턴

- 클라이언트가 사용하려고 하는 실제 대상인 것처럼 위장해서 클라이언트의 요청을 받는 오브젝트를 프록시(proxy)라고 한다.

- 프록시 패턴에서의 프록시는 타깃과 같은 인터페이스를 사용하며, 타깃을 제어할 수 있는 위치에 있다는 특징이 있다.

※ 프록시는 클라이언트가 타깃에 접근하는 방법을 제어하기 위해 사용되거나, 타깃에 부가적인 기능을 부여해주기 위해 사용된다. 후자의 경우에서는 데코레이터라고도 부른다.

- 프록시는 서로 같은 인터페이스를 사용하기 때문에, 개수가 많아질수록 변경 작업이 까다로워지고 중복코드가 발생할 가능성이 높아진다.

- 타깃 오브젝트의 메소드들은 프록시를 거치기 때문에, 동일 타깃 안에서 서로 메소드 호출이 일어나는 경우에는 프록시의 부가기능이 적용되지 않는다는 사실을 주의해야 한다. AspectJ AOP를 사용하면 이러한 한계를 극복할 수 있다.

※ 이러한 이슈를 self invocation이라고 한다.

※ AspectJ AOP self invocation 이슈 해결 방법 참고 : https://tecoble.techcourse.co.kr/post/2022-11-07-transaction-aop-fact-and-misconception/

 

 

4. 데코레이터 패턴

- 데코레이터 패턴은 여러 데코레이터들을 사용하여 타깃의 코드와 호출 방법의 변동 없이, 부가적인 기능을 추가할 때 사용된다.

- 클라이언트와 타깃의 사이에 데코레이터 역할을 하는 인터페이스들을 두고, 각 인터페이스에 구현체를 조립하는 방식으로 사용한다.

- 각 데코레이터는 생성자나 수정자를 통해 오브젝트를 받아서 내부 메소드에서 기능을 적용해서 반환해주면 된다.

- 프록시 패턴과 함께 사용될 수 있다.

 

 

5. 동적 프록시

- 동적 프록시는 프록시 팩토리에 의해 런타임 시 동적으로 생성되는 오브젝트다.

 

1) 리플렉션

- 자바 코드 자체를 추상화해서 접근하도록 만든 것

- java.lang.reflect.Method 인터페이스는 메소드에 대한 자세한 정보를 담고 있으며, 다음과 같이 메소드를 실행(invoke)시킬 수 있도록 해준다.

// 메소드를 실행시킬 대상 오브젝트 obj와 파라미터 목록 args를 받아서 실행한다.
public Object invoke(Object obj, Object... args)

 

예시

Method lengthMethod = String.class.getMethod("length"); // String의 length() 메소드 정보를 저장
int namelen = (Integer)lengthMethod.invoke("jooyeok"); // 실행, 결과 7

Method charAtMethod = String.class.getMethod("charAt", int.class); // String의 charAt() 메소드 정보를 저장
Character c = (Character)charAtMethod.invoke("jooyeok", 3); // 실행, 결과 y

 

 

2) InvocationHandler 인터페이스

- 동적 프록시로부터 메소드 호출 정보를 받아서 처리하는 역할을 한다.

- 동적 프록시는 클라이언트의 모든 요청을 리플렉션으로 변환하여 invoke() 메소드로 넘기며, 이러한 이유로 코드의 중복을 줄일 수 있다.

- InvocationHandler에 타깃 오브젝트를 저장해두고, invoke() 메소드에 타깃 오브젝트에 실행할 메소드와 파라미터를 넘기면 된다. 

 

예시

InvocationHandler 구현체

public class UppercaseHandler implements InvocationHandler {
    Object target;
    private UppercaseHandler(Object target) {
        this.target = target;
    }
    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object ret = method.invoke(target, args);
        if (ret instanceof String) { // ret이 String 타입이면 대문자로 변환하여 리턴
            return ((String)ret).toUpperCase();
        } else {
            return ret;
        }
    }
}

※ Method 오브젝트에는 메소드에 관한 정보가 들어있기 때문에, 메소드의 이름으로 if 조건을 따질 수도 있다.

ex) if(method.getName().startWith("get")){...}

 

동적 프록시 생성

// 생성된 다이내믹 프록시 오브젝트는 Hello 인터페이스를 구현하고 있으므로 Hello 타입으로 캐스팅해도 안전하다.
Hello proxiedHello = (Hello)Proxy.newProxyInstance(
    getClass().getClassLoader(), // 동적으로 생성되는 다이내믹 프록시 클래스의 로딩에 사용할 클래스 로더
    new Class[] { Hello.class }, // 구현할 인터페이스 목록
    new UppercaseHandler(new HelloImpl())); // 부가기능과 위임 코드를 담은 InvocationHandler

 

 

3) 동적 프록시를 위한 Factory Bean

- 동적 프록시는 말 그대로 동적으로 생성되기 때문에 일반적인 방법으로는 Spring 빈에 정의할 수 없다.

- Factory Bean은 Spring을 대신해서 오브젝트의 생성 로직을 담당하는 특별한 빈이다.

- Factory Bean은 FactoryBean 인터페이스를 구현해서 만들 수 있다.

public interface FactoryBean<T> {
    T getObject() throws Exception; // Spring이 사용할 Bean 오브젝트
    Class<? extends T> getObjectType(); // 프록시가 구현할 인터페이스를 반환
    boolean isSingleton(); // getObject() 메소드가 반환하는 오브젝트가 싱글톤인지를 알려줌
}

 

예시

- FactoryBean 구현

public class TxProxyFactoryBean implements FactoryBean<Object> {
    Object target;
    PlatformTransactionManager transactionManager;
    String pattern;
    Class<?> serviceInterface; // 다양한 인터페이스에 적용할 수 있도록 멤버로 설정
    
    public void setTarget(Object targer) {
        this.target = targer;
    }
    
    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }
    
    public void setPattern(String pattern) {
        this.pattern = pattern;
    }
    
    public void setServiceInterface(Class<?> serviceInterface) {
        this.serviceInterface = serviceInterface;
    }
    
    // 동적 프록시를 생성해서 반환
    public Object getObject() throws Exception {
        TransactionHandler txHandler = new TransactionHandler();
        txHandler.setTarget(targer);
        txHandler.setTransactionManager(transactionManager);
        txHandler.setPattern(pattern);
        return Proxy.newProxyInstance(
                getClass().getClassLoader(), new Class[] { serviceInterface },
                txHandler);
    }
    
    public Class<?> getObjectType() {
        return serviceInterface;
    }
    
    public boolean isSingleton() {
        return false; //getObject()는 매번 같은 오브젝트를 리턴하지 않음
    } 
}

 

- Factory Bean 설정

<bean id="userService" class="[패키지].TxproxyFactoryBean">
	<proprety name="target" ref="userSerivceImpl" />
	<proprety name="transactionManager" ref="transactionManager" />
	<proprety name="pattern" value="" />
	<proprety name="serviceInterface" value="[패키지].Userservice" />
</bean>

※ 여러 개의 프록시를 적용할 때에는 동일한 FactoryBean을 참조한 상태에서 프로퍼티만 바꿔서 추가해주면 된다.

 

 

4) ProxyFactoryBean

- Spring은 Proxy 오브젝트를 생성해주는 기술을 추상화한 ProxyFactoryBean을 제공해준다.

- 여러개의 메소드에만 공통적인 부가기능을 제공해줄 수 있고, 여러개의 클래스에는 공통적인 부가기능을 제공해줄 수는 없다는 문제점을 해결할 수 있다.

- ProxyFactoryBean은 인터페이스 자동 검출 기능이 있기 때문에 구현해야 할 인터페이스를 작성할 필요가 없다. 직접 작성하려면 setInterfaces() 메소드를 이용하면 된다.  프록시 적용 인터페이스를 따로 지정해주지 않았다면, 타깃 오브젝트가 구현하고 있는 인터페이스를 자동으로 프록시의 인터페이스로 사용한다.

- ProxyFactoryBean 하나로도 여러 개의 부가 기능을 제공해주는 프록시를 만들 수 있으며, 타깃 오브젝트에 적용할 독립적인 부가기능을 담은 오브젝트를 Advice라고 한다. Advice는 자신이 공유돼야 하므로 타깃 정보를 가지지 않는 대신 템플릿 구조로 설계되어 있다.

- 부가기능을 부여할 메소드를 선정하는 알고리즘을 담은 오브젝트를 Pointcut이라고 한다.

public interface Pointcut {
    ClassFilter getClassFilter(); // 프록시를 적용할 클래스를 확인
    MethodMatcher getMethodMatcher(); // Advice를 적용할 메소드를 확인
}

- Advice와 Pointcut 모두 여러 프록시에서 공유가 가능하도록 만들어지기 때문에 Bean으로 등록해서 쓰면 된다.

- Advice와 Pointcut을 묶은 오브젝트를 Advisor라고 한다.

 

예시

public void proxyFactoryBean() {
    ProxyFactoryBean pfBean = new ProxyFactoryBean(); // ProxyFactoryBean은 Spring이 제공해준다.
    pfBean.setTarget(new HelloImpl()); //타깃 설정
    pfBean.addAdvice(new UppercaseAdvice()); // 하나 이상의 부가 기능을 추가(add)해나갈 수 있다.
    
    /* pointcut을 적용하는 경우에는 Advice와 Pointcut을 묶어서 Advisor 타입으로 등록한다.
    NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
    pointcut.setMappedName("up*"); // up으로 시작하는 메소드들을 선택
    pfBean.addAdvisor(new DefaultPointcutAdvisor(pointcut, new UppercaseAdvice()));
    */
    
    Hello proxiedHello = (Hello) pfBean.getObject();
    ...
}
    
static class UppercaseAdvice implements MethodInterceptor {
    public Object invoke(MethodInvocation invocation) throws Throwable {
        String ret = (String)invocation.proceed(); // Pointcut에 매핑된 타깃 오브젝트의 메소드를 실행한다.
        return ret.toUpperCase(); // 부가 기능을 적용
    }
}

- MethodInterceptor는 InvocationHandler과 달리 타깃을 클래스가 직접 알 수 없기 때문에 독립적으로 만들어질 수 있다.

- MethodInvocation이 일종의 콜백 메소드가 되기 때문에, 템플릿 역할을 하는 MethodInvocation을 싱글톤으로 두고 공유할 수 있게 되는 것이다.

 

aop 네임 스페이스 사용

- aop 네임 스페이스를 통해 자동 프록시 생성기 bean을 간단히 등록할 수 있다.

- 스키마는 다음과 같이 추가한다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
           ">

...

- <aop:config>는 자동 프록시 생성기 bean을 설정하는 태그다. 다음과 같이 <aop:pointcut> 태그로 포인트컷을 독립시키거나 bean으로 따로 설정할 수도 있고

<aop:config> <!--자동 프록시 생성기 bean에 해당하는 설정-->
	<aop:pointcut id="transactionPointcut" expression="bean(*Service)" />
	<aop:advisor advice-ref="transactionAdvice" pointcut-ref="transactionPointcut" />
</aop:config>

다음과 같이 어드바이스와 포인트컷을 결합하여 사용할 수도 있다. 이 때 포인트컷 bean은 자동으로 생성된다.

<aop:config>
	<aop:advisor advice-ref="transactionAdvice" pointcut="bean(*Service)" />
</aop:config>

※ aop:pointcut의 expression은 뒤에 있는 [5-6) Pointcut 표현식]에서 설명한다.

※ Advice bean은 뒤에 있는 [6-2) transactionAttributes]에서 설명한다.

※ 인터페이스를 구현하지 않은 클래스에 프록시를 적용해야 할 경우에는 <aop:config>의 proxy-target-class 속성을 "true"로 지정하고 pointcut의 선정 대상도 클래스로 지정한다. 이렇게 설정하면 인터페이스가 있더라도 무시하고 프록시 클래스를 만든다. 추천하는 방식은 아니다.

 

 

5) BeanPostProcessor

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />

- Bean 후처리기를 bean으로 등록해놓으면 자동으로 타겟을 프록시로 생성해주기 때문에, 일일이 설정파일에서 중복으로 FactoryBean을 등록하지 않아도 된다.

- Bean 후처리기는 bean으로 등록된 advisor들의 pointcut을 확인하여, 전달받은 bean이 프록시 적용 대상인지를 확인한다. 프록시 적용 대상이 맞다면 내장된 프록시 생성기에게 현재 bean을 포장할 프록시를 만들게 하고 이 프록시에 advisor을 연결해주는 식으로 동작한다.

※ Bean 후처리기가 만드는 프록시는 java.lang.reflect.proxy 타입이다.

- Spring 컨테이너는 Bean 후처리기가 돌려준 오브젝트를 Bean으로 등록하고 사용한다.

 

 

6) Pointcut 표현식

- 포인트컷 표현식을 아래 포스팅에 정리해놓았으니 참고하길 바란다.

https://kimcoder.tistory.com/543

 

[Spring] AOP 포인트컷 표현식

1. 지시자의 종류 - 메소드가 아닌 오브젝트를 선정하는 경우에는 해당 오브젝트 안에 있는 메소드들이 AOP 적용 대상이 된다. 1) execution() - 표현식 언어를 사용해서 포인트컷을 작성하는 방법이

kimcoder.tistory.com

 

Pointcut bean 등록

<bean id="transactionPointcut" class="org.springframework.aop.aspectj.AspectJExpressionPointcut">
	<property name="expression" value="execution([표현식])" />
</bean>

 

Pointcut bean 테스트 코드

assertThat(
    pointcut.getClassFilter().matches(Target.class)&&pointcut.getMethodMatcher().matches(Target.class.getMethod("targetMethod", int.class, int.class), null), is(true)
);

 

 

 

6. @AspectJ AOP

- AspectJ는 바이트코드를 조작해서 타깃 오브젝트 자체의 코드를 바꿈으로써 애스펙트를 적용한다.

- @AspectJ는 애스펙트를 클래스, 메소드, 어노테이션으로 정의하는 방법이다.

- 독립적인 bean의 조합으로 만들어지는 어드바이저와 달리 @AspectJ의 애스펙트는 다양한 조합을 갖는 포인트컷과 어드바이스를 하나의 모듈로 정의할 수 있다.

- 사용법은 https://kimcoder.tistory.com/232를 참고하자.

 

 

 

7. TransactionInterceptor

1) TransactionInterceptor란?

- TransactionInterceptor은 트랜잭션 설정을 메소드별로 적용하기 위해 사용된다. 트랜잭션 설정이라고 한다면 TransactionDefinition 인터페이스가 갖는 전파, 격리수준, 제한시간, 읽기전용 총 4가지 설정(속성)같은 것이 있다.

- TransactionInterceptor은 PlatformTransactionManager와 Properties 타입의 두 가지 속성을 가진다.

 

 

2) transactionAttributes

- TransactionInterceptor의 두 번째 속성인 Properties 타입의 속성이다. 메소드 패턴과 트랜잭션 속성을 키와 값으로 갖는 컬렉션이다.

- 트랜잭션의 동작 방식을 모두 제어할 수 있으며 TransactionDefinition의 4가지 기본 항목에 rollbackOn() 메소드를 하나 더 갖고 있다. rollbackOn()은 롤백을 적용할 예외를 결정한다.

※ TransactionInterceptor은 기본적으로 런타임 예외가 발생하면 트랜잭션을 rollback시키고 체크 예외가 발생하면 트랜잭션을 commit시킨다는 특징이 있어서, 이를 커스터마이징할 수 있도록 rollbackOn()를 추가로 두는 것이다.

 

사용 방법

- 트랜잭션 속성들을 다음과 같이 문자열로 정의한다. 여기서 트랜잭션 전파 항목만 필수고, 생략시 DefaultTransactionDefinition에 사용되는 기본 속성이 부여된다.

PROPAGATION NAME, ISOLATION NAME, readonly, timeout_NNNN, -Exception, +Exception

※ PROPAGATION NAME : 트랜잭션 전파 방식 (참고 - https://kimcoder.tistory.com/242#propagation)

※ ISOLATION NAME : 격리 수준

※ readonly : 읽기 전용 속성으로, 트랜잭션의 시작일 경우에만 적용된다.

※ timeout_NNNN : 제한 시간을 초 단위로 timeout_ 뒤에 작성함. 트랜잭션의 시작일 경우에만 적용된다.

※ -Exception : 한 개 이상을 등록할 수 있으며, 체크 예외 중에서 rollback시킬 예외들을 넣는다.

※ +Exception : 한 개 이상을 등록할 수 있으며, 런타임 예외지만 commit시킬 예외들을 넣는다.

 

사용 예시

<bean id="transactionAdvice" class="org.springframework.transaction.interceptor.TransactionInterceptor">
    <property name="transactionManager" ref="transactionManager"/>
    <property name="transactionAttributes">
        <props>
            <prop key="get*">PROPAGATION_REQUIRED,readOnly,timeout_20</prop>
            <prop key="up*">PROPAGATION_REQUIRES_NEW,ISOLATION_SERIALIZABLE</prop>
            <prop key="*">PROPAGATION_REQUIRED</prop> <!--개발 초기에 *(default)를 하나 두고 단계적으로 추가해나가는 방식이 좋다.-->
        </props>
    </property>
</bean>

※ 메소드가 여러 패턴과 일치한다면 가장 정확히 일치하는 패턴에 적용된다.

 

tx 네임 스페이스 사용

- <tx:advice> 태그를 사용하여 편리하게 TransactionInterceptor 타입의 Bean을 등록할 수도 있다.

- 스키마는 다음과 같이 추가한다.

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                            https://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.springframework.org/schema/aop
                            http://www.springframework.org/schema/aop/spring-aop.xsd
                            http://www.springframework.org/schema/tx
                            http://www.springframework.org/schema/tx/spring-tx.xsd">

      ...

- tx 네임스페이스를 사용하는 경우에는 트랜잭션의 속성들을 하나의 문자열로 작성하지 않고, 각각의 속성에 나눠서 작성한다. 예를 들어, <tx:method> 태그의 속성에는 name, propagation, read-only, timeout, isolation, rollback-for, no-rollback-for가 있다.

※ <tx:advice>, <tx:method> 사용법과 예시 참고 : https://snoopy81.tistory.com/332

 

 

 

8. @Transactional

- Fallback 정책을 통해서, 특정 트랜잭션 속성을 적용할 대상을 매우 유연하게 선정할 수 있도록 하는 어노테이션이다.

 

1) Fallback 정책

- 일반적으로 어노테이션을 사용하는 메소드 선정 방식은 유연하고 구체적인 속성 제어가 가능하지만, 동일한 속성 정보를 가진 어노테이션을 메소드마다 반복적으로 부여해야될 수도 있다고 언급했다. 이러한 문제를 해결하기 위한 대체 정책을 의미한다.

- @Transactional 어노테이션의 작성 위치에 따른 설정의 우선순위를 다르게 함으로써 매우 유연한 제어를 가능하게 해준다.

타깃 메소드 > 타깃 클래스 > 선언 메소드 > 선언 타입

※ 여기에서 선언은 인터페이스처럼 선언만 하고 구현하지 않은 대상을 뜻한다.

- 일반적으로 @Transactional의 적용 대상은 인터페이스가 정의한 메소드이지만, 인터페이스를 사용하는 프록시 AOP가 아닌 방식으로 트랜잭션을 적용하게 된다면 타깃 클래스에 @Transaction을 작성하는 것이 안전하다. 왜냐하면 그 외의 경우에는 인터페이스의 @Transactional 어노테이션이 구현 클래스로 전달되지 않기 때문이다.

 

 

2) @Transaction을 사용하기 위한 설정

- 트랜잭션 AOP와 관련된 인프라 bean들을 등록하기 위한 설정이다.

- advisor, advice, pointcut 그리고 어노테이션을 이용하는 트랜잭션의 속성 정보를 등록한다.

 

(1) xml 방식

- 트랜잭션 매니저 bean의 이름이 "transactionManager"라면 transaction-manager 속성을 생략해도 된다.

<tx:annotation-driven transaction-manager="myTxManager" />

※ Proxy(기본) 대신 Aspectj를 이용하여 트랜잭션을 적용할 경우에는 이 태그에 mode="aspectj" 속성을 추가하면 된다.

※ 인터페이스를 구현하지 않은 클래스에 프록시를 적용해야 할 경우에는 이 태그에 proxy-target-class="true" 속성을 추가하면 된다.

 

(2) Annotation 방식

- @Configuration 클래스에 @EnableTransactionManagement 어노테이션을 추가해주면 된다.

@Configuration
@EnableTransactionManagement
public class AppConfig {
    ...
}

※ xml 방식에서의 <tx:annotation-driven> 태그의 mode, proxy-target-class 속성은 각각 @EnableTransactionManagement 어노테이션의 mode, proxyTargetClass 엘리먼트와 대응된다.

Proxy(기본) 대신 Aspectj를 이용하여 트랜잭션을 적용할 경우에는 이 어노테이션에 mode=AdviceMode.ASPECTJ 속성을 추가하면 된다.

- 트랜잭션 매니저는 PlatformTransactionManager 타입으로 등록된 bean이 자동으로 적용되기 때문에 bean의 이름은 보통 신경쓰지 않아도 된다. 하지만, 명시적으로 트랜잭션 매니저를 지정해야 한다면 TransactionManagementConfigurer 인터페이스를 구현해서 annotationDrivenTransactionManager() 메소드에 지정하면 된다.

@Configuration
@EnableTransactionManagement
public class AppConfig implements TransactionManagementConfigurer {
    @Bean PlatformTransactionManager myTxManager() {
        ...
    }
    
    @Override
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return myTxManager();
    }
}

 

 

3) @Transaction의 구조 및 속성

- @Transaction 어노테이션을 사용할 때 설정할 수 있는 속성들이다.

@Target({ElementType.TYPE, ElementType.METHOD}) // 어노테이션 사용대상 지정
@Retention(RetentionPolicy.RUNTIME) // 어노테이션의 정보유지 기간 
@Inherited
@Documented
public @interface Transactional {

	@AliasFor("transactionManager")
	String value() default "";
    
	@AliasFor("value")
	String transactionManager() default "";
	
	String[] label() default {};
	
	Propagation propagation() default Propagation.REQUIRED;

	Isolation isolation() default Isolation.DEFAULT;

	int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

	String timeoutString() default "";

	boolean readOnly() default false;

	Class<? extends Throwable>[] rollbackFor() default {};

	String[] rollbackForClassName() default {};

	Class<? extends Throwable>[] noRollbackFor() default {};

	String[] noRollbackForClassName() default {};
}

- 속성에 대한 설명은 https://kimcoder.tistory.com/477을 참고하면 된다.

 

 

4) @Transaction 적용 예시

- @Transaction의 속성들 중에서 readOnly 속성을 사용한 예시다. @Transactional의 readOnly 속성은 기본값이 false이므로 add, update, deleteAll 메소드에는 readOnly가 false로 적용되고, get, getAll 메소드에는 readOnly가 true로 적용된다.

@Transactional
public interface UserService {
    void add(User user);
    void update(User user);
    void deleteAll();
    
    @Transactional(readOnly=true)
    User get(String id);
    
    @Transactional(readOnly=true)
    List<User> getAll();
}

 

 

5) 테스트에서의 @Transactional

- 테스트에 적용된 @Transactional은 기본적으로 트랜잭션을 강제로 rollback시키도록 설정되어 있다.

- 테스트에서 강제 rollback을 원하지 않고 그대로 commit하고 싶다면 다음과 같이 @Rollback 어노테이션을 사용해서 롤백이 적용되지 않도록 설정해주면 된다.

@Test
@Transactional
@Rollback(false) // 기본값은 true이기 때문에 @Rollback만 작성하면 rollback이 적용된다.
public void testMethod() {...}

 

 

 

● 참고 자료 : 토비의 스프링 3.1

● 트랜잭션 관련 참고할만한 자료 : https://codevang.tistory.com/264

반응형

댓글