티스토리 뷰

환경구성

  • Spring boot version: 3.2.5
  • Java version : 17
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-jdbc'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.h2database:h2'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'

	// 테스트에서 lombok 사용
	testCompileOnly 'org.projectlombok:lombok'
	testAnnotationProcessor 'org.projectlombok:lombok'
}

 

 

학습목표

1. ACID 개념
2. Spring JDBC와 ACID 개념 매핑해 보기
3. @Transactional 

 

 

1. ACID 개념

데이터베이스를 사용하는 이유 중 하나는 ACID 개념을 기반으로 데이터베이스가 개발되었기 때문입니다. 정보처리기사를 준비하신 분이라면 한 번쯤 들어봤을 ACID에 대해 정리해 보겠습니다.

 

우선 트랜잭션부터~!

데이터베이스에서 트랜잭션은 일련의 작업 단위로, 여러 데이터베이스 조작을 하나의 논리적 기능으로 그룹화한 것을 말합니다.
트랜잭션은 모두 함께 성공하거나 실패해야 하며, 그 결과는 데이터베이스의 일관성과 무결성을 유지해야 합니다.
데이터베이스 시스템에서 트랜잭션은 다음과 같은 ACID 특성을 충족시켜야 합니다

 

트랜잭션의 일련의 작업단위는 결국 개발자가 작성한 쿼리를 하나의 트랜잭션으로 처리하는 단위를 말합니다.

아래 예시와 같이 CREATE 문 1개가 하나의 트랜잭션일 수도 있고, 여러 INSERT 문이 하나의 트랜잭션일 수도 있습니다. 

이외에 언급한 단어는 모두 ACID 관한 내용으로, 항상 붙어 다니는 개념입니다. 이제 ACID에 대해서 알아보죠.

# CREATE sql 문의 하나의 트랜잭션인 경우
CREATE TABLE member (
	member_id varchar(10),
    username varchar(10),
    primary key (member_id)
);
# 여러 INSERT 문을 하나의 트랜잭션에 수행하는 경우
INSERT INTO member(memberid, username) value('member1', 'tester1');
INSERT INTO member(memberid, username) value('member2', 'tester2');
INSERT INTO member(memberid, username) value('member3', 'tester3');
INSERT INTO member(memberid, username) value('member4', 'tester4');
INSERT INTO member(memberid, username) value('member5', 'tester5');
INSERT INTO member(memberid, username) value('member6', 'tester6');
INSERT INTO member(memberid, username) value('member7', 'tester7');

commit;

 

Actomicity 원자성 - All or Nothing

Actomicity 원자성을 간단히 설명해 보자면 `All or Nothing`이라고 표현할 수 있습니다.  앞서 GPT의 답변 중 `트랜잭션은 모두 성공하거나 모두 실패해야 한다` 에 해당하는 개념입니다.

철수의 계좌는 100,000원이 있고, 영희의 계좌는 100,000원이 있습니다.
철수는 영희에게 50,000원을 이체하려고 하려고 합니다. 
위 경우를 SQL로 정의해 보면 아래와 같습니다.
# 철수 계좌 수정
UPDATE 계좌테이블 SET 잔액 = 잔액 - 50000 WHERE name = '철수';

# 영희 계좌 수정
UPDATE 계좌테이블 SET 잔액 = 잔액 + 50000 WHERE name = '영희';

 

철수가 영희에게 50,000원을 계좌 이체를 할 때 어떤 오류로 이체가 실패했다면?
아래 두 가지 경우로 처리되어야 합니다.
1. 철수 계좌 : 100,000원 / 영희 계좌 : 100,000원
2. 철수 계좌 :  50,000원 / 영희 계좌 : 150,000원

 

즉 두 쿼리문이 한 트랜잭션에서 수행되어야 하며, 에러 발생 시 rollback 해 원자성을 지키도록 해야 하죠.

 

Consistency 일관성

트랜잭션이 데이터베이스의 일관된 상태를 유지하도록 보장하는 속성입니다. 일관성은 트랜잭션이 완료된 후에도 데이터베이스가 제약조건, 트리거, 데이터 무결성 규칙 등을 만족하는 유효한 상태에 있어야 한다는 것을 의미합니다.

 

무결성 제약 조건(Integrity Constraints): 데이터베이스에서는 기본 키, 외래 키, 유니크 키 등의 무결성 제약 조건을 사용하여 데이터의 정확성과 일관성을 유지합니다. 이러한 제약 조건들은 데이터가 정의된 규칙에 따라 입력, 수정되도록 강제합니다.

 

예시: 모든 은행 계좌의 잔액은 항상 0 이상이어야 한다는 규칙을 적용할 때, 트랜잭션은 이 규칙을 준수하여 처리되어야 합니다. 잔액이 0인데 출금 요청하면 해당 요청을 거절해 계좌의 일관성을 유지하도록 해야 합니다.

 

Isolation 독립성

서로 다른 두 개의 트랜잭션이 서로 영향을 받지 않아야 한다는 개념입니다. 

 

예시: 두 트랜잭션이 동시에 같은 데이터에 접근할 경우, 고립성은 한 트랜잭션이 완료될 때까지 다른 트랜잭션에서 해당 데이터를 읽거나 쓰는 것을 제한합니다. 이를 통해 잘못된 데이터 읽기 또는 업데이트로 인한 오류를 방지할 수 있습니다.

Durability 지속성

지속성은 트랜잭션이 성공적으로 완료된 후, 그 결과가 영구적으로 데이터베이스에 반영되어야 한다는 것을 보장합니다. 시스템이 충돌하거나 문제가 발생해도, 이미 커밋된 트랜잭션의 결과는 손실되지 않아야 합니다.

 

예시: 은행 거래가 성공적으로 처리된 후, 그 결과는 데이터베이스에 지속적으로 저장되어야 하며, 전원 장애 또는 시스템 오류 후에도 그 결과는 유지되어야 합니다. 


 

 

2. Spring JDBC와 ACID 개념 매핑해 보기

원자성 (Atomicity)

JDBC에서는 try-catch 문을 이용해 원자성을 보장합니다.

아래와 같이 member 테이블에 회원을 추가하는 쿼리문을 실행할 때, 어떠한 에러로 중간에 에러가 발생했다면 SQLException을 터트리고 커넥션을 종료하도록 코드를 구성해 원자성(All or Nothing)을 보장할 수 있습니다.

public class MemberRepositoryV0 {

    public Member save(Member member) throws SQLException {
        String sql = "insert into member(member_id, money) values(?, ?)";

        Connection con = null; //
        PreparedStatement pstmt = null;

        try {
            con = getConnection();
            pstmt = con.prepareStatement(sql);
            pstmt.setString(1, member.getMemberId()); // sql의 첫번째 물음표 자리에 넣을 파라미터
            pstmt.setInt(2, member.getMoney()); // sql의 두번째 물음표 자리에 넣을 파라미터
            pstmt.executeUpdate(); // 변경할때는 executeUpdate, 쿼리가 데이터베이스에 실행하는 함수, 반영 row 만큼 숫자르 반환,
            return member;
        } catch (SQLException e) {
            log.error("db error", e);
            throw e;
        } finally {
            close(con,pstmt,null);
        }
    }

    private static Connection getConnection() {
        return DBConnectionUtil.getConnection();
    }
    
     /**
     * TCP/IP connection으로 외부 DB 리소스를 사용 중.
     * 만약 않닫는다면 계속 연결된 상태로 있음.
     */
    private void close(Connection conn, Statement stmt, ResultSet rs){
        // 앞에 정의한 자원을 역순으로 닫아줘야함
        if(rs != null){
            try {
                rs.close();
            } catch (SQLException e) {
                log.info("error", e);
            }
        }
        if(stmt != null){
            try {
                // Statement : SQL을 직접 넣는 것
                // PreparedStatement extends Statement: 파라미터를 바인딩 할수 있게 해줌
                // SQL 인젝션을 방지하기 위해 PreparedStatement을 사용해야함
                stmt.close();
            } catch (SQLException e) {
                log.info("error", e);
            }

        }

        if (conn != null ) {
            try {
                conn.close();
            } catch (SQLException e) {
                log.info("error", e);
            }
        }
    }
}

 

 

일관성 (Consistency)

일관성은 데이터베이스 스키마에 정의된 제약조건 (Primary key, Foreign key, Not null, Unique)을 이용해 데이터의 무결성 제약조건들이 일관성을 보장하도록 합니다. 

 

아래와 같이 데이터베이스 스키마에 money 칼럼에 not null 제약조건을 걸어두고, JDBC에서 money가 null 인 인스턴스로 쿼리를 실행하면 제약조건에 의해 에러가 발생해  일관성을 지키도록 조치합니다.

# 데이터베이스 스키마
create table member (
       member_id varchar(10),
       money integer not null,
       primary key (member_id)
);
// member.getId = "tester"
// member.getMoney = null;

public Member save(Member member) throws SQLException {
    
    String sql = "insert into member(member_id, money) values(?, ?)";

    Connection con = null; //
    PreparedStatement pstmt = null;

    try {
        con = getConnection();
        pstmt = con.prepareStatement(sql);
        pstmt.setString(1, member.getMemberId());
        pstmt.setInt(2, member.getMoney());
        pstmt.executeUpdate();
        return member;
    } catch (SQLException e) {
        log.error("db error", e);
        throw e;
    } finally {
        close(con,pstmt,null);
    }
}

 

 

고립성 (Isolation)

JDBC에서 트랜잭션의 Isolation level 은 Connection 객체의 setTransactionIsolation(int level) 메서드를 사용하여 설정할 수 있습니다. 고립 수준에 따라, 동시에 실행되는 트랜잭션들이 서로 영향을 미치는 정도가 결정됩니다. 

 

JDBC에서 Isolation level를 정의할 수 있는데 Connection.TRANSACTION_READ_COMMITTED와 같은 명령어를 통해 level을 개발자가 변경할 수 있습니다.

conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);

 

※ MySQL과 PostgreSQL Isolation level ckarh ( 출처 - 쉬운 코딩)

지속성 (Durability)

트랜잭션이 커밋되면, 데이터베이스 관리 시스템(DBMS)은 해당 변경사항이 데이터베이스에 영구적으로 기록되도록 보장합니다. 시스템이 충돌하더라도, 이미 커밋된 데이터는 손실되지 않습니다. 따라서, JDBC에서 조치해 주는 사항은 없습니다.

 


 

@Transactional 

@Transactional을 사용하면 특정 메서드나 클래스 전체에 트랜잭션의 경계를 설정할 수 있으며, 스프링이 자동으로 트랜잭션을 시작하고 종료합니다. 즉, 개발자는 트랜잭션 관리에 대해 신경 쓸 필요 없이 비즈니스 로직에 집중할 수 있습니다.

 

@Transactional의 주요 기능:

  • 자동 롤백: 메소드 실행 중 예외가 발생하면 트랜잭션이 자동으로 롤백됩니다.
  • 프록시 기반: 스프링은 @Transactional이 붙은 클래스나 메소드를 호출할 때 프록시를 통해 접근하며,
    이 프록시는 트랜잭션 관리를 처리합니다.
  • 다양한 설정 옵션: 트랜잭션의 전파 수준(propagation), 격리 수준(isolation), 읽기 전용(read-only) 설정, 타임아웃 설정 등을 지정할 수 있습니다.

 

아래의 transferMoney 메서드를 한 트랜잭션으로 수행하기 위해 @Transactional을 사용한 예시 코드입니다.

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

@Service
public class BankService {

    @Transactional
    public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {
        Account fromAccount = accountRepository.findById(fromAccountId).orElseThrow();
        Account toAccount = accountRepository.findById(toAccountId).orElseThrow();

        fromAccount.debit(amount); // 출금
        toAccount.credit(amount); // 입금

        accountRepository.save(fromAccount);
        accountRepository.save(toAccount);
    }
}

 

 

 

※ 참고 자료

김영한 강의 :  스프링 DB 1편 - 데이터 접근 핵심 원리

 

스프링 DB 1편 - 데이터 접근 핵심 원리 | 김영한 - 인프런

김영한 | 백엔드 개발에 필요한 DB 데이터 접근 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 DB 접근 기술의 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니

www.inflearn.com

쉬운 코딩 - 트랜잭션 영상

 

이상 정리 끝!

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
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
글 보관함