김영한님의 스프링 DB 2편 - 데이터 접근 활용 기술을 듣고 정리한 내용입니다.
PlatformTransactionManager 인터페이스
package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
트랜잭션은 트랜잭션 시작(획득), 커밋, 롤백으로 추상화 가능하다.
서비스 계층은 스프링 트랜잭션 추상화 인터페이스에 의존한다.
스프링 트랜잭션 추상화인 PlatformTransactionManager가 있다.
스프링은 해당 인터페이스를 제공함과 동시에
자주 사용하는 접근 기술에 대한 트랜잭션 매니저의 구현체도 제공한다.
JdbcTemplate, MyBatis 를 사용하면 DataSourceTransactionManager (JdbcTransactionManager)을
스프링 빈으로 등록하고
JPA를 사용하면 JpaTransactionManager을 스프링 빈으로 등록한다.
트랜잭션 사용 방식
- 선언적 트랜잭션 관리
- 프로그래밍 방식 트랜잭션 관리
선언적 트랜잭션 관리
@Transactional 어노테이션을 선언하여 트랜잭션을 적용하는것을 뜻한다.
프로그래밍 방식의 트랜잭션 관리
트랜잭션 매니저, 트랜잭션 템플릿을 사용하여 트랜잭션 코드를 직접 작성하는 것
AopUtils.isAopProxy()
선언적 트랜잭션에서 스프링 트랜잭션은 AOP를 기반으로 동작함
클래스 이름을 출력해보면 basicService$$EnhancerBySpringCGLIB... 라고 프록시 클래스의 이름이 출력됨
스프링의 프록시
프록시란
클라이언트로부터 타겟을 대신해서 요청을 받는 대리인
실제 오브젝트인 타겟은 프록시를 통해 최종적으로 요청받고 처리함
타겟은 자신의 기능에만 집중하고 부가기능은 프록시에 위임함
스프링 AOP는 런타임에 프록시 인스턴스가 동적으로 변경되는 다이나믹 프록시 기법으로 구현됨.
JDK Dynamic Proxy
- JDK에서 지원하는 프록시 생성 방법
- Invocation Handler를 재정의한 invoke를 구현하여 부가기능 수행
- Reflection API 사용
- 인터페이스를 통해서만 프록시 생성 가능
JDK Dynamic Proxy를 사용하여 Proxy 객체를 생성하는 방법
public class Proxy implements java.io.Serializable {
...
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader, //클래스 로더
Class<?>[] interfaces, //타깃 인터페이스
InvocationHandler h //타깃 정보 포함된 Handler
) {
Objects.requireNonNull(h);
@SuppressWarnings("removal")
final Class<?> caller = System.getSecurityManager() == null
? null
: Reflection.getCallerClass();
/*
* Look up or generate the designated proxy class and its constructor.
*/
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
return newProxyInstance(caller, cons, h);
}
...
}
단순히 리플랙션의 Proxy 클래스의 newProxyInstance 메소드를 사용하면된다.
1. 타깃의 인터페이스를 자체적인 검증 로직을 통해 ProcyFactory에 의해 타깃의 인터페이스를 상속한 Proxy 객체생성
2. Proxy 객체에 InvocationHandler를 포함시켜 하나의 객체로 반환
인터페이스를 기준으로 Proxy객체를 생성한다.
구현체는 인터페이스를 상속받아야 한다.
@Autowired를 통해 생성된 ProxyBean 사용하려면 인터페이스의 타입으로 지정 필요
CodeGenratorLibrary(CGLIB)
- 상속을 기반으로 프록시 생성
- final이 붙으면 오버라이딩이 불가능하므로 CGLIB프록시가 작동하지 않는다.
- Enhancer의존성을 추가해야 한다.
- 3.2 version Spring core 패키지에 포함되어서 의존성 추가하지 않아도 된다.
- Default 생성자가 필요하다.
- 4.0 version Objensis 라이브러리를 포함하면서 필요 없어졌다.
- 타겟의 생성자를 두 번 호출한다.
- 4.0 version Objensis 라이브러리를 포함하면서 한 번만 호출된다.
- Spring 4.3 & spring boot 1.4부터 default로 CGLIB을 사용하게 되었다.
CGLib로 Proxy 구성하는 방법 ( 링크 )
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PersonService.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
if (method.getDeclaringClass() != Object.class && method.getReturnType() == String.class) {
return "Hello Tom!";
} else {
return proxy.invokeSuper(obj, args);
}
});
PersonService proxy = (PersonService) enhancer.create();
reflection api는 C코드로 실행한다. 그래서 느리다.
CGLIB을 사용할 때의 FastClass는 JVM 내에서 직접 메서드를 호출하는 바이트 코드를 만든다.
https://www.javacodegeeks.com/2013/12/cglib-the-missing-manual.html
Spring 은 CGLIB 와 Dynamic Proxy 모두 사용하고있으며 어떤걸 사용할지는 조건에 따라 다르다.
스프링의 @Transactional
규칙
1. 우선순위 규칙
2. 클래스에 적용 시 메서드는 자동 적용
프록시 방식의 AOP 한계
@Transactional을 사용하는 트랜잭션 AOP는 프록시를 사용한다.
프록시를 사용하면 메서드 내부 호출에 프록시를 적용할 수 있다.
가장 단순한 방법은 internal() 메서드를 별도의 클래스로 분리하는 것.
트랜잭션 AOP 주의사항
스프링 초기화 시점에는 트랜잭션 AOP가 적용되지 않을 수 있다.
확실한 대안은 ApplicationReadyEvent 이벤트를 사용하는 것이다.
@EventListener(value = ApplicationReadyEvent.class)
@Transactional
public void init2() {
log.info("Hello init ApplicationReadyEvent");
}
해당 이벤트는 트랜잭션 AOP를 포함한 스프링이 컨테이너가 완전히 생성되고 난 다음
이벤트가 붙은 메서드를 호출해준다.
트랜잭션 옵션
@Transactional - 코드
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Reflective
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;
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
value, transactionManager
- 트랜잭션 사용 시 트랜잭션 매니저 주입받아 사용. 트랜잭션 매니저가 둘 이상이라면 트랜잭션 매니저 이름 지정하여 구분
public class TxService {
@Transactional("memberTxManager")
public void member() {...}
@Transactional("orderTxManager")
public void order() {...}
}
rollbackFor
- 예외 발생 시 스프링 트랜잭션의 기본정책
언체크 예외인 RuntimeException, Error와 그 하위 예외 발생 시 롤백
체크 예외인 Exception과 하위예외 커밋
@Transactional(rollbackFor = Exception.class)
noRollbackFor
rollbackFor과 반대. 어떤 예외 발생 시 롤백하면 안되는지 지정한다.
propagation
트랜잭션 전파 관련
isolation
트랜잭션 격리수준 지정
DEFAULT | 데이터베이스에서 설정한 격리 수준을 따른다. |
READ_UNCOMMITTED | 커밋되지 않은 읽기 |
READ_COMMITTED | 커밋된 읽기 |
REPEATABLE_READ | 반복 가능한 읽기 |
SERIALIZABLE | 직렬화 가능 |
timeout
트랜잭션 수행 시간에 대한 타임아웃 초단위 지정
label
트랜잭션 어노테이션에 있는 값을 직접 읽어 어떤 동작 하고싶을 경우 사용
readOnly
트랜잭션은 기본적으로 읽기쓰기 가능한 트랜잭션이 생성된다.
readOnly=true 일 경우 읽기전용 트랜잭션 생성
이 경우 등록,수정,삭제 안되고 읽기 기능만 작동
드라이버에 따라 성능 최적화발생 가능
트랜잭션 전파
전파 범위 종류
- REQUIRED(default)
- REQUIRES_NEW
- MANDATORY
- SUPPORTS
- NESTED
- NEVER
REQUIRED
default 속성이다.
Required는 부모 트랜잭션이 존재한다면 부모 트랜잭션에 합류한다. 그렇지 않다면 새로운 트랜잭션을 만든다.
중간에 자식 / 부모에서 예외가 발생한다면 자식과 부모 모두 rollback 한다.
REQUIRES_NEW
무조건 새로운 트랜잭션을 만든다.
nested한 방식으로 메소드 호출이 이루어지더라도 rollback은 각각 이루어 진다.
MANDATORY
무조건 부모 트랜잭션에 합류시킨다.
부모 트랜잭션이 존재하지 않는다면 예외를 발생시킨다.
SUPPORTS
메소드가 트랜잭션을 필요로 하지는 않지만, 진행 중인 트랜잭션이 존재하면 트랜잭션을 사용한다.
진행 중인 트랜잭션이 존재하지 않으면 트랜잭션이 적용되지 않은 채로 메소드가 정상 동작한다.
NESTED
부모 트랜잭션이 존재하면 부모 트랜잭션에 중첩시키고, 부모 트랜잭션이 존재하지 않으면 새로운 트랜잭션을 생성한다.
부모 트랜잭션에 예외가 발생하면 자식 트랜잭션도 rollback한다.
여기까지는 Required와 동일하다.
하지만 자식 트랜잭션에 예외가 발생하더라도 부모 트랜잭션은 rollback하지 않는다.
NEVER
메소드가 트랜잭션을 필요로 하지 않는다. 만약 진행 중인 트랜잭션이 존재하면 예외가 발생한다.
물리 트랜잭션과 논리 트랜잭션
- 물리 트랜잭션: 실제 DB에 적용되는 트랜잭션
- 논리 트랜잭션: TransactionManager를 통해 트랜잭션을 사용하는 단위이며 물리 트랜잭션 내 모든 논리 트랜잭션이 커밋되어야 물리 트랜잭션이 커밋이 됨, 논리 트랜잭션이 하나라도 롤백되면 물리 트랜잭션은 롤백
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard
'스터디 > 2023_스프링부트' 카테고리의 다른 글
[study] 스프링 DB 2편 - 8. 활용방안 (0) | 2023.08.28 |
---|---|
[study] 스프링 DB 2편 - 7. Querydsl (0) | 2023.08.28 |
[study] 스프링 DB 2편 - 6. 스프링 데이터 JPA (0) | 2023.08.28 |
[study] 스프링 DB 2편 - 5. JPA (0) | 2023.08.28 |
[study] 스프링 DB 2편 - 4. MyBatis (0) | 2023.08.28 |