[F-Lab 66해빗 페이백 챌린지 ]

[F-Lab 모각코 페이백 26일차] 스프링 트랜잭션 (Spring Transaction) -2

everydeveloper 2023. 6. 13. 23:51

학습 목표

  • 스프링 트랜잭션 (Spring Transaction)

 

TIL

  • 스프링 트랜잭션 (Spring Transaction)

 

 

스프링 트랜잭션 (Spring Transaction)

Spring Framework는 데이터베이스 트랜잭션 관리를 위한 독특한 방식을 제공합니다. 

Spring에서의 트랜잭션 관리는 크게 두 가지 방법으로 나뉘어집니다: 선언적 트랜잭션 관리(Declarative Transaction Management)과 프로그래밍적 트랜잭션 관리(Programmatic Transaction Management).

1. 선언적 트랜잭션 관리: 이 방식은 비즈니스 로직 코드와 별개로 트랜잭션 관리를 수행합니다. 주로 어노테이션(`@Transactional`)을 사용하거나 XML 구성을 통해 트랜잭션의 경계를 정의하게 됩니다. 

2. 프로그래밍적 트랜잭션 관리: 이 방식은 개발자가 직접 코드를 통해 트랜잭션의 시작, 커밋, 롤백 등을 처리합니다. `TransactionTemplate` 또는 `PlatformTransactionManager`를 이용해 직접적으로 트랜잭션을 컨트롤합니다.

보통 선언적 트랜잭션 관리 방식이 코드의 복잡성을 줄이고, 보다 깔끔한 코드를 작성할 수 있게 돕기 때문에, 더 선호되는 방식입니다. 하지만 프로그래밍적 트랜잭션 관리는 트랜잭션의 세밀한 컨트롤이 필요할 경우 유용하게 사용될 수 있습니다. 

이러한 Spring의 트랜잭션 관리 기능을 사용하면 개발자는 데이터베이스 연결, 세션, 트랜잭션 관리 등의 저수준 작업에 대한 부담을 덜 수 있게 됩니다.

 

 

선언적 트랜잭션 관리

선언적 트랜잭션 관리는 Spring에서 제공하는 트랜잭션 관리 전략 중 하나로, 비즈니스 코드와 트랜잭션 관리를 분리하여 코드의 가독성과 유지 보수성을 향상시킵니다.

이 방식은 XML 구성 또는 `@Transactional` 어노테이션을 사용하여 트랜잭션의 범위를 지정하며, 이를 통해 어떤 메서드가 트랜잭션의 일부로 수행되어야 하는지 선언할 수 있습니다.

예를 들어, 아래는 `@Transactional` 어노테이션을 사용한 예입니다:

@Service
public class SomeService {

    @Autowired
    private SomeRepository someRepository;

    @Transactional
    public void someTransactionalMethod(SomeEntity someEntity) {
        someRepository.save(someEntity);
    }
}


위 코드에서 `someTransactionalMethod`는 `@Transactional` 어노테이션이 붙어 있으므로, 이 메서드에서 발생하는 모든 데이터베이스 연산은 하나의 트랜잭션으로 묶이게 됩니다. 만약 이 메서드에서 예외가 발생하면, 해당 트랜잭션은 롤백(취소)되어 데이터베이스의 상태는 메서드 호출 이전 상태로 되돌아갑니다.

또한 `@Transactional` 어노테이션은 속성을 설정할 수 있으며, 이를 통해 트랜잭션의 동작 방식을 세밀하게 조절할 수 있습니다. 예를 들어, `readOnly`, `isolation`, `propagation`, `timeout`, `rollbackFor` 등의 속성을 설정할 수 있습니다. 

선언적 트랜잭션 관리는 AOP(Aspect-Oriented Programming)를 이용하여 구현되며, 이를 통해 트랜잭션 코드가 비즈니스 로직 코드에 침투하지 않도록 합니다. 이는 코드의 깔끔함과 유지 보수성을 향상시키는 데 도움이 됩니다.

 

스프링 트랜잭션의 세부 설정

Spring의 `@Transactional` 어노테이션을 사용할 때, 다양한 속성들을 통해 트랜잭션의 세부 동작을 설정할 수 있습니다. 이러한 속성들에는 다음과 같은 것들이 있습니다:

1. `readOnly`: 이 속성이 `true`로 설정되면, 해당 트랜잭션은 읽기 전용으로 설정됩니다. 즉, 해당 트랜잭션에서는 데이터 변경 작업이 일어나지 않으며, 이는 성능 향상에 도움이 될 수 있습니다. 기본값은 `false`입니다.

2. `propagation`: 이 속성은 트랜잭션 전파 옵션을 설정합니다. `Propagation` 열거형의 값을 사용하며, 예를 들어, `Propagation.REQUIRED`은 현재 트랜잭션이 존재하면 그것을 사용하고, 그렇지 않으면 새 트랜잭션을 생성합니다. `Propagation.REQUIRES_NEW`는 항상 새로운 트랜잭션을 시작하고, 기존의 트랜잭션을 일시 중단합니다.

3. `isolation`: 이 속성은 트랜잭션의 격리 수준을 설정합니다. `Isolation` 열거형의 값을 사용하며, 예를 들어, `Isolation.READ_COMMITTED`는 다른 트랜잭션에서 커밋된 데이터만을 읽을 수 있음을 의미합니다.

4. `timeout`: 이 속성은 트랜잭션의 제한 시간을 설정합니다. 설정한 시간이 지나면, 트랜잭션은 자동으로 롤백됩니다.

5. `rollbackFor` 및 `noRollbackFor`: 이 두 속성을 사용하여 트랜잭션이 어떤 예외가 발생했을 때 롤백되어야 하는지, 롤백되지 않아야 하는지를 설정할 수 있습니다. 예를 들어, `rollbackFor = Exception.class`는 모든 예외가 발생하면 트랜잭션이 롤백되도록 설정합니다.

아래는 일부 속성을 사용한 `@Transactional` 어노테이션의 예입니다:

@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED, timeout = 60, rollbackFor = Exception.class)
public void someMethod() {
    // ...
}



위의 예제에서 `someMethod` 메서드는 읽기 전용 트랜잭션을 새로 생성하며, 커밋된 데이터만 읽을 수 있고, 60초가 지나면 트랜잭션이 자동으로 롤백됩니다. 또한, 모든 예외가 발생하면 트랜잭션이 롤백됩니다. 또한, 모든 예외가 발생하면 트랜잭션이 롤백됩니다.

이처럼 Spring의 `@Transactional` 어노테이션은 트랜잭션의 세부 동작을 설정하기 위한 다양한 속성들을 제공합니다. 이러한 속성들을 이해하고 적절하게 사용하면, 애플리케이션의 데이터 무결성을 보호하고 성능을 최적화하는 데 도움이 될 수 있습니다.

 

스프링 트랜잭션 전파 속성과 격리 수준의 의미

트랜잭션의 전파 속성(Propagation)과 격리 수준(Isolation)은 서로 다른 개념입니다. 

1. 트랜잭션 전파 속성(Propagation): 이 속성은 이미 진행 중인 트랜잭션에 대해 새로운 트랜잭션을 시작할 때 어떤 행동을 취할지를 결정합니다. 여기에는 몇 가지 옵션들이 있습니다:

    - REQUIRED: 이미 존재하는 트랜잭션이 있다면 그것을 사용하고, 없다면 새로운 트랜잭션을 시작합니다. 이는 기본값입니다.
    - REQUIRES_NEW: 항상 새로운 트랜잭션을 시작하고, 기존의 트랜잭션(있을 경우)은 일시 중단됩니다.
    - SUPPORTS: 이미 존재하는 트랜잭션이 있다면 그것을 사용하고, 없다면 트랜잭션 없이 실행됩니다.
    - NOT_SUPPORTED: 트랜잭션 없이 실행됩니다. 이미 존재하는 트랜잭션이 있다면 일시 중단됩니다.
    - NEVER: 트랜잭션 없이 실행됩니다. 이미 존재하는 트랜잭션이 있다면 예외를 발생시킵니다.
    - MANDATORY: 반드시 이미 존재하는 트랜잭션 내에서 실행되어야 합니다. 없다면 예외를 발생시킵니다.
    - NESTED: 이미 존재하는 트랜잭션이 있다면 중첩 트랜잭션을 시작합니다. 없다면 REQUIRED와 같이 동작합니다.

2. 트랜잭션 격리 수준(Isolation): 이 속성은 동시에 여러 트랜잭션이 진행될 때, 한 트랜잭션이 다른 트랜잭션에서 변경하거나 추가한 데이터를 볼 수 있도록 할지를 결정합니다. 이를 통해 트랜잭션 병행 처리시 발생할 수 있는 문제들(더티 리드, 팬텀 리드 등)을 방지하고 데이터 무결성을 유지하는 데 도움이 됩니다. 여기에는 몇 가지 옵션들이 있습니다:

    - READ_UNCOMMITTED: 커밋되지 않은 변경 내용이라도 다른 트랜잭션에서 볼 수 있습니다.
    - READ_COMMITTED: 다른 트랜잭션에서 커밋한 변경 내용만 볼 수 있습니다. 이는 기본값입니다.

    - REPEATABLE_READ: 트랜잭션이 시작된 시점의 데이터를 계속해서 볼 수 있으며, 다른 트랜잭션에서 이후에 변경하거나 추가한 데이터는 보지 못합니다. 이렇게 하면 한 트랜잭션 내에서 같은 데이터를 여러 번 조회했을 때 항상 일관된 결과를 얻을 수 있습니다.

- SERIALIZABLE: 트랜잭션이 완전히 격리되어, 한 번에 하나의 트랜잭션만 데이터를 변경할 수 있습니다. 이는 가장 엄격한 격리 수준입니다만, 동시성이 크게 떨어져 성능 저하의 원인이 될 수 있습니다.

이처럼 트랜잭션 전파 속성과 격리 수준은 서로 다른 관점에서 트랜잭션의 동작을 제어합니다. 전파 속성은 트랜잭션이 어떻게 시작되고 중첩되는지를 결정하며, 격리 수준은 한 트랜잭션에서 다른 트랜잭션의 변경 내용을 어떻게 볼 수 있는지를 결정합니다. 이러한 설정들을 이해하고 적절하게 조합하면, 동시성과 데이터 무결성 사이에서 균형을 잡는 데 도움이 됩니다.

 

외부 환경과 스프링 트랜잭션

트랜잭션은 일반적으로 데이터베이스와 같은 영구적인 데이터 저장소와 연관되어 있지만, 데이터베이스 외의 환경에서도 중요한 개념입니다. 실제로는 여러 동작이 원자적으로(즉, 모든 동작이 성공적으로 완료되거나 아무 것도 실행되지 않거나) 완료되어야 하는 모든 상황에서 트랜잭션이 유용합니다. 

예를 들어, 메세지 큐를 사용하는 경우에도 트랜잭션을 사용할 수 있습니다. 메세지를 큐에 넣는 동작과 데이터베이스에서 레코드를 업데이트하는 동작을 하나의 트랜잭션으로 묶을 수 있습니다. 만약 둘 중 하나의 동작이 실패한다면, 모든 동작이 롤백되어 데이터의 일관성이 유지됩니다.

또한, 스프링 프레임워크에서는 '분산 트랜잭션'을 지원합니다. 이는 여러 자원(데이터베이스, 메세지 큐, 파일 시스템 등)을 하나의 트랜잭션으로 묶는 기능입니다. 이를 위해 Java Transaction API(JTA)와 같은 표준을 이용하거나, XA 프로토콜을 지원하는 미들웨어를 사용할 수 있습니다.

따라서 답변을 요약하면, 트랜잭션은 외부 환경과의 상호작용이 있는 경우뿐만 아니라 여러 동작을 원자적으로 실행해야 하는 모든 경우에 유용합니다. 이는 트랜잭션이 데이터의 일관성과 무결성을 유지하는데 도움이 되기 때문입니다.

 

스프링 트랜잭션은 프록시 패턴으로 구현이 되어 잇나?

네, 스프링의 트랜잭션 관리는 프록시 패턴을 사용하여 구현됩니다. 스프링은 AOP(Aspect-Oriented Programming)를 기반으로 트랜잭션 관리를 수행하며, 이를 위해 프록시 패턴을 활용합니다.

 

프록시 패턴은 기존의 객체에 대한 접근을 제어하기 위해 대리자(proxy) 객체를 사용하는 디자인 패턴입니다. 스프링의 트랜잭션 관리에서도 이와 유사한 방식을 적용합니다. 스프링은 트랜잭션 관리에 필요한 코드를 생성하기 위해 프록시 객체를 동적으로 생성하여 사용합니다.

 

스프링의 트랜잭션 프록시는 `@Transactional` 어노테이션이 적용된 메서드를 호출할 때, 해당 메서드를 감싸고 트랜잭션 관련 작업을 처리하는 방식으로 동작합니다. 이를 통해 개발자는 트랜잭션 관련 코드를 명시적으로 작성하지 않아도 됩니다. 스프링은 프록시를 사용하여 트랜잭션의 시작, 커밋, 롤백 등의 작업을 처리하고, 메서드 실행 전후에 필요한 부가적인 작업을 수행할 수 있습니다.

 

프록시를 이용한 트랜잭션 관리는 트랜잭션 경계 설정, 예외 처리, 더티 체킹 등을 투명하게 처리할 수 있도록 도와줍니다. 개발자는 비즈니스 로직에만 집중할 수 있고, 트랜잭션 관리와 관련된 부분은 스프링이 자동으로 처리해줍니다.

 

따라서, 스프링의 트랜잭션 관리는 프록시 패턴을 사용하여 구현되며, 이를 통해 효율적이고 편리한 트랜잭션 처리가 가능해집니다.

 

데이터베이스 트랜잭션에 대해 말해주세요

 

데이터베이스 트랜잭션은 데이터베이스 상태를 일관되고 안전하게 유지하기 위해 수행되는 작업의 단위입니다. 트랜잭션은 한 개 이상의 데이터베이스 조작(쿼리, 갱신 등)으로 구성되며, 이러한 조작은 모두 성공적으로 완료되거나, 전혀 수행되지 않아야 합니다. 트랜잭션은 "원자성(Atomicity)", "일관성(Consistency)", "격리성(Isolation)", "지속성(Durability)"이라는 ACID 속성을 만족해야 합니다.

 

- 원자성(Atomicity): 트랜잭션은 원자적인 작업 단위로 간주되어야 합니다. 즉, 트랜잭션 내의 모든 조작은 전부 수행되거나 전혀 수행되지 않아야 합니다. 한 조작이라도 실패하면 트랜잭션 전체가 롤백되어 이전 상태로 복원됩니다.

 

- 일관성(Consistency): 트랜잭션이 실행되기 전과 실행된 후의 데이터베이스 상태는 일관성을 유지해야 합니다. 트랜잭션은 정의된 규칙에 따라 데이터베이스의 무결성을 보장해야 합니다. 즉, 데이터베이스의 제약 조건이 항상 만족되어야 합니다.

 

- 격리성(Isolation): 동시에 여러 트랜잭션이 수행되더라도 각각의 트랜잭션은 독립적으로 실행되는 것처럼 보장되어야 합니다. 한 트랜잭션이 다른 트랜잭션의 작업에 영향을 주지 않고 독립적으로 실행될 수 있어야 합니다.

 

- 지속성(Durability): 트랜잭션이 성공적으로 완료되면, 그 결과는 영구적으로 유지되어야 합니다. 시스템 장애 또는 전원 장애 등이 발생해도 데이터베이스는 트랜잭션의 결과를 보존해야 합니다.

 

트랜잭션은 일반적으로 다음과 같은 단계를 거칩니다.

 

1. 시작(Start): 트랜잭션을 시작합니다. 데이터베이스 연결을 확립하고, 트랜잭션 ID를 할당합니다.

 

2. 작업(Perform): 데이터베이스 조작(쿼리, 갱신, 삭제 ) 수행합니다. 단계에서는 데이터베이스의 상태가 변경될 있습니다.

 

3. 커밋(Commit): 트랜잭션의 모든 작업이 성공적으로 완료되었고, 데이터베이스의 상태가 일관성을 유지하는 경우, 트랜잭션을 커밋합니다. 커밋은 트랜잭션의 변경 사항을 영구적으로 저장하고, 데이터베이스 상태를 업데이트합니다.

 

4. 롤백(Rollback): 트랜잭션 중에 오류가 발생하거나, 트랜잭션을 명시적으로 롤백하는 경우 해당 트랜잭션의 모든 변경 사항은 취소되고 이전 상태로 롤백됩니다. 롤백은 트랜잭션을 취소하여 데이터베이스의 일관성을 복원합니다.

 

트랜잭션은 데이터베이스 관리 시스템(DBMS)에서 제공하는 기능을 사용하여 구현됩니다. DBMS는 트랜잭션을 관리하고 ACID 속성을 준수하도록 보장합니다. 또한, DBMS는 여러 트랜잭션이 동시에 수행될 때 격리성을 보장하기 위해 락(lock) 등의 메커니즘을 사용합니다.

 

트랜잭션의 중요성은 데이터베이스의 무결성과 일관성을 보장하며, 데이터의 정확성과 신뢰성을 유지하는 기여합니다. 트랜잭션은 복수의 데이터베이스 조작을 하나의 논리적인 단위로 묶어서 오류 발생 데이터의 일관성을 보장하고, 데이터베이스 상태를 안전하게 유지합니다.

에러, 예외, 예외 처리 단어의 의미

"에러(Error)", "예외(Exception)", "예외 처리(Exception Handling)"는 프로그래밍에서 중요한 개념들이며, 이들의 의미와 상관관계는 아래와 같습니다:

1. 에러(Error): 에러는 시스템 레벨에서 발생하는 심각한 문제를 나타냅니다. 에러는 보통 JVM(Java Virtual Machine)에서 발생하며, 대부분의 경우 프로그램에서 복구할 수 없는 상태를 나타냅니다. 예를 들어, JVM에서 사용 가능한 메모리가 바닥나면 OutOfMemoryError가 발생합니다. 이러한 에러는 프로그램의 정상적인 작동을 방해하며, 대부분의 경우 프로그램을 종료시키는 결과를 초래합니다.

2. 예외(Exception): 예외는 프로그램의 정상적인 흐름을 방해하는 조건을 나타냅니다. 예외는 다양한 원인으로 발생할 수 있으며, 예를 들면, 파일을 열려고 했지만 해당 파일이 존재하지 않는 경우 FileNotFoundException이 발생하며, 또는 배열의 범위를 벗어난 인덱스에 접근하려고 하면 ArrayIndexOutOfBoundsException이 발생합니다. 예외는 프로그램에서 처리할 수 있으며, 적절한 예외 처리를 통해 프로그램의 안정성과 신뢰성을 높일 수 있습니다.

3. 예외 처리(Exception Handling): 예외 처리는 프로그램이 예외를 적절하게 처리하도록 설계하는 프로세스입니다. Java에서는 try-catch-finally 구문을 사용하여 예외를 처리합니다. 예외 처리를 통해 프로그램은 예외가 발생했을 때 적절하게 대응할 수 있습니다. 예를 들어, 파일이 없는 경우 프로그램은 사용자에게 오류 메시지를 표시하거나, 다른 방법으로 파일을 로드하거나, 또는 적절하게 프로그램을 종료할 수 있습니다.

따라서 이 세 가지 개념은 프로그램의 정상적인 작동과 문제 대응 메커니즘과 관련이 있습니다. 에러와 예외는 문제 상황을 나타내며, 예외 처리는 이러한 문제 상황을 적절하게 다루는 방법을 제공합니다.

 

나 같은 경우는 오늘 방금 전까지 ArrayIndexOutOfBoundsException를 항상 에러라고 말하고 다녔는데 흐음 실로 충격이 크다.

에러와 예외를 몇 년동안이나 거의 구분하지 못하고 대부분 통일해서 쓰고 있었던 것 같다. Exception이라고 붙어도 있었는데 허어참 ㅋㅋㅋ