트랜잭션

알비레오·2024년 9월 4일

DB

목록 보기
9/15

트랜잭션(Transaction)

  • 데이터베이스의 상태를 변화시키기 위해서 수행하는 작업의 단위

트랜잭션의 특징(ACID)

  • 원자성(Atomicity)
  • 일관성(Consistency)
  • 독립성(Isolation)
  • 지속성(Durability)

원자성(Atomicity)

- 트랜잭션이 데이터베이스에 모두 반영되던가, 아니면 전혀 반영되지 않아야 한다는 것

일관성(Consistency)

- 트랜잭션의 작업 처리 결과가 항상 일관성이 있어야 한다는 것

트랜잭션이 진행되는 동안에 데이터베이스가 변경되더라도 업데이트된 데이터베이스로 트랜잭션이 진행되는 것이 아니라, 처음에 트랜잭션을 진행하기 위해 참조한 데이터베이스로 진행된다.

독립성(Isolation)

- 둘 이상의 트랜잭션이 동시에 실행되고 있을 경우 어떤 하나의 트랜잭션이라도, 다른 트랜잭션의 연산에 끼어들 수 없다는 것

지속성(Durability)

- 트랜잭션이 성공적으로 완료되었을 경우, 결과는 영구적으로 반영되어야 한다는 것

트랜잭션 상태

1. 활성(Active) : 트랜잭션이 정상적으로 진행중인 상태
트랜잭션이 시작되면, 해당 트랜잭션의 상태는 활동(Actice)상태가 된다.
해당 상태는 설계자가 설계한 대로 연산들이 정상적으로 실행중인 상태를 의미한다.

- 작업 성공시
2-1 부분완료(Partially Committed) : 트랜잭션의 마지막까지 실행되었지만, Commit 연산이 실행되기 직전의 상태
2-2 완료(Committed) : 트랜잭션이 성공이 종료되어 Commit 연산을 실행한 후의 상태

- 작업 실패시
2-1 실패(Failed) : 트랜잭션 실행에 오류가 발생하여 중단된 상태
2-2 철회(Aborted) : 트랜잭션이 비정상적으로 종료되어 Rollback 연산을 수행한 상태

TCL 트랜잭션 제어어(commit, rollback, savepoint)

  • commit

    • 모든 작업들을 정상 처리하겠다고 확정하는 명령어
    • 해당 처리 과정을 DB에 영구 저장하겠다는 의미
    • Commit을 수행하면 하나의 트랜잭션 과정이 종료되는 것
    • Commit을 수행하면 이전 데이터가 완전히 반영되어 UPDATE 된다.
  • rollback

    • 작업 중 문제가 발생되어 트랜잭션의 처리 과정에서 발생한 변경사항을 취소하는 명령어
    • 해당 명령을 트랜잭션에게 하달하면, 트랜잭션은 시작되기 이전의 상태로 되돌아간다.
    • 마지막 Commit을 완료한 시점으로 돌아간다는 뜻
    • 즉, Commit하여 저장한 예전 상태를 복구하는 것
  • savepoint

    • 현재의 트랜잭션을 작게 분할하는 명령어
    • 저장된 savepoint는 rollback to savepoint문을 사용하여 표시한 곳까지 rollback 할 수 있다.
    • 원칙대로라면 원자성을 보장해야 하지만, 매우 긴 트랜잭션의 경우 대부분의 로직이 수행됐는데 마지막에만 문제가 생겨 Rollback이 되면 문제가 발생하지 않은 영역까지 반복해야 하는 난감한 상황이 발생한다.
    • 때문에 개발자가 임의적으로 현재의 트랜잭션을 작게 분할하여 지정한 곳까지 rollback 할 수 있어 이러한 문제를 해결한다.

mssql 예시(TCL(commit, rollback, savepoint))

-- 테이블 생성
CREATE TABLE employees (
    employee_id INT PRIMARY KEY,
    name VARCHAR(100),
    department VARCHAR(50),
    salary DECIMAL(10, 2)
);

-- 데이터 삽입
INSERT INTO employees (employee_id, name, department, salary) VALUES (1, 'Alice', 'Sales', 50000);
INSERT INTO employees (employee_id, name, department, salary) VALUES (2, 'Bob', 'Engineering', 60000);
INSERT INTO employees (employee_id, name, department, salary) VALUES (3, 'Charlie', 'Sales', 55000);
INSERT INTO employees (employee_id, name, department, salary) VALUES (4, 'Diana', 'Engineering', 70000);

-- employees 테이블 확인
select * from employees; 

-- commit 예시
BEGIN TRANSACTION;

-- Sales 부서 직원들의 급여를 10% 인상
UPDATE employees SET salary = salary * 1.1 WHERE department = 'Sales';

-- 변경 사항을 데이터베이스에 확정
COMMIT;

-- 결과 확인
SELECT * FROM employees;

-- rollback 예시
BEGIN TRANSACTION;

-- Sales 부서 직원들의 급여를 10% 인상
UPDATE employees SET salary = salary * 1.1 WHERE department = 'Sales';

-- 변경 사항을 취소
ROLLBACK;

-- 결과 확인
SELECT * FROM employees;

-- save transaction과 rollback transaction 예시
BEGIN TRANSACTION;

-- Sales 부서 직원들의 급여를 10% 인상
UPDATE employees SET salary = salary * 1.1 WHERE department = 'Sales';

-- SAVE TRANSACTION 설정
SAVE TRANSACTION my_savepoint;

-- Engineering 부서 직원들의 급여를 5% 인상
UPDATE employees SET salary = salary * 1.05 WHERE department = 'Engineering';

-- SAVE TRANSACTION으로 롤백
ROLLBACK TRANSACTION my_savepoint;

-- 나머지 변경 사항을 확정
COMMIT TRANSACTION;

-- 결과 확인
SELECT * FROM employees;

  • BEGINE TRANSACTION : 트랜잭션의 시작을 정의
  • SAVE TRANSACTION : 트랜잭션 내에서 특정 지점을 저장. 이 지점으로 롤백 가능
  • ROLLBACK TRANSACTION : 지정된 SAVE TRANSACTION 지점까지 롤백한다.
  • COMMIT TRANSACTION : 트랜잭션의 모든 변경사항을 확정한다.

스프링 @Transactional

  • 메소드, 클래스, 인터페이스 위에 추가하여 사용한다(선언적 트랜잭션)
  • 적용된 범위에서는 트랜잭션 기능이 포함된 프록시 객체가 생성되어 자동으로 commit 혹은 rollback을 진행해준다.
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {

    @Transactional
    public void registerUser(User user) {
        // 사용자 등록 로직
        userRepository.save(user);
        
        // 추가적인 로직
    }
}

@Transactional 옵션

isolation

  • 트랜잭션에서 일관성 없는 데이터 허용 수준을 설정한다.
  • 트랜잭션의 격리 수준을 설정한다.
  • 데이터베이스의 동시성 제어 방법을 정의한다.
  • 주요 격리 수준에는 READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE 등이 있다.
@Transactional(isolation = Isolation.SERIALIZABLE)
public void someMethod() {
    // 높은 수준의 격리
}
  • READ_UNCOMMITTED: 가장 낮은 격리 수준으로, 다른 트랜잭션의 커밋되지 않은 변경 사항을 읽을 수 있다.
  • READ_COMMITTED: 커밋된 데이터만 읽을 수 있다. 기본 격리 수준이다.
  • REPEATABLE_READ: 트랜잭션이 시작된 시점 이후 변경된 데이터는 읽을 수 없다.
  • SERIALIZABLE: 가장 높은 격리 수준으로, 모든 트랜잭션이 순차적으로 실행된 것처럼 보인다.

propagation

  • 트랜잭션 동작 도중 다른 트랜잭션을 호출할 때, 어떻게 할 것인지 지정하는 옵션이다.
  • 트랜잭션의 전파 방식을 정의한다.
  • 트랜잭션이 이미 존재하는지, 또는 새로운 트랜잭션을 시작해야 하는지 결정한다.
  • 주요 전파 옵션에는 REQUIRED, REQUIRES_NEW, MANDATORY, SUPPORTS, NOT_SUPPORTED, NEVER, NESTED 등이 있다.
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void someMethod() {
    // 새로운 트랜잭션을 시작
}

전파 옵션

REQUIRED: 기본값으로, 이미 존재하는 트랜잭션이 있으면 이를 사용하고, 없으면 새로운 트랜잭션을 시작한다.
REQUIRES_NEW: 항상 새로운 트랜잭션을 시작하며, 기존 트랜잭션이 있으면 이를 일시적으로 중단한다.
MANDATORY: 이미 존재하는 트랜잭션이 있어야 하며, 없으면 예외를 발생시킨다.
SUPPORTS: 기존 트랜잭션이 있으면 이를 사용하고, 없으면 트랜잭션 없이 실행한다.
NOT_SUPPORTED: 트랜잭션 없이 실행하며, 현재 트랜잭션이 있으면 이를 일시적으로 중단한다.
NEVER: 트랜잭션 없이 실행해야 하며, 현재 트랜잭션이 있으면 예외를 발생시킨다.
NESTED: 트랜잭션의 중첩을 허용합니다. 내부 트랜잭션이 실패하면 외부 트랜잭션은 영향을 받지 않는다.

noRollbackfor

  • 특정 예외 발생 시 rollback하지 않는다.
@Transactional(noRollbackFor = AnotherException.class)
public void someMethod() throws AnotherException {
    // AnotherException 발생 시 롤백하지 않음
}

rollbackFor

  • 특정 예외 발생 시 rollback한다.
@Transactional(rollbackFor = CustomException.class)
public void someMethod() throws CustomException {
    // CustomException 발생 시 롤백
}

timeout

  • 지정한 시간 내에 메소드 수행이 완료되지 않으면 rollback한다(-1일 경우 timeout을 사용하지 않는다)
@Transactional(timeout = 30)
public void someMethod() {
    // 30초 후 타임아웃
}

readOnly

  • 트랜잭션을 읽기 전용으로 설정한다.

isolation level(격리 레벨)

DEFAULT

  • 기본 격리 수준
  • DB의 isolation level을 따른다

READ_UNCOMMITED(level 0)

  • 커밋되지 않는 데이터에 대한 읽기를 허용

READ_COMMITED(level 1)

  • 커밋된 데이터에 대해 읽기 허용

REPEATABLE_READ(level 2)

  • 동일 필드에 대해 다중 접근 시 모두 동일한 결과를 보장

SERIALIZABLE(level 3)

  • 가장 높은 격리, 성능 저하의 우려가 있음

DAO, DTO, VO, DO 개념

1. DAO(Data Access Object)

  • 데이터베이스와 상호작용하는 객체
  • 데이터베이스 작업을 캡슐화하고, 데이터베이스에 대한 CRUD(Create, Read, Update, Delete) 연산을 수행하는 메서드를 제공한다

특징

  • 데이터 접근 캡슐화 : 데이터베이스에 대한 접근 로직을 한 곳에 모아 관리한다.
  • 비즈니스 로직과 분리 : 데이터베이스 작업을 비즈니스 로직에서 분리하여, 코드의 유지보수성을 높인다
  • 인터페이스 구현 : DAO 인터페이스를 정의하고, 이를 구현한 클래스가 데이터베이스 작업을 실제로 수행한다.
public interface UserDAO {
    void insertUser(User user);
    User getUserById(int id);
    void updateUser(User user);
    void deleteUser(int id);
}
public class UserDAOImpl implements UserDAO {
    // 데이터베이스 연결을 위한 코드
    @Override
    public void insertUser(User user) {
        // SQL INSERT 작업
    }
    @Override
    public User getUserById(int id) {
        // SQL SELECT 작업
        return new User(); // 실제 데이터 반환
    }
    @Override
    public void updateUser(User user) {
        // SQL UPDATE 작업
    }
    @Override
    public void deleteUser(int id) {
        // SQL DELETE 작업
    }
}

2. VO(Value Object)

  • 데이터 객체의 일종으로, 데이터를 담기 위한 객체
  • 주로 데이터의 상태를 보존하고, 이 데이터가 변경되지 않도록 설계된다.

특징

  • 불변성 : VO는 불변 객체(Immutable Object)로 설계되는 경우가 많다. 즉, 객체가 생성된 이후에는 데이터가 변경되지 않는다.
  • 비즈니스 의미 : VO는 비즈니스 로직에서 의미 있는 데이터 집합을 표현한다.
public class AddressVO {
    private final String street;
    private final String city;
    private final String zipCode;
    public AddressVO(String street, String city, String zipCode) {
        this.street = street;
        this.city = city;
        this.zipCode = zipCode;
    }
    // getter 메서드
    public String getStreet() { return street; }
    public String getCity() { return city; }
    public String getZipCode() { return zipCode; }
}

3. DTO(Data Transfer Object)

  • 데이터 전송 객체로, 계층 간 또는 시스템 간 데이터 전송을 위해 사용된다.
  • 데이터의 구조를 정의하고, 데이터를 저장하는 역할을 한다.
  • 주로 네트워크 통신이나 데이터베이스에서 결과를 반환할 때 사용된다.

특징

  • 전송 목적 : 데이터 전송을 위해 필요한 필드만 포함된다.
  • 빈 객체 : DTO는 데이터만 담고 있으며, 비즈니스 로직을 포함하지 않는다
  • 일반적으로 직렬화 가능 : 네트워크 통신이나 파일 저장을 위해 직렬화(Serializable) 가능한 경우가 많다.
public class UserDTO {
    private int id;
    private String name;
    private String email;
    // 기본 생성자
    public UserDTO() {}
    // 파라미터가 있는 생성자
    public UserDTO(int id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }
    // getter 및 setter 메서드
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

4. DO(Domain Object)

  • 도메인 객체로, 도메인 모델을 표현하는 객체이다.
  • 비즈니스 로직과 규칙을 포함하며, 시스템의 도메인 모델을 구성하는 클래스이다.
  • 일반적으로 도메인의 핵심 엔티티를 모델링한다.

특징

  • 비즈니스 로직: 도메인 모델과 관련된 비즈니스 로직을 포함할 수 있다.
  • 엔티티 : 데이터베이스의 엔티티와 매핑될 수 있으며, 비즈니스 규칙을 표현한다.
public class UserDO {
    private int id;
    private String name;
    private String email;    
    public UserDO(int id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }
    // 비즈니스 로직
    public void updateEmail(String newEmail) {
        // 이메일 업데이트 로직
        this.email = newEmail;
    }
    // getter 및 setter 메서드
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

요약

  • DAO(Data Access Object) : 데이터베이스와 상호작용을 하는 객체로, 데이터 접근 로직을 캡슐화한다.
  • VO(Value Object) : 데이터의 불변성을 유지하며, 비즈니스 의미를 가지는 객체이다.
  • DTO(Data Transfer Object) : 데이터 전송을 위해 사용되는 객체로, 데이터의 구조를 정의한다
  • DO(Domain Object) : 도메인 모델을 표현하며, 비즈니스 로직과 규칙을 포함한다.

0개의 댓글