대부분의 트랜잭션 적용은 이전에 하던 설정으로도 가능하긴 하다.
하지만 우리 모두가 알고 있는 것처럼 대부분의 트랜잭션은 어노테이션으로 제어한다.
가장 마지막 단계인 @Transactional에 대해 알아보도록 하자.
트랜잭션 어노테이션
우선 한 번 보도록 하자.
@Inherited
@InterceptorBinding
//어노테이션을 사용할 대상
//메서드와 타입처럼 한 개 이상의 대상을 지정한다.
@Target({ElementType.TYPE, ElementType.METHOD})
//어노테이션 정보가 언제까지 유지되는지를 결정한다.
//런타임 때 어노테이션 정보를 리플렉션을 통해서 얻는다.
@Retention(RetentionPolicy.RUNTIME)
public @interface Transactional {
TxType value() default Transactional.TxType.REQUIRED;
@Nonbinding
Class[] rollbackOn() default {};
@Nonbinding
Class[] dontRollbackOn() default {};
public static enum TxType {
REQUIRED,
REQUIRES_NEW,
MANDATORY,
SUPPORTS,
NOT_SUPPORTED,
NEVER;
private TxType() {
}
}
}
@Transactional의 대상은 메서드와 타입이다.
@Transactional을 사용하면 적용된 모든 오브젝트를 자동으로 타깃 오브젝트로 인식한다.
우선 AnnotationTransactionAttributeSource가 @Transactional 어노테이션에서 트랜잭션 속성을 가져온다.
또한 포인트컷도 @Transactional을 통해 포인트컷의 선정 대상이 되도록 한다.
이렇게 AnnotationTransactionAttributeSource를 통해 포인트컷의 선정대상이 되도록 하며, 트랜잭션의 속성을 가져올 수 있다.
스프링은 @Transactional을 다음과 같은 방식으로 적용한다.
- 우선 메서드에 적용된 @Transactional을 찾아본다.
- 메서드에 적용되어 있지 않으면, 타깃 클래스에 부여된 @Transactional을 찾는다. 클래스 레벨에 @Transactional이 적용되어 있다면, 해당 클래스 레벨의 @Transactional을 적용한다.
- 클래스 레벨에도 없다면 선언 메서드(인터페이스의 메서드)의 @Transactional을 적용한다.
- 이렇게 1~3에도 없다면 선언 타입(인터페이스)의 @Transactional을 적용하며, 여기에도 없다면 적용하지 않는다.
그렇기에 다음과 같이 정의가 되어 있다면 해당 순서대로 찾고 @Transactional을 적용하는 것이다.
//4
public interface Seungkyu{
//3
void method1();
}
//2
public class SeungkyuImpl implements Seungkyu{
//1
void method1();
}
그렇기에 클래스에 공통적으로 트랜잭션을 적용하고, 커스텀해야 하는 메서드가 있다면 해당 메서드에만 개별적으로 트랜잭션을 적용하는 방법도 있다.
인터페이스에 적용하면 클래스가 변경되어도 트랜잭션이 적용될 것 같지만, 스프링을 사용하면 인터페이스에 정의된 @Transactional은 무시될 수 있기 때문이다.
트랜잭션 어노테이션 적용
어노테이션으로 트랜잭션을 적용하게 된다면, 클래스에 공통 트랜잭션을 설정하고 달라지는 트랜잭션에 대해서는 하나하나 어노테이션을 추가해줘야 한다는 단점이 있다.
하지만 기존의 트랜잭션보다 설정이 쉽고 직관적이기 때문에 이 방법을 선호하는 사람이 더 많다.
package seungkyu;
import lombok.AllArgsConstructor;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@AllArgsConstructor
@Transactional
public class UserServiceImpl implements UserService{
private final UserDao userDao;
protected MailSender mailSender;
public void upgradeLevels() {
List<User> users = userDao.getAll();
for (User user : users) {
if (user.isUpgradeLevel()) upgradeLevel(user);
}
}
public void add(User user) {
if(user.getLevel() == null)
user.setLevel(Level.BRONZE);
userDao.add(user);
}
@Transactional(readOnly = true)
public User get(String id) {
return userDao.get(id);
}
@Transactional(readOnly = true)
public List<User> getAll() {
return userDao.getAll();
}
public void deleteAll() {
userDao.deleteAll();
}
public void update(User user) {
userDao.update(user);
}
}
이렇게 클래스 전체에 @Transactional을 설정하고, 개별 메서드에 다른 설정을 거는 방식으로도 간편하게 트랜잭션을 적용할 수 있다.
'Spring > 토비의 스프링' 카테고리의 다른 글
[토비의 스프링] 6.6 트랜잭션 속성 (2) | 2025.06.26 |
---|---|
[토비의 스프링] 6.5 스프링 AOP (1) | 2025.06.25 |
[토비의 스프링] 6.4 스프링의 프록시 팩토리 빈 (0) | 2025.06.19 |
[토비의 스프링] 6.3 다이내믹 프록시와 팩토리 빈 (3) | 2025.06.17 |
[토비의 스프링] 6.2 고립된 단위 테스트 (1) | 2025.06.16 |