-
트랜잭션, 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