일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 인프런
- .NET
- 고차함수
- 인프런강좌
- 자바스크립트파라미터
- 인프런강의
- slice
- 틱택토구현
- 이벤트리스너
- 자바스크립트틱택토
- 코딩
- 자바스크립트
- c#
- 자바스크립트recude
- 자바스크립트함수
- HTTP
- NPM
- 객체리터럴
- 인프런무료강좌
- 인터넷프로토콜
- sort
- Blazor
- 인프런자바스크립트
- 인프런인강
- 자바스크립트객체리터럴
- 비주얼스튜디오
- 객체의비교
- EntityFramework
- 제로초
- 콜백함수
- Today
- Total
샐님은 개발중
스프링과 문제 해결 - 트랜잭션 - 트랜잭션 매니저 본문
문제점들
애플리케이션 구조
@Controller(웹,서블릿, HTTP 관련 처) - @Service(가급적 순수 자바 코드로 작성, 비지니스 로직담당) - @Repositroy DB 접근 계층 - DB 서버
MemberServiceV2.java
package hello.jdbc.service;
import hello.jdbc.domain.Member;
import hello.jdbc.repository.MemberRepositoryV1;
import hello.jdbc.repository.MemberRepositoryV2;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import javax.sql.DataSource;
import javax.swing.*;
import java.sql.Connection;
import java.sql.SQLException;
@Slf4j
@AllArgsConstructor
public class MemberServiceV2 {
private final DataSource dataSource;
private final MemberRepositoryV2 memberRepository;
public void accountTransfer(String fromId, String toId, int money) throws SQLException {
Connection con = dataSource.getConnection();
try{
con.setAutoCommit(false); //트랜잭션 시작
// 비지니스 로직
bizLogic(con, fromId, toId, money);
con.commit(); //성공시 커밋
}catch (Exception e){
con.rollback(); //실패시 롤백
throw new IllegalStateException(e);
}finally {
release(con);
}
}
private void bizLogic(Connection con, String fromId, String toId, int money) throws SQLException {
Member fromMember = memberRepository.findById(con,fromId);
Member toMember = memberRepository.findById(con,toId);
memberRepository.update(con, fromId, fromMember.getMoney() -money);
//validation(toMember);
memberRepository.update(con,toId,toMember.getMoney()+money);
}
private void validation(Member toMember){
if(toMember.getMemberId().equals("ex")){
throw new IllegalStateException("이체중 예외 발생");
}
}
/**
* 커넥션 사용 후 종료. 커넥션 풀을 사용하면 con.close()를 호출했을 때 풀에 반납됨.
* 현재 수동 커밋 모드 동작하기 때문에 풀에 돌려주기 전에 기본값인 자동 커밋 모드로 변경해야함
*
*/
private void release(Connection con) {
if (con != null) {
try {
con.setAutoCommit(true); //커넥션 풀 고려
con.close();
} catch (Exception e) {
log.info("error", e);
}
}
}
}
트랜잭션은 비지니스 로직이 있는 서비스 계층에서 시작하는 게좋다.
그런데 트랙잰션을 사용하기 위해 javax.sql.DataSource , java.sql.Connection , java.sql.SQLException 등의 jdbc 기술에 의존해야 한다. 결과적으로 비지니스 로직 보다 jbdc를 사용하는 코드가 더 많다. -> 향후 jdbc 에서 jpa 등으로 다른 기술을 사용하려면 모두 변경해야하는 문제 가 발생하고 유지보수가 어렵다.
스프링은 이러한 문제들을 해결해 주는 다양한 방법과 기술들을 제공한다.
트랜잭션 추상화
스프링 트랜잭션 추상화 핵심은 PlatformTransactionManager 인터페이스 이다.
package org.springframework.transaction;
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
getTransaction() : 트랜잭션 시작
commit(): 트랜잭션 커밋
rollback() : 트랜잭션 롤백
PlatformTransactionManager 인터페이스와 구현체를 포함해서 트랜잭션 매니저 줄여서 얘기하겠다.
리소스 동기화
트랜잭션 유지위해 트랜잭션의 처음과 끝은 같은 데이터베이스 커넥션을 유지해야함.
이전 : 파라미터로 커넥션을 전달
스프링은 트랜잭션 동기화 매니저 제공
- 멀티쓰레드 상황에서 안전하게 커넥션을 동기화 하게 해줌. 따라서 파라미터로 커넥션을 전달하지 않아도 됨.
동작 방식
1. 트랜잭션 매니저는 데이터소스를 통해 커넥션만들고 트랜잭션을 시작
2.트랜잭션 매니저는 트랜잭션이 시작된 커넥션을 트랜잭션 동기화 매니저에 보관
/**
* 트랜잭션 - 트랜잭션 매니저
*/
class MemberServiceV3Test_1 {
public static final String MEMBER_A = "memberA";
public static final String MEMBER_B = "memberB";
public static final String MEMBER_EX = "ex";
private MemberRepositoryV3 memberRepository;
private MemberServiceV3_1 memberService;
@BeforeEach
void before(){
DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
memberRepository = new MemberRepositoryV3(dataSource);
PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
memberService = new MemberServiceV3_1(transactionManager,memberRepository);
}
1)test.java 에서 accountTransfer 호출 - 2) memeberService.accountTransfer 실행
@Test
@DisplayName("정상 이체")
void accountTransfer() throws SQLException {
Member memberA = new Member(MEMBER_A, 10000);
Member memberB = new Member(MEMBER_B, 10000);
memberRepository.save(memberA);
memberRepository.save(memberB);
memberService.accountTransfer( memberA.getMemberId(),memberB.getMemberId(),2000);
Member findMemberA = memberRepository.findById(memberA.getMemberId());
Member findMemberB = memberRepository.findById(memberB.getMemberId());
assertThat(findMemberA.getMoney()).isEqualTo(8000);
assertThat(findMemberB.getMoney()).isEqualTo(12000);
}
public Member findById(String memberId) throws SQLException {
String sql = "select * from member where member_id = ?";
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = getConnection();
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 {
close(con, pstmt, rs);
}
}
3 리포지토리에서 트랜잭션 동기화 매니저에 보관된 커넥션을 꺼내서 사용. 따라서 파라미터로 커넥션 전달 하지 않아도 된다.
private final DataSource dataSource;
public MemberRepositoryV3(DataSource dataSource) {
this.dataSource = dataSource;
}
public Member findById(String memberId) throws SQLException {
String sql = "select * from member where member_id = ?";
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = getConnection();
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;
}
}
private Connection getConnection() throws SQLException {
// 주의! 트랜잭션 동기화를 사용하려면 DataSourceUtils 사용해야함. 트랜잭션 동기화에 보관된커넥션을 꺼낸다.
// 트랜잭션 동기화 매니저 : 멀티쓰레드 상황에서 안전하게 커넥션을 동기화 할 수 있게 한다.
Connection con = DataSourceUtils.getConnection(dataSource);
log.info("get connection={}, class={}", con, con.getClass());
return con;
}
4. 트랜잭션이 종료되면 트랜잭션 매니저는 트랜잭션 동기화 매니저에 보관된 커넥션을 통해 트랜잭션 종료, 커넥션닫는다.
'스프링 DB 1편 - 데이터 접근 핵심 원리' 카테고리의 다른 글
ItemController 에서 save 로직 리팩토링 (0) | 2023.07.24 |
---|---|
JPA - Map 데이터를 List로 변환 해서 데이터 저장 (0) | 2023.07.24 |
트랜잭션 문제 해결 - 트랜잭션 템플릿 (0) | 2023.07.21 |
트랜잭션 이해 (0) | 2023.07.20 |
커넥션 풀과 데이터 소스 이해 (0) | 2023.07.20 |