Notice
Recent Posts
Recent Comments
Link
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Tags more
Archives
Today
Total
관리 메뉴

밤빵's 개발일지

[TIL]20240727 JPA의 트랜잭션 본문

개발Article

[TIL]20240727 JPA의 트랜잭션

최밤빵 2024. 7. 27. 21:21

JPA(Java Persistence API)에서 트랜잭션은 데이터베이스와의 상호작용에서 매우 중요한 개념이다. 트랜잭션은 데이터베이스 작업의 성공 또는 실패를 보장하여 데이터의 일관성과 무결성을 유지하는 핵심 메커니즘이기때문에 오늘 개발일지에서는 JPA에서의 트랜잭션 관리 개념과 원리, 주요 기능, 사용 방법, 그리고 주의사항을 중심으로 정리해 보았다. ( 그저 다 성공하면 커밋! 하나라도 실패하면 롤백! 으로만 알고있어서 이번기회에 좋은 공부했다!) 

🤓JPA의 트랜잭션(Transaction)이란?

트랜잭션(Transaction)은 데이터베이스에서 작업의 논리적 단위로, 일련의 작업이 모두 성공하거나 모두 실패해야 하는 원자성을 보장하는 개념이다. 트랜잭션은 여러 데이터베이스 조작이 하나의 작업으로 수행되도록 보장하고, 이를 통해 데이터의 일관성과 무결성을 유지한다. JPA는 데이터베이스와의 상호작용에서 이러한 트랜잭션을 쉽게 관리할 수 있도록 지원한다.

 

▶ 트랜잭션의 개념과 원리

트랜잭션은 ACID 속성을 만족해야 한다:

 

→ Atomicity(원자성):

트랜잭션 내의 모든 작업은 하나의 단위로 실행되며, 모두 성공하거나 모두 실패해야 한다.

→ Consistency(일관성):

트랜잭션이 시작되기 전과 완료된 후에 데이터베이스는 항상 일관된 상태를 유지해야 한다.

→ Isolation(고립성):

각 트랜잭션은 다른 트랜잭션으로부터 독립적으로 실행되어야 하며, 중간 상태가 다른 트랜잭션에 영향을 주어서는 안 된다.

→ Durability(지속성):

트랜잭션이 성공적으로 완료되면, 그 결과는 영구적으로 데이터베이스에 저장되어야 한다.

JPA에서는 EntityManager를 사용하여 데이터베이스 작업을 수행하며, EntityManager는 트랜잭션을 명시적으로 관리한다. 트랜잭션의 관리는 EntityTransaction 객체를 사용하여 이루어지며, begin(), commit(), rollback() 등의 메서드를 통해 트랜잭션을 제어할 수 있다.

 

▶ JPA에서의 트랜잭션 관리

JPA는 트랜잭션 관리를 위해 두 가지 접근 방식을 제공한다:

 

→ 명시적 트랜잭션 관리:

개발자가 직접 EntityTransaction 객체를 사용하여 트랜잭션의 시작과 종료를 명시적으로 제어한다.

→ 선언적 트랜잭션 관리:

스프링 프레임워크와 같은 컨테이너 기반 환경에서는 애너테이션(@Transactional)을 사용하여 트랜잭션을 선언적으로 관리할 수 있다. 이를 통해 트랜잭션의 전파(Propagation) 및 격리 수준(Isolation Level)을 세부적으로 설정할 수 있다.

 

▶ 명시적 트랜잭션 관리 예시

JPA의 명시적 트랜잭션 관리는 EntityManager와 EntityTransaction을 사용하여 트랜잭션을 직접 제어한다.

▽ 간단한 예시코드 

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.Persistence;

public class JpaTransactionExample {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("example-unit");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();

        try {
            tx.begin(); // 트랜잭션 시작

            // 데이터베이스 작업
            Member member = new Member();
            member.setUsername("user1");
            em.persist(member); // 엔티티 저장

            tx.commit(); // 트랜잭션 커밋 (변경사항을 데이터베이스에 반영)
        } catch (Exception e) {
            tx.rollback(); // 오류 발생 시 트랜잭션 롤백
            e.printStackTrace();
        } finally {
            em.close(); // 엔티티 매니저 종료
        }
        emf.close(); // 엔티티 매니저 팩토리 종료
    }
}

위 코드는 JPA에서 명시적으로 트랜잭션을 관리하는 예제이다. tx.begin()으로 트랜잭션을 시작하고, tx.commit()으로 변경사항을 커밋한다. 예외가 발생할 경우 tx.rollback()을 호출하여 트랜잭션을 롤백한다.

 

▶ 선언적 트랜잭션 관리 예시

스프링 프레임워크에서는 @Transactional 애너테이션을 사용하여 선언적으로 트랜잭션을 관리할 수 있다. 이를 통해 트랜잭션의 전파 수준, 격리 수준 등을 설정할 수 있으며, 코드의 가독성을 높이고 유지보수를 용이하게 할 수 있다.

▽ 간단한 예시코드

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MemberService {
    private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Transactional
    public void saveMember(Member member) {
        memberRepository.save(member);
    }

    @Transactional(readOnly = true)
    public Member findMember(Long id) {
        return memberRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("Member not found"));
    }
}

위 코드는 스프링에서 @Transactional 애너테이션을 사용하여 선언적으로 트랜잭션을 관리하는 예제이다. saveMember() 메서드는 기본 트랜잭션을 사용하며, findMember() 메서드는 readOnly = true로 설정하여 읽기 전용 트랜잭션을 사용한다. 이는 성능 최적화와 데이터 변경 방지를 위해 유용하다.

 

▶ 트랜잭션 전파(Propagation)와 격리 수준(Isolation Level)

JPA에서 트랜잭션을 사용할 때는 트랜잭션의 전파와 격리 수준을 적절히 설정하는 것이 중요하다.

 

→ 트랜잭션 전파(Propagation):

트랜잭션 전파는 트랜잭션이 다른 메서드로 전파되는 방식을 정의한다. 스프링에서는 다양한 전파 옵션을 제공한다:

REQUIRED: 기본 전파 수준으로, 기존 트랜잭션이 존재하면 해당 트랜잭션을 사용하고, 없으면 새로 생성한다.

REQUIRES_NEW: 항상 새로운 트랜잭션을 생성하며, 기존 트랜잭션을 일시 정지시킨다.

NESTED: 기존 트랜잭션 내에서 중첩된 트랜잭션을 시작한다.

→ 격리 수준(Isolation Level):

격리 수준은 동시에 여러 트랜잭션이 실행될 때 데이터의 일관성을 유지하기 위한 규칙을 정의한다. 스프링에서는 다음과 같은 격리 수준을 제공한다:

→ READ_UNCOMMITTED:

다른 트랜잭션이 커밋하지 않은 데이터를 읽을 수 있다. 가장 낮은 격리 수준이며, 데이터 불일치 문제가 발생할 수 있다.

→ READ_COMMITTED:

다른 트랜잭션이 커밋한 데이터만 읽을 수 있다.

→ REPEATABLE_READ:

트랜잭션이 시작된 시점의 데이터를 일관되게 읽을 수 있다.

→ SERIALIZABLE:

가장 높은 격리 수준으로, 트랜잭션을 순차적으로 실행하여 완벽한 일관성을 보장하지만, 성능이 저하될 수 있다.

 

▶ JPA 트랜잭션 사용 시 주의사항

→ 트랜잭션 경계 설정:

트랜잭션 경계를 적절하게 설정하는 것이 중요하다. 트랜잭션이 너무 길어지면 데이터베이스의 리소스를 과도하게 사용하여 성능에 악영향을 미칠 수 있다.

→ 트랜잭션 롤백 전략:

예외 발생 시 트랜잭션을 롤백하는 전략을 명확히 정의해야 한다. 일반적으로 런타임 예외(RuntimeException)와 체크 예외(Checked Exception)를 구분하여 처리한다.

→ 지연 로딩과 트랜잭션 범위:

영속성 컨텍스트가 열려 있는 동안 지연 로딩(Lazy Loading)이 수행되어야 한다. 트랜잭션 범위를 벗어나면 LazyInitializationException이 발생할 수 있으므로 주의가 필요하다.

→ 트랜잭션과 성능 최적화:

모든 메서드에 트랜잭션을 적용하면 오버헤드가 발생할 수 있다. 데이터베이스 읽기 전용 메서드에는 @Transactional(readOnly = true)를 적용하여 성능을 최적화할 수 있다.

 

▶ 마무리 (내용 요약)

이번 개발일지를 통해 JPA의 트랜잭션 개념과 관리 방법에 대해 다뤘다. 트랜잭션은 데이터의 일관성과 무결성을 보장하는 핵심 메커니즘으로, JPA를 사용하는 애플리케이션 개발에서 필수적으로 이해해야 할 개념이다. 트랜잭션 관리에는 명시적 관리와 선언적 관리가 있으며, 각각의 사용 예시와 설정 방법에 따라 효율적이고 안정적인 애플리케이션을 개발할 수 있다. 적절한 트랜잭션 경계 설정과 롤백 전략을 마련해서 성능을 최적화하는 것이 중요하다.