[트랜잭션] - 두개가 모두 성공해야 성공, 한번에 커밋& 롤백
원자성: 트랜잭션 내에서 실행한 작업들은 마치 하나의 작업인 것처럼 모두 성공 하거나 모두 실패
일관성: 모든 트랜잭션은 일관성 있는 데이터베이스 상태를 유지
격리성: 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않도록 격리. 예를 들어 동시에 같은 데이터를 수정하지 못하도록 해야 한다. 격리성은 동시성과 관련된 성능 이슈로 인해 트랜잭션 격리 수준 (Isolation level)을 선택
지속성: 트랜잭션을 성공적으로 끝내면 그 결과가 항상 기록되어야 한다. 중간에 시스템에 문제가 발생해도 데이터베이스 로그 등을 사용해서 성공한 트랜잭션 내용을 복구
[DB연결 구조와 세션]
[트랜잭션 사용]
데이터 변경 쿼리를 실행하고 데이터베이스에 그 결과를 반영하려면 커밋 명령어인 commit 을 호출, 결과를 반영하고 싶지 않으면 롤백 명령어인 rollback 을 호출. 커밋을 호출하기 전까지는 임시로 데이터를 저장.
수동 커밋 모드로 설정하는 것 = 트랜잭션을 시작
[DB락]
세션이 트랜잭션을 시작하고 데이터를 수정하는 동안에는 커밋이나 롤백 전까지 다른 세션에서 해당 데이터를 수정할 수 없게 막아야. , 세션이 트랜잭션을 시작하고 데이터를 수정하는 동안에는 커밋이나 롤백 전까지 다른 세션에서 해당 데이터를 수정할 수 없게 막아야됨
<MemberServieceV1Test>
class MemberServiceV1Test {
public static final String MEMBER_A = "memberA";
public static final String MEMBER_B = "memberB";
public static final String MEMBER_EX = "ex";
private MemberRepositoryV1 memberRepository;
private MemberServiceV1 memberService;
@BeforeEach
void before() {
DriverManagerDataSource dataSource = new DriverManagerDataSource(URL,
USERNAME, PASSWORD); //memberRepository가 필요로 하니까 dataSource를 가져오고
memberRepository = new MemberRepositoryV1(dataSource);
memberService = new MemberServiceV1(memberRepository);
}
@AfterEach
void after() throws SQLException {
memberRepository.delete(MEMBER_A);
memberRepository.delete(MEMBER_B);
memberRepository.delete(MEMBER_EX);
}
@Test
@DisplayName("정상 이체")
void accountTransfer() throws SQLException {
//given
Member memberA = new Member(MEMBER_A, 10000);
Member memberB = new Member(MEMBER_B, 10000);
memberRepository.save(memberA);
memberRepository.save(memberB);
//whern
memberService.accountTransfer(memberA.getMemberId(), memberB.getMemberId(), 2000);
//then -id가져오고
Member findMemberA = memberRepository.findById(memberA.getMemberId());
Member findMemberB = memberRepository.findById(memberB.getMemberId());
assertThat(findMemberA.getMoney()).isEqualTo(8000);
assertThat(findMemberB.getMoney()).isEqualTo(12000);
}
@Test
@DisplayName("이체중 예외 발생")
void accountTransferException() throws SQLException {
//given
Member memberA = new Member(MEMBER_A, 10000);
Member memberEx = new Member(MEMBER_B, 10000);
memberRepository.save(memberA);
memberRepository.save(memberEx);
//whern
assertThatThrownBy(() ->
memberA.getMemberId(), memberEx.getMemberId(), 2000).isInstanceOf(IllegalStateException.class);
memberService.accountTransfer(memberA.getMemberId(), memberEx.getMemberId(), 2000);
//then -id가져오고
Member findMemberA = memberRepository.findById(memberA.getMemberId());
Member findMemberB = memberRepository.findById(memberEx.getMemberId());
assertThat(findMemberA.getMoney()).isEqualTo(8000);
assertThat(findMemberB.getMoney()).isEqualTo(10000);
}
}
(트랜잭션의 시작 : set Auto Commit = false)
<MemberRepositoryV2> - V1에 추가
/같은 파라미터를 사용해야 같은 커넥션 유지
public Member findById( Connection con,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); //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 { //data가 없다
throw new NoSuchElementException("member not found memberId=" +
memberId);
}
} catch (SQLException e) {
log.error("db error", e);
throw e;
} finally {
//connection은 여기서 닫지 않는다!
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(pstmt);
//JdbcUtils.closeConnection(con); 여기서 커넥션을 종료하면 안됨
}
}
public void update(Connection con, String memberId, int money) throws SQLException {
String sql = "update member set money=? where member_id=?";
//Connection con = null;
PreparedStatement pstmt = null;
try {
//con = getConnection();
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);
//JdbcUtils.closeConnection(con);
}
}
<MemberServiceV2>
<MemberRepositoryV2Test>
'Spring 강의 > DB' 카테고리의 다른 글
[스프링 DB 2편] - (1) 데이터 접근 기술 (0) | 2022.08.28 |
---|---|
[스프링 DB 1편] - (2) 커넥션풀과 데이터소스 (0) | 2022.04.20 |
[스프링 DB 1편] - (1) JDBC 이해 (0) | 2022.04.18 |