ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 트랜잭션, DB 락
    Spring&SpringBoot 2025. 11. 12. 15:04

    트랜잭션

    트랜잭션 ACID

    • 원자성(Atomicity) : 트랜잭션 내에서 실행한 작업들은 마치 하나의 작업인 것처럼 모두 성공하거나 모두 실패해야 한다
    • 일관성(Consistency) : 모든 트랜잭션은 일관성 있는 데이터베이스 상태를 유지해야 한다
      • 예) 데이터베이스에서 정한 무결성 제약 조건을 항상 만족해야 한다
    • 격리성(Isolation) : 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않도록 격리한다
      • 예) 동시에 같은 데이터를 수정하지 못하도록 해야 한다
      • 트랜잭션 격리 수준 - Isolation level
        • READ UNCOMMITED(커밋되지 않은 읽기)
        • READ COMMITTED(커밋된 읽기) - 실무
        • REPEATABLE READ(반복 가능한 읽기)
        • SERIALIZABLE(직렬화 가능)
    • 지속성(Durability) : 트랜잭션을 성공적으로 끝내면 그 결과가 항상 기록되어야 하며 중간에 문제가 발생했을 때 복구해야 한다

     

     

    데이터베이스 연결 구조와 DB 세션

    • 사용자는 WAS나 DB접근툴 같은 클라이언트를 사용해 데이터베이스 서버에 접근할 수 있다
    • 클라이언트가 데이터베이스 서버에 연결을 요청하고 커넥션을 맺으면 데이터베이스 서버 내부에 세션이 만들어진다
    • 해당 커넥션을 통한 모든 요청이 만들어진 세션을 통해 실행된다
    • 세션은 트랜잭션을 시작하고 커밋, 롤백을 통해 트랜잭션을 종료한다
    • 사용자가 커넥션을 닫거나 DB관리자가 세션을 강제로 종료하면 세션은 종료된다
    • 커넥션 풀이 생성되는 만큼 세션이 만들어진다 

     

     

    DB 락

    - 데이터베이스에서 동시에 여러 사용자가 같은 데이터를 건드릴 때 충돌을 막기 위한 것

     

     

    트랜잭션 적용

    • 애플리케이션에서 같은 커넥션을 유지하기 위해 커넥션을 파라미터로 전달해서 같은 커넥션이 사용되도록 함

     

     

        ...
        
        public Member findById(Connection con, String memberId) throws SQLException {
            String sql = "select * from member where member_id = ?";
    
            PreparedStatement pstmt = null;
            ResultSet rs = null;
    
            try {
                pstmt = con.prepareStatement(sql);
                pstmt.setString(1, memberId);
    
                rs = pstmt.executeQuery();
                if (rs.next()) {
                    Member member = new Member();
                    member.setMemberId(rs.getString("member_id"));
                    member.setMoney(rs.getInt("money"));
                    return member;
                } else {
                    throw new NoSuchElementException("member not found memberId= " + memberId);
                }
            } catch (SQLException e) {
                log.error("db error", e);
                throw e;
            } finally {
                JdbcUtils.closeResultSet(rs);
                JdbcUtils.closeStatement(pstmt);
            }
        }
    
        public void update(Connection con, String memberId, int money) throws SQLException {
            String sql = "update member set money = ? where member_id = ?";
    
            PreparedStatement pstmt = null;
    
            try {
                pstmt = con.prepareStatement(sql);
                pstmt.setInt(1, money);
                pstmt.setString(2, memberId);
    
                int resultSize = pstmt.executeUpdate();
                log.info("resultSize={}" + resultSize);
            } catch (SQLException e) {
                log.error("db error", e);
                throw e;
            } finally {
                JdbcUtils.closeStatement(pstmt);
            }
        }
        ...
    @Slf4j
    @RequiredArgsConstructor
    public class MemberServiceV2 {
        private final MemberRepositoryV1 repository;
        private final DataSource dataSource;
    
        public void accountTransfer(String fromId, String toId, int money) throws SQLException {
            Connection connection = dataSource.getConnection();
            try {
                // 트랜잭션 시작
                connection.setAutoCommit(false);
    
                // 비즈니스 로직
                bizLogic(fromId, toId, money, connection);
                
                // 성공시 커밋
                connection.commit();
            } catch (Exception e) {
                // 실패시 롤백
                connection.rollback();
            } finally {
                release(connection);
            }
        }
    
        private void bizLogic(String fromId, String toId, int money, Connection connection) throws SQLException {
            Member fromMember = repository.findById(connection, fromId);
            Member toMember = repository.findById(connection, toId);
    
            repository.update(fromId, fromMember.getMoney() - money);
            validation(toMember);
            repository.update(toId, toMember.getMoney() + money);
        }
    
        private void release(Connection connection) throws SQLException {
            if (connection != null) {
                try {
                    connection.setAutoCommit(true);
                    connection.close();
                } catch (Exception e) {
                    log.info("error", e);
                }
            }
        }
    
        private void validation(Member member) {
            if (member.getMemberId().equals("ex")) {
                throw new IllegalArgumentException("예외 발생");
            }
        }
    }
    728x90

    'Spring&SpringBoot' 카테고리의 다른 글

    트랜잭션 커밋/롤백과 예외  (0) 2025.11.20
    @Transaction  (0) 2025.11.13
    JDBC  (0) 2025.11.12
    스프링 AOP 실무 주의사항  (0) 2025.10.21
    로그출력AOP & 재시도AOP  (0) 2025.10.21
Designed by Tistory.