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
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
'Spring Series > Spring Framework' 카테고리의 다른 글
[Spring] 리소스 추상화 (0) | 2022.05.01 |
---|---|
[Spring] Static 멤버 클래스를 Bean으로 등록하기 (0) | 2022.04.14 |
[Spring] 트랜잭션 추상화 (0) | 2022.03.27 |
[Spring] 예외처리 전략 (0) | 2022.03.20 |
[Spring] Template/Callback 패턴 (0) | 2022.03.19 |
댓글