728x90
반응형
SMALL
학습내용
- 트랜잭션 개념 이해
- JDBC 트랜잭션 사용
- 트랜잭션 관리 방식
학습정리
1. 트랜잭션
개념
- 데이터베이스에서 여러 작업을 하나의 논리적 작업 단위로 묶어 처리 하는 것
목적
- 목적은 작업 도중 오류가 발생하더라도 데이터베이스의 무결성과 일관성을 보장
트랜잭션의 주요 기능
트랜잭션 시작
-- 트랜잭션 시작 (데이터베이스에 따라 BEGIN이나 START TRANSACTION 사용) BEGIN; -- 또는 START TRANSACTION;
작업 수행
-- 주문 시 특정 상품의 재고 감소 UPDATE products SET stock = stock - 1 WHERE id = 'productId'; -- 예를 들어, 주문 내역을 기록하는 작업도 함께 수행할 수 있습니다. INSERT INTO categories (name) VALUES ('카테고리 명');
커밋 또는 롤백
-- 모든 작업이 성공적으로 수행되었다면: COMMIT; -- 작업 중 오류가 발생한 경우, 아래와 같이 롤백하여 변경 사항을 취소합니다: ROLLBACK;
2. JDBC 트랜잭션 사용
예시 코드 사용
Repository
@Slf4j @Component @RequiredArgsConstructor public class CategoryJdbcRepository { //해당 소스코드는 @Transactional을 사용시에 일어나는 소스코드 private final DataSource dataSource; //트랜잭션 적용 전 save public Category save(Category category) throws SQLException { String sql = "INSERT INTO categories (name) VALUES (?)"; Connection connection = null; PreparedStatement preparedStatement = null; try { connection = dataSource.getConnection(); preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1, category.getName()); preparedStatement.executeUpdate(); return category; } catch (SQLException e) { log.error("insert Error", e); throw e; } finally { JdbcUtils.closeResultSet(null); JdbcUtils.closeStatement(preparedStatement); JdbcUtils.closeConnection(connection); } } public Category findById(Connection connection, Long categoryId) throws SQLException { String sql = "SELECT * FROM categories WHERE id = ?"; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { preparedStatement = connection.prepareStatement(sql); preparedStatement.setLong(1, categoryId); resultSet = preparedStatement.executeQuery(); if (resultSet.next()) { return Category.builder() .name(resultSet.getString("name")) .build(); } else { throw new NoSuchElementException("categoryId : " + categoryId); } } catch (SQLException e) { log.error("select Error", e); throw e; } finally { JdbcUtils.closeResultSet(resultSet); JdbcUtils.closeStatement(preparedStatement); } } public void update(Connection connection, Long categoryId, String name) throws SQLException { String sql = "UPDATE categories SET name = ? where id = ?"; PreparedStatement preparedStatement = null; try { preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1, name); preparedStatement.setLong(2, categoryId); } catch (SQLException e) { log.error("select Error", e); throw e; } finally { JdbcUtils.closeResultSet(null); JdbcUtils.closeStatement(preparedStatement); } } }
Service
@Slf4j @Service @RequiredArgsConstructor public class CategoryJdbcService { private final DataSource dataSource; private final CategoryJdbcRepository categoryJdbcRepository; //실제로 스프링내에서 트랜잭션 처리 로직 //DB연결 -> 오토커밋끄고 -> 로직실행 -> 정상:커밋 / 실패:롤백 -> 오토커밋 다시 true -> DB연결끊기 public void updateCategory(Long categoryId, String name) throws SQLException { Connection connection = dataSource.getConnection(); try { connection.setAutoCommit(false); Category category = categoryJdbcRepository.findById(connection, categoryId); if (Objects.nonNull(category)) { categoryJdbcRepository.update(connection, categoryId, name); } connection.commit(); } catch (SQLException e) { connection.rollback(); throw new IllegalStateException(e); } finally { connection.setAutoCommit(true); connection.close(); } } }
Controller
@RestController @RequiredArgsConstructor @RequestMapping("category") public class CategoryController { private final CategoryJdbcService categoryJdbcService; @PatchMapping("/{id}/name") public ApiResponse<JSONObject> updateByName(@RequestParam Long id, @RequestBody CategoryRequest request) throws SQLException { //예외처리는 추가적으로 필요 categoryJdbcService.updateCategory(id, request.getName()); return ApiResponse.Success(new JSONObject()); } }
3. 트랜잭션 관리 방식
프로그래밍 방식
Spring의PlatformTransactionManager 를 이용하여 트랜잭션 경계를 명시적으로 제어하는 방식
예시 코드
@Slf4j @Service @RequiredArgsConstructor public class ProductTransactionService { private final PlatformTransactionManager transactionManager; //jpa repository 그대로 사용 private final ProductRepository productRepository; public void updateProductStock(Long productId, Integer quantity) { TransactionStatus status = transactionManager.getTransaction( new DefaultTransactionDefinition()); try { Product product = productRepository.findById(productId) .orElseThrow(() -> new ServiceException(ServiceExceptionCode.NOT_FOUND_PRODUCT)); if (product.getStock() < quantity) { throw new ServiceException(ServiceExceptionCode.OUT_OF_STOCK_PRODUCT); } product.reduceStock(quantity); //@Transactional이 없으니 명시적으로 save productRepository.save(product); //위에 로직이 트랜잭션으로 묶여 있으면 true log.info("isTransaction: {}", TransactionSynchronizationManager.isActualTransactionActive()); transactionManager.commit(status); } catch (Exception e) { transactionManager.rollback(status); throw e; } } }
선언적 트랜잭션 (@Transactional)
- 특징
- 장동 경계처리
- 간결한 코드
- 세밀한 제어 옵션
- propagation : 트랜잭션 전파 규칙 설정
- isolation : 트랜잭션 격리 수준 설정
- readOnly : 읽기 전용 트랜잭션으로 최적화
- rollbackFor : 롤백 대상으로 처리 할 예외를 지정
- 예외 처리와 롤백
- 특징
선언적 트랜잭션과 프로그래밍 방식 비교
항목 선언적 트랜잭션 프로그래밍 방식 코드 간결성 어노테이션을 사용해 간단히 설정 직접 작성해야 하므로 복잡할 수 있음 유지보수 트랜잭션 로직이 코드와 분리됨 트랜잭션 로직과 비즈니스 로직 혼합 가능 유연성 전역 설정으로 동작 제어 가능 세부적인 제어 및 조건 처리 가능 적용 사례 일반적인 트랜잭션 관리에 적합 복잡한 트랜잭션 로직 구현에 적합
참고자료
- JDBC
- 트랜잭션
728x90
반응형
LIST
'TIL' 카테고리의 다른 글
7_3.롤백 전략과 커밋 관리 (0) | 2025.02.07 |
---|---|
7_2.트랜잭션 전파 옵션과 DB Read/Write 분리 (0) | 2025.02.07 |
6_5.데이터베이스 기반 작업 큐 실습 (0) | 2025.02.04 |
6_4.CAP 이론과 일관성 전략 (0) | 2025.02.04 |
6_3.DB 락 메커니즘 이해와 실습 (0) | 2025.02.02 |