스프링 DB 2편(데이터 접근 활용 기술)

Ch09. 스프링 트랜잭션 이해 - 트랜잭션 옵션 소개

webmaster 2022. 7. 6. 15:22
728x90
/**
 * Describes a transaction attribute on an individual method or on a class.
 *
 * <p>When this annotation is declared at the class level, it applies as a default
 * to all methods of the declaring class and its subclasses. Note that it does not
 * apply to ancestor classes up the class hierarchy; inherited methods need to be
 * locally redeclared in order to participate in a subclass-level annotation. For
 * details on method visibility constraints, consult the
 * Transaction Management</a>
 * section of the reference manual.
 *
 * <p>This annotation is generally directly comparable to Spring's
 * {@link org.springframework.transaction.interceptor.RuleBasedTransactionAttribute}
 * class, and in fact {@link AnnotationTransactionAttributeSource} will directly
 * convert this annotation's attributes to properties in {@code RuleBasedTransactionAttribute},
 * so that Spring's transaction support code does not have to know about annotations.
 *
 * <h3>Attribute Semantics</h3>
 *
 * <p>If no custom rollback rules are configured in this annotation, the transaction
 * will roll back on {@link RuntimeException} and {@link Error} but not on checked
 * exceptions.
 *
 * <p>Rollback rules determine if a transaction should be rolled back when a given
 * exception is thrown, and the rules are based on patterns. A pattern can be a
 * fully qualified class name or a substring of a fully qualified class name for
 * an exception type (which must be a subclass of {@code Throwable}), with no
 * wildcard support at present. For example, a value of
 * {@code "javax.servlet.ServletException"} or {@code "ServletException"} will
 * match {@code javax.servlet.ServletException} and its subclasses.
 *
 * <p>Rollback rules may be configured via {@link #rollbackFor}/{@link #noRollbackFor}
 * and {@link #rollbackForClassName}/{@link #noRollbackForClassName}, which allow
 * patterns to be specified as {@link Class} references or {@linkplain String
 * strings}, respectively. When an exception type is specified as a class reference
 * its fully qualified name will be used as the pattern. Consequently,
 * {@code @Transactional(rollbackFor = example.CustomException.class)} is equivalent
 * to {@code @Transactional(rollbackForClassName = "example.CustomException")}.
 *
 * <p><strong>WARNING:</strong> You must carefully consider how specific the pattern
 * is and whether to include package information (which isn't mandatory). For example,
 * {@code "Exception"} will match nearly anything and will probably hide other
 * rules. {@code "java.lang.Exception"} would be correct if {@code "Exception"}
 * were meant to define a rule for all checked exceptions. With more unique
 * exception names such as {@code "BaseBusinessException"} there is likely no
 * need to use the fully qualified class name for the exception pattern. Furthermore,
 * rollback rules may result in unintentional matches for similarly named exceptions
 * and nested classes. This is due to the fact that a thrown exception is considered
 * to be a match for a given rollback rule if the name of thrown exception contains
 * the exception pattern configured for the rollback rule. For example, given a
 * rule configured to match on {@code com.example.CustomException}, that rule
 * would match against an exception named
 * {@code com.example.CustomExceptionV2} (an exception in the same package as
 * {@code CustomException} but with an additional suffix) or an exception named
 * {@code com.example.CustomException$AnotherException}
 * (an exception declared as a nested class in {@code CustomException}).
 *
 * <p>For specific information about the semantics of other attributes in this
 * annotation, consult the {@link org.springframework.transaction.TransactionDefinition}
 * and {@link org.springframework.transaction.interceptor.TransactionAttribute} javadocs.
 *
 * <h3>Transaction Management</h3>
 *
 * <p>This annotation commonly works with thread-bound transactions managed by a
 * {@link org.springframework.transaction.PlatformTransactionManager}, exposing a
 * transaction to all data access operations within the current execution thread.
 * <b>Note: This does NOT propagate to newly started threads within the method.</b>
 *
 * <p>Alternatively, this annotation may demarcate a reactive transaction managed
 * by a {@link org.springframework.transaction.ReactiveTransactionManager} which
 * uses the Reactor context instead of thread-local variables. As a consequence,
 * all participating data access operations need to execute within the same
 * Reactor context in the same reactive pipeline.
 *
 * @author Colin Sampaleanu
 * @author Juergen Hoeller
 * @author Sam Brannen
 * @author Mark Paluch
 * @since 1.2
 * @see org.springframework.transaction.interceptor.TransactionAttribute
 * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute
 * @see org.springframework.transaction.interceptor.RuleBasedTransactionAttribute
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

   /**
    * Alias for {@link #transactionManager}.
    * @see #transactionManager
    */
   @AliasFor("transactionManager")
   String value() default "";

   /**
    * A <em>qualifier</em> value for the specified transaction.
    * <p>May be used to determine the target transaction manager, matching the
    * qualifier value (or the bean name) of a specific
    * {@link org.springframework.transaction.TransactionManager TransactionManager}
    * bean definition.
    * @since 4.2
    * @see #value
    * @see org.springframework.transaction.PlatformTransactionManager
    * @see org.springframework.transaction.ReactiveTransactionManager
    */
   @AliasFor("value")
   String transactionManager() default "";

   /**
    * Defines zero (0) or more transaction labels.
    * <p>Labels may be used to describe a transaction, and they can be evaluated
    * by individual transaction managers. Labels may serve a solely descriptive
    * purpose or map to pre-defined transaction manager-specific options.
    * <p>See the documentation of the actual transaction manager implementation
    * for details on how it evaluates transaction labels.
    * @since 5.3
    * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#getLabels()
    */
   String[] label() default {};

   /**
    * The transaction propagation type.
    * <p>Defaults to {@link Propagation#REQUIRED}.
    * @see org.springframework.transaction.interceptor.TransactionAttribute#getPropagationBehavior()
    */
   Propagation propagation() default Propagation.REQUIRED;

   /**
    * The transaction isolation level.
    * <p>Defaults to {@link Isolation#DEFAULT}.
    * <p>Exclusively designed for use with {@link Propagation#REQUIRED} or
    * {@link Propagation#REQUIRES_NEW} since it only applies to newly started
    * transactions. Consider switching the "validateExistingTransactions" flag to
    * "true" on your transaction manager if you'd like isolation level declarations
    * to get rejected when participating in an existing transaction with a different
    * isolation level.
    * @see org.springframework.transaction.interceptor.TransactionAttribute#getIsolationLevel()
    * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setValidateExistingTransaction
    */
   Isolation isolation() default Isolation.DEFAULT;

   /**
    * The timeout for this transaction (in seconds).
    * <p>Defaults to the default timeout of the underlying transaction system.
    * <p>Exclusively designed for use with {@link Propagation#REQUIRED} or
    * {@link Propagation#REQUIRES_NEW} since it only applies to newly started
    * transactions.
    * @return the timeout in seconds
    * @see org.springframework.transaction.interceptor.TransactionAttribute#getTimeout()
    */
   int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

   /**
    * The timeout for this transaction (in seconds).
    * <p>Defaults to the default timeout of the underlying transaction system.
    * <p>Exclusively designed for use with {@link Propagation#REQUIRED} or
    * {@link Propagation#REQUIRES_NEW} since it only applies to newly started
    * transactions.
    * @return the timeout in seconds as a String value, e.g. a placeholder
    * @since 5.3
    * @see org.springframework.transaction.interceptor.TransactionAttribute#getTimeout()
    */
   String timeoutString() default "";

   /**
    * A boolean flag that can be set to {@code true} if the transaction is
    * effectively read-only, allowing for corresponding optimizations at runtime.
    * <p>Defaults to {@code false}.
    * <p>This just serves as a hint for the actual transaction subsystem;
    * it will <i>not necessarily</i> cause failure of write access attempts.
    * A transaction manager which cannot interpret the read-only hint will
    * <i>not</i> throw an exception when asked for a read-only transaction
    * but rather silently ignore the hint.
    * @see org.springframework.transaction.interceptor.TransactionAttribute#isReadOnly()
    * @see org.springframework.transaction.support.TransactionSynchronizationManager#isCurrentTransactionReadOnly()
    */
   boolean readOnly() default false;

   /**
    * Defines zero (0) or more exception {@linkplain Class classes}, which must be
    * subclasses of {@link Throwable}, indicating which exception types must cause
    * a transaction rollback.
    * <p>By default, a transaction will be rolled back on {@link RuntimeException}
    * and {@link Error} but not on checked exceptions (business exceptions). See
    * {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)}
    * for a detailed explanation.
    * <p>This is the preferred way to construct a rollback rule (in contrast to
    * {@link #rollbackForClassName}), matching the exception type, its subclasses,
    * and its nested classes. See the {@linkplain Transactional class-level javadocs}
    * for further details on rollback rule semantics and warnings regarding possible
    * unintentional matches.
    * @see #rollbackForClassName
    * @see org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class)
    * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
    */
   Class<? extends Throwable>[] rollbackFor() default {};

   /**
    * Defines zero (0) or more exception name patterns (for exceptions which must be a
    * subclass of {@link Throwable}), indicating which exception types must cause
    * a transaction rollback.
    * <p>See the {@linkplain Transactional class-level javadocs} for further details
    * on rollback rule semantics, patterns, and warnings regarding possible
    * unintentional matches.
    * @see #rollbackFor
    * @see org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(String)
    * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
    */
   String[] rollbackForClassName() default {};

   /**
    * Defines zero (0) or more exception {@link Class Classes}, which must be
    * subclasses of {@link Throwable}, indicating which exception types must
    * <b>not</b> cause a transaction rollback.
    * <p>This is the preferred way to construct a rollback rule (in contrast to
    * {@link #noRollbackForClassName}), matching the exception type, its subclasses,
    * and its nested classes. See the {@linkplain Transactional class-level javadocs}
    * for further details on rollback rule semantics and warnings regarding possible
    * unintentional matches.
    * @see #noRollbackForClassName
    * @see org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(Class)
    * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
    */
   Class<? extends Throwable>[] noRollbackFor() default {};

   /**
    * Defines zero (0) or more exception name patterns (for exceptions which must be a
    * subclass of {@link Throwable}) indicating which exception types must <b>not</b>
    * cause a transaction rollback.
    * <p>See the {@linkplain Transactional class-level javadocs} for further details
    * on rollback rule semantics, patterns, and warnings regarding possible
    * unintentional matches.
    * @see #noRollbackFor
    * @see org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(String)
    * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
    */
   String[] noRollbackForClassName() default {};

}

value, transactionManager

트랜잭션을 사용하려면 먼저 스프링 빈에 등록된 어떤 트랜잭션 매니저를 사용할지 알아야 한다.

생각해보면 코드로 직접 트랜잭션을 사용할 때 분명 트랜잭션 매니저를 주입받아서 사용했다. @Transactional에서도 트랜잭션 프록시가 사용할 트랜잭션 매니저를 지정해주어야 한다.

사용할 트랜잭션 매니저를 지정할 때는 value , transactionManager 둘 중 하나에 트랜잭션 매니저의 스프링 빈의 이름을 적어주면 된다.

이 값을 생략하면 기본으로 등록된 트랜잭션 매니저를 사용하기 때문에 대부분 생략한다.

그런데 사용하는 트랜잭션 매니저가 둘 이상이라면 다음과 같이 트랜잭션 매니저의 이름을 지정해서 구분하면 된다

public class TxService {
 	@Transactional("memberTxManager")
 	public void member() {...}
    
 	@Transactional("orderTxManager")
 	public void order() {...}
}

rollbackFor

예외 발생시 스프링 트랜잭션의 기본 정책은 다음과 같다.

  • 언체크 예외인 RuntimeException , Error와 그 하위 예외가 발생하면 롤백한다.
  • 체크 예외인 Exception 과 그 하위 예외들은 커밋한다.
@Transactional(rollbackFor = Exception.class)
  • 위 옵션을 쓰면 기본 정책에 추가로 어떤 예외가 발생할 때 롤백할지 지정할 수 있다
  • rollbackForClassName 도 있는데, rollbackFor 는 예외 클래스를 직접 지정하고, rollbackForClassName는 예외 이름을 문자로 넣으면 된다.

noRollbackFor

rollbackFor 와 반대이다. 기본 정책에 추가로 어떤 예외가 발생했을 때 롤백하면 안 되는지 지정할 수 있다.

예외 이름을 문자로 넣을 수 있는 noRollbackForClassName 도 있다.

propagation

트랜잭션 전파에 대한 옵션(뒤에서 자세한 설명)

isolation

트랜잭션 격리 수준을 지정할 수 있다. 기본 값은 데이터베이스에서 설정한 트랜잭션 격리 수준을 사용하는 DEFAULT이다.

대부분 데이터베이스에서 설정한 기준을 따른다. 애플리케이션 개발자가 트랜잭션 격리 수준을 직접 지정하는 경우는 드물다

  • DEFAULT : 데이터베이스에서 설정한 격리 수준을 따른다.
  • READ_UNCOMMITTED : 커밋되지 않은 읽기
  • READ_COMMITTED : 커밋된 읽기
  • REPEATABLE_READ : 반복 가능한 읽기
  • SERIALIZABLE : 직렬화 가능

timeout

트랜잭션 수행 시간에 대한 타임아웃을 초 단위로 지정한다. 기본 값은 트랜잭션 시스템의 타임아웃을 사용한다. 운영 환경에 따라 동작하는 경우도 있고 그렇지 않은 경우도 있기 때문에 꼭 확인하고 사용해야 한다.

timeoutString 도 있는데, 숫자 대신 문자 값으로 지정할 수 있다.

label

트랜잭션 애노테이션에 있는 값을 직접 읽어서 어떤 동작을 하고 싶을 때 사용할 수 있다. 일반적으로 사용하지 않는다

readOnly

트랜잭션은 기본적으로 읽기 쓰기가 모두 가능한 트랜잭션이 생성된다.

readOnly=true 옵션을 사용하면 읽기 전용 트랜잭션이 생성된다. 이 경우 등록, 수정, 삭제가 안되고 읽기 기능만 작동한다. (드라이버나 데이터베이스에 따라 정상 동작하지 않는 경우도 있다.)

그리고 readOnly 옵션을 사용하면 읽기에서 다양한 성능 최적화가 발생할 수 있다

프레임워크

  • JdbcTemplate은 읽기 전용 트랜잭션 안에서 변경 기능을 실행하면 예외를 던진다.
  • JPA(하이버네이트)는 읽기 전용 트랜잭션의 경우 커밋 시점에 플러시를 호출하지 않는다. 읽기 전용이니 변경에 사용되는 플러시를 호출할 필요가 없다. 추가로 변경이 필요 없으니 변경 감지를 위한 스냅샷 객체도 생성하지 않는다. 이렇게 JPA에서는 다양한 최적화가 발생한다.
    • JPA 관련 내용은 JPA를 더 학습해야 이해할 수 있으므로 지금은 이런 것이 있다 정도만 알아두자.

JDBC 드라이버

  • 참고로 여기서 설명하는 내용들은 DB와 드라이버 버전에 따라서 다르게 동작하기 때문에 사전에 확인이 필요하다.
  • 읽기 전용 트랜잭션에서 변경 쿼리가 발생하면 예외를 던진다.
  • 읽기, 쓰기(마스터, 슬레이브) 데이터베이스를 구분해서 요청한다. 읽기 전용 트랜잭션의 경우 읽기 (슬레이브) 데이터베이스의 커넥션을 획득해서 사용한다.

데이터베이스

  • 데이터베이스에 따라 읽기 전용 트랜잭션의 경우 읽기만 하면 되므로, 내부에서 성능 최적화가 발생한다.
728x90