개발/전자정부프레임워크

Spring Boot에서 트랜잭션 관리(Transaction Management) 설정하기

꿈꾸는법사 2024. 12. 29. 12:18
반응형

오늘은 Spring Boot에서 트랜잭션 관리(Transaction Management)를 설정하고 사용하는 방법을 배워보겠습니다. 트랜잭션은 데이터의 일관성과 무결성을 유지하기 위한 중요한 기능입니다.


1. 트랜잭션이란?

트랜잭션(Transaction)은 데이터베이스의 상태를 변환시키는 하나의 논리적 작업 단위입니다.
트랜잭션은 다음 4가지 특성을 가집니다.

  • 원자성(Atomicity): 모든 작업이 성공하거나 실패하는 하나의 단위로 처리됩니다.
  • 일관성(Consistency): 트랜잭션 실행 후 데이터의 상태가 일관성을 유지합니다.
  • 독립성(Isolation): 다른 트랜잭션의 영향을 받지 않고 독립적으로 수행됩니다.
  • 지속성(Durability): 트랜잭션이 완료된 후 변경 사항이 영구적으로 저장됩니다.

2. Spring Boot에서 트랜잭션 설정하기

(1) 의존성 추가

Spring Boot Starter는 기본적으로 트랜잭션 관련 의존성을 포함합니다. 필요 시 추가적으로 JDBC 관련 의존성을 설정합니다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

(2) @EnableTransactionManagement 사용

Spring Boot에서는 @EnableTransactionManagement 어노테이션을 통해 트랜잭션을 활성화합니다.
@SpringBootApplication이 있는 클래스에서 자동으로 활성화되므로 별도의 설정이 필요하지 않습니다.


3. 트랜잭션 적용하기

Spring에서는 @Transactional 어노테이션을 사용해 트랜잭션을 정의합니다.

(1) 서비스 클래스에서 트랜잭션 선언

@Transactional을 적용해 트랜잭션을 설정합니다.

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

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Transactional
    public void registerUser(User user) {
        // 1. 사용자 저장
        userRepository.save(user);

        // 2. 예외 발생 테스트
        if (user.getUsername().equals("error")) {
            throw new RuntimeException("강제로 예외 발생");
        }

        // 3. 추가 로직
        System.out.println("사용자 등록 완료");
    }
}

(2) 데이터베이스 작업 중 예외 처리

@Transactional 어노테이션은 기본적으로 예외가 발생하면 롤백(Rollback)합니다.
이때 롤백 조건을 커스터마이징할 수도 있습니다.

@Transactional(rollbackFor = {RuntimeException.class, Exception.class})
public void updateUser(User user) {
    // 업데이트 로직
}

4. 테스트 코드 작성

(1) 데이터베이스 엔터티

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;

@Entity
public class User {

    @Id
    @GeneratedValue
    private Long id;
    private String username;

    // Getter, Setter
}

(2) 레포지토리 인터페이스

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
}

(3) 컨트롤러 작성

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping("/register")
    public String registerUser(@RequestBody User user) {
        try {
            userService.registerUser(user);
            return "등록 성공";
        } catch (Exception e) {
            return "등록 실패: " + e.getMessage();
        }
    }
}

5. 실행 및 테스트

(1) 정상적인 요청

POST /register
{
    "username": "john"
}

결과:

  • 사용자 데이터가 데이터베이스에 저장됩니다.
  • "등록 성공" 메시지가 반환됩니다.

(2) 예외 발생 요청

POST /register
{
    "username": "error"
}

결과:

  • 데이터가 롤백됩니다.
  • "등록 실패" 메시지가 반환됩니다.

6. 트랜잭션 사용 시 주의사항

  1. Lazy 로딩과 트랜잭션 범위
    Lazy 로딩은 트랜잭션 범위 내에서만 동작합니다. 트랜잭션 외부에서 엔터티의 Lazy 필드에 접근하면 예외가 발생할 수 있습니다.
  2. 프록시 객체 제한
    트랜잭션 메서드가 같은 클래스 내에서 호출될 경우, 프록시 객체가 생성되지 않아 트랜잭션이 적용되지 않을 수 있습니다.
  3. 체크 예외 처리
    @Transactional은 기본적으로 RuntimeException만 롤백 대상으로 처리합니다. 체크 예외는 rollbackFor 속성을 사용해 처리해야 합니다.
반응형