본문 바로가기

개발기술/ORM

Spring JPA Hibernate 트랜잭션

 

@Transactional (read only)

  •  if we need atomicity we use trasnactional
    • and once we use transactional we do not call save so that hibernate track changes and flush at once.
  •  but, if we do not need atomicity, we avoid trasnactional becasue using trsnactional burden db

 

 

 

Hibernate에서는 어떤 일이 벌어지는가?

 

  1. 명시적 vs 암시적 트랜잭션:
    @Transactional을 사용하면 명시적으로 트랜잭션의 시작과 끝을 정의할 수 있습니다. 이는 Hibernate의 암시적 트랜잭션 관리보다 더 세밀한 제어를 제공합니다.

결론적으로, Hibernate는 기본적으로 auto-commit 모드가 아닌 명시적 트랜잭션 관리를 사용하며, Spring의 @Transactional 어노테이션은 이러한 트랜잭션 관리를 더욱 세밀하게 제어할 수 있게 해줍니다. 이는 개발자에게 트랜잭션 관리에 대한 더 많은 유연성과 제어력을 제공합니다.

 


  1. Hibernate의 기본 트랜잭션 관리:
    Hibernate는 내부적으로 트랜잭션을 사용합니다. 기본적으로 auto-commit 모드가 아닌 명시적 트랜잭션 관리를 사용합니다.
  2. Session과 트랜잭션:
    Hibernate의 Session 객체는 데이터베이스 연결을 래핑하고 있으며, 각 세션은 하나의 트랜잭션과 연관됩니다.
  3. 암시적 트랜잭션 시작:
    Hibernate는 필요할 때 자동으로 트랜잭션을 시작합니다. 예를 들어, flush()나 commit() 메서드가 호출될 때 트랜잭션이 없다면 자동으로 시작합니다.
  4. @Transactional의 역할:
    Spring의 @Transactional 어노테이션은 Hibernate의 트랜잭션 관리를 더욱 세밀하게 제어할 수 있게 해줍니다. 이는 트랜잭션의 경계, 전파 방식, 격리 수준 등을 명시적으로 정의할 수 있게 해줍니다.
  5. 트랜잭션 동기화:
    Spring은 트랜잭션 동기화 매니저를 사용하여 Hibernate의 Session과 트랜잭션을 관리합니다. 이를 통해 여러 데이터 액세스 기술 간의 트랜잭션을 조정할 수 있습니다.

 

 

Spring에서는 어떤 일이 벌어지는가?

 

트랜잭션 @Transactional 의 필요성

 

 JPA의 요구사항(Persistence Context) : JPA에서는 영속성 컨텍스트(Persistence Context)를 통해 엔티티의 변경 사항을 관리합니다. 트랜잭션이 있어야 이러한 변경 사항을 데이터베이스에 정확하게 동기화(Flush)할 수 있습니다.  그렇기 때문에 JPA는 쓰기 작업을 수행할 때 트랜잭션이 활성화되어 있어야 합니다. 그렇지 않으면 TransactionRequiredException과 같은 예외가 발생합니다.

 

데이터베이스 읽기 작업에서 트랜잭션

 

  특정 읽기 작업이 여러 단계로 나뉘어져 실행되며, 각 단계가 같은 데이터를 기반으로 수행되어야 하는 경우 트랜잭션이 필요합니다. 트랜잭션을 사용해 읽기 작업을 묶어 원자성을 보장하면, 중간에 다른 트랜잭션이 개입하지 않도록 하여 안정성을 높일 수 있습니다. 읽기 트랜잭션에 특정 격리 수준을 설정함으로써, 다른 트랜잭션에서 수행된 데이터 변경이 현재 트랜잭션에 미치는 영향을 제어할 수 있습니다.

  일관성 보장이 필요하지 않은 경우: 게시판이나 뉴스 피드처럼 일관성이 엄격히 요구되지 않는 조회 작업에서는 트랜잭션을 사용하지 않아도 무방합니다. 트랜잭션을 사용하지 않으면 성능이 향상되며, 각 쿼리가 별도의 독립적인 읽기 작업으로 처리됩니다.

 

Persistence Context (영속성 컨텍스트)

  • 정의: JPA에서 엔티티의 상태를 관리하는 일종의 '세션' 또는 '1차 캐시'입니다. EntityManager (the JPA interface for interacting with the database).를 통해 데이터베이스로부터 가져온 엔티티는 영속성 컨텍스트 내에서 관리되며, 이 컨텍스트는 엔티티의 생명주기를 관리합니다. 이 덕분에 JPA는 엔티티의 상태 변화(변경 추적)를 감지하고, 트랜잭션이 커밋될 때 그 변화를 데이터베이스에 반영할 수 있습니다.
  • 동작:
    •  변경 추적(Dirty Checking): 엔티티가 영속성 컨텍스트에 관리되는 동안, JPA는 해당 엔티티의 상태 변화를 자동으로 감지합니다. 이 감지는 '스냅샷'(snapshot)을 사용하여 처음 영속성 컨텍스트에 로드될 때의 상태와 현재 상태를 비교함으로써 이루어집니다. 트랜잭션이 커밋될 때, 변경된 사항은 자동으로 데이터베이스에 반영됩니다.
    • 지연 쓰기(Flush 지연): save메소드 호출과 같이 DB쓰기 작업이 호출될때, 데이터 변경이 즉시 데이터베이스에 반영되지 않고, 트랜잭션 커밋 시점에 캐쉬에 쌓아놓았던 변경사항을 한꺼번에 데이터베이스에 반영됩니다. 이를 flush라고 하며, JPA는 명시적으로 flush() 메서드를 호출하거나, 트랜잭션이 끝나는 시점에서 자동으로 flush를 수행하여 변경된 엔티티를 데이터베이스에 동기화합니다.

트랜잭션과 영속성 컨텍스트

영속성 컨텍스트는 트랜잭션과 밀접한 관련이 있습니다. 트랜잭션은 데이터베이스에 변경 사항을 동기화하는 시점을 명확히 정의하는 경계입니다.

  • 트랜잭션 내: 트랜잭션이 있는 경우, JPA는 트랜잭션이 커밋되는 시점에 영속성 컨텍스트 내의 엔티티 상태를 데이터베이스와 동기화합니다. 즉, 트랜잭션이 커밋될 때 변경된 엔티티가 데이터베이스에 반영됩니다.
  • 트랜잭션이 없는 경우: 트랜잭션 없이 변경된 엔티티는 자동으로 커밋되지 않고, 명시적으로 flush()를 호출해야 데이터베이스에 반영됩니다. 트랜잭션 경계가 없으면 변경 사항이 데이터베이스에 반영되지 않거나 일관성이 깨질 수 있습니다.

 

 

 

  1. 1
    4
    .
  2. Automatic Transaction Management:
    @Transactional handles the entire transaction lifecycle automatically, including starting, committing, and rolling back transactions
    4
    .
  3. Rollback Management:
    Contrary to the statement, rollback is a crucial aspect of @Transactional. By default, it automatically rolls back the transaction for runtime exceptions
    3
    5
    . You can also configure custom rollback rules using attributes like rollbackFor and noRollbackFor
    5
    .
  4. Isolation Level Setting:
    While setting the isolation level is indeed a feature of @Transactional, it's just one of several configurable aspects, not the main purpose
    4
    5
    .
  5. Propagation Behavior:
    @Transactional allows you to specify how transactions should propagate across method calls
    1
    5
    .
  6. Read-Only Flag:
    You can optimize performance for read-only operations by setting the readOnly flag
    5
    .
  7. Timeout Configuration:
    @Transactional allows you to set a timeout for the transactio

 

트랜잭션 @Transactional 의 비용

 

JPA 트랜잭션 설정

 

JPA의 트랜잭션 관리

  • 엔티티 변경: 엔티티 객체를 변경하면 JPA는 이를 자동으로 감지합니다(Dirty Checking).
  • 트랜잭션 커밋: 트랜잭션이 커밋될 때 변경 사항이 데이터베이스에 반영됩니다.
  • 트랜잭션이 설정되지 않은 경우, 데이터 변경이 즉시 데이터베이스에 반영되지 않으며, 엔티티 매니저의 flush 메서드를 명시적으로 호출해야 변경 사항이 데이터베이스에 반영됩니다.

LazyLoading and @Transactional

  • Lazy loading : a design pattern commonly used in programming where the initialization of a resource is delayed until it is actually needed. In the case of JPA (Java Persistence API) or Hibernate, lazy loading is used to defer the loading of associated entities from the database until they are specifically accessed in your application. This is particularly useful for improving the performance of your application by avoiding unnecessary database queries.
  • Role of @Transactional in Relation to Lazy Loading: @Transactional ensures that there is an open session or persistence context during the execution of the method. If there is no active session (e.g., if the method is not transactional), accessing a lazily-loaded field might result in an exception (commonly LazyInitializationException). a database session does not actively "wait" until lazy-loaded data is accessed. 
    • database session : It refers to a temporary, interactive connection between an application and a database. During this session, the application can execute queries, fetch data, update records, and perform other transaction-related activities.
      • Isolation and Consistency
      • Transactional Scope
      • Connection Pooling:
      • Entity Lifecycle:
      • Persistence Context:

 

 

1. Isolation(격리수준)

@Transactional(isolation = isolation.DEFAULT) : 지정한 메소드는 해당 격리수준으로 트랜잭션이 실행됨

 

트랜잭션에서 일관성이 없는 데이터를 허용하는 수준으로 위의 격리성에서 표기한 격리수준. 일반적으로 격리수준은 기본적으로 default로 건드리지 않고, 매우 중요한 정보에 대해서는 repeatable_read나 serializable로 엄격하게 관리하여 특별적용한다.

 

- DEFAULT : 사용하고 있는 DB의 기본 격리수준을 따르겠다

- READ_UNCOMMITED : 한 트랜잭션이 커밋하지도 않았는데 다른 트랜잭션이 데이터를 조회할 수 있음 (Dirty Read발생)

- READ_COMMITED : 한 트랜잭션이 끝나면 그것을 읽을 수 있다.  나의 트랜잭션이 길이가 길면 초반부 트랜잭션과 마지막 부분의 트랜잭션에서 데이터의 값이 달라지는 문제가있다.(Dirty Read 방지)

- REPEATABLE_READ : 한 트랜잭션이 완료될때까지, 특정 영역을 shared lock을 걸어서 다른 사용자는 해당하는 영역을 조회할 수 없음.  시작하는 순간 해당 데이터에 대해서 snapshot을 찍고, 트랜잭션에 의해서 데이터가 변경되더라도 데이터 변경이 없는 것처럼 snapshot의 데이터를 보여준다. (Non-repeateable read방지)

- SERIALIZABLE : 트랜잭션이 사용하고 있는 영역에 모두 shared lock을 사용해서 다른 트랜잭션이 해당영역에 접근할 수 없음. DB가 한번에 한가지 트랜잭션만 실행하여 100% 독립성 (Phantom Read 방지)

 

2. Propagation (전파수준) 

@Transactional(

트랜잭션 동작 도중 다른 트잭션을 호출 하는 상황. A트랜잭션 중 B트랜잭션을 호출하는 경우, B트랜잭션을 어떻게 취급할 것인가에 대한 상황이다. A는 부모, B는 자식이라고 칭하자. 

 

- REQUIRED : default값으로 설정됨. 부모 트랜잭션 안에서 자식 트랜잭션을 연계하여 하나로 포함하여 여긴다. 부모 트랜잭션이 존재하지 않으면 자식은 독단적으로 트랜잭션을 생성한다.

 

- SUPPORTS : 부모 트랜잭션 내에서 자식 트랜잭션이 호출되면 하나의 트랜잭션으로 인식되지만, 부모 트랜잭션이 없는 상태에서는 호출되면 트랜잭션을 별도로 만들지 않는다.

 

- REQUIRES_NEW : 부모 트랜잭션 안에서 자식 트랜잭션이 별도로 생성된다. 서로 트랜잭션간 영향을 받지 않는다.

 

- NESTED : 부모 트랜잭션이 있는 경우, 자식 트랜잭션을 중첩트랜잭션으로 생성한다. 이는, 부모트랜잭션은 자식트랜잭션에 종속되지 않지만, 자식 트랜잭션은 부모트랜잭션에 종속되도록 만든다.

 

ex) 사용자 일기 작성 관련해서 프로그램 로그를 DB에 저장하는 상황이라면, 프로그램 로그가 실패한다고 사용자 일기 작성이 롤백이 되면 안될것. 그러나, 사용자 일기 작성이 실패하여 롤백할 경우에는 로그 DB도 롤백해야한다.

 

3. readOnly속성

 

트랜잭션을 읽기 전용 속성으로 지정. 이는 성능을 최적화하기 위해서 사용하거나 트랜잭션 기능에 제약을 걸어두기 위해 사용함. 트랜잭션을 읽기 전용속성으로 지정하면 성능이 빨라진다. 그리고 트랜잭션 내에서 읽기 외에는 작업을 해서는 안되는 작업의 경우 제약을 걸어둠.

 

@Transactional(readOnly=true)

 

4. 트랜잭션 롤백 예외

@Transactional(rollbackFor =Exception.class)

@Transactional(norollbackFor=Exception.class)

특정 예외가 발생했을때에만 트랜잭션 롤백시킬 경우를 설정. default로 runtime exception, error 발생할때 롤백을 하도록 

 

4. Timeout속성

@Transactional(timeout=10)

 

일정 시간 내에서 트랜잭션 끝내지 못하면 롤백시킴.격리 수준이 빡빡해서 해당 작업이 문제가 생겨서 오랬동안 막혀있다면, 다른 트랜잭션도 해당 데이터를 사용하지 못하고 대기하고 있어서 

 

그 외 : 데이터베이스 상에서도 트랜잭션 상 세부설정을 할 수 있음.