Springμ μ¬μ©νμ§ μκ³ Transaction ν΄κ²°
μ ν리μΌμ΄μ μμ λ°μ΄ν°λ² μ΄μ€μ λν νΈλμμ μ μννκΈ° μν΄μ λ°μ΄ν°λ² μ΄μ€ μλ²μ μ°κ²°μ μμ²ν΄ 컀λ₯μ μ μ»λλ€κ³ λ°°μ μ΅λλ€.
μ΄ λ λ°μ΄ν°λ² μ΄μ€ μλ²λ μ¬μ€ λ΄λΆμ μΈμ μ λ§λλλ€. κ·Έλ¦¬κ³ ν΄λΉ 컀λ₯μ μ μ΄μ©ν λͺ¨λ μμ²μ κ° μ»€λ₯μ μ μΈμ μ ν΅ν΄μ μ€ννκ² λ©λλ€. λ§μ½ 컀λ₯μ νμ μ΄μ©νλ€λ©΄ 컀λ₯μ νμ΄ 10κ°μ 컀λ₯μ μ μμ±νλ©΄ μΈμ λ 10κ°κ° λ§λ€μ΄μ§κ² μ£ ?
νΈλμμ μ¬μ©λ²
- νΈλμμ μ μ¬μ©νκΈ° μν΄μ λ°μ΄ν°λ² μ΄μ€μμ set autocommit falseλ₯Ό νΈμΆν΄μ μΈμ μμ λͺ λ Ήμ΄ μν ν μλμΌλ‘ commitλμ§ μλλ‘ μ€μ ν΄ μ£Όμ΄μΌ ν©λλ€. set autocommit false μ€μ μ νΈλμμ μ μμνλ€κ³ νν
- μ°Έκ³ λ‘ νλ² μ€μ ν΄λλ©΄ ν΄λΉ μΈμ μμλ μ€μ κ°μ΄ κ³μ μ μ§ λ©λλ€.
- λ°μ΄ν° λ³κ²½ 쿼리λ₯Ό μ€ννκ³ κ²°κ³Όλ₯Ό λ°μνλ €λ©΄ commitμ νΈμΆνκ³ , κ²°κ³Όλ₯Ό λ°μνκ³ μΆμ§ μμΌλ©΄ rollbackμ νΈμΆνλ©΄ λ©λλ€.
κ³μ’μ΄μ²΄ μμ λ₯Ό ν΅ν΄ μ€νλ§μ μ¬μ©νμ§ μμ μλ°μμ μ΄λ»κ² νΈλμμ μ μνν μ μλμ§ μμ보λλ‘ νκ² μ΅λλ€.
κ³μ’μ΄μ²΄ λ¬Έμ
1. κΈ°λ³Έ λ°μ΄ν° μ λ ₯
set autocommit true;
delete from member;
insert into member(member_id, money) values ('memberA',10000);
insert into member(member_id, money) values ('memberB',10000);
2. κ³μ’μ΄μ²΄ μ€ν ( memberA -> memberBλ‘ 2000μ μ‘κΈ) λμ€ μ€λ₯ λ°μ
μΈμ
1λ² νΈλμμ
set autocommit false; // νΈλμμ
μμ
update member set money=10000 - 2000 where member_id = 'memberA'; //μ±κ³΅
update member set money=10000 + 2000 where member_iddd = 'memberB'; //쿼리 μμΈ λ°μ
- ν λ²μ νΈλμμ μμ μ€λ₯κ° λ°μν κ²½μ° commitμ μννκ² λλ©΄ memberAμ moneyλ§ 2000μμ΄ μ€μ΄λλ λ¬Έμ κ° λ°μν©λλ€.
- μ΄λ κ² μ€κ°μ λ¬Έμ κ° λ°μν κ²½μ°μλ commitμ νΈμΆνλκ² μλλΌ rollbackμ νΈμΆν΄μ λ°μ΄ν°λ₯Ό νΈλμμ μμ μμ μΌλ‘ μμ볡ꡬ ν΄μΌν©λλ€. rollbackμ μννλ©΄ λ€μ 1λ² μνλ‘ λμκ°λλ€.
- κ·Έλ°λ° λ§μ½ μΈμ 1μ΄ νΈλμμ μ μμνκ³ λ°μ΄ν°λ₯Ό μμ νλ λμ μμ§ μ»€λ°μ μννμ§ μμλλ°, μΈμ 2μμ λμμ κ°μ λ°μ΄ν°λ₯Ό μμ νκ²λλ©΄ νΈλμμ μ μμμ±μ΄ κΉ¨μ§κ² λκ³ λ§μ½ μΈμ 1μ΄ μ€κ°μ rollbackμ νκ² λλ©΄ μΈμ 2λ μλͺ»λ λ°μ΄ν°λ₯Ό μμ νλ λ¬Έμ κ° λ°μν©λλ€.
μ΄λ° λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν΄ μΈμ μ΄ νΈλμμ μ μμνκ³ λ°μ΄ν°λ₯Ό μμ νλ λμ λ€λ₯Έ μΈμ μμ ν΄λΉ λ°μ΄ν°λ² μ΄μ€μ μ κ·Όνμ§ λͺ»νλλ‘ λ§μμΌ ν©λλ€. μ΄λ lockμ ν΅ν΄ μ€μ ν μ μμ΅λλ€.
DB λ½ - update μμ case
κΈ°λ³Έ λ°μ΄ν°κ° λ°μ΄ν°λ² μ΄μ€μ μ μ₯λμ΄ μλ μν©μμ
μΈμ 1μμ νΈλμμ μ μμνκ³ memberA moneyλ₯Ό 500μμΌλ‘ μ λ°μ΄νΈ. (μμ§ commitμ νμ§ μμ μν)
μ΄ λ memberA rowμ λν λ½μ μΈμ 1μ΄ κ°μ§κ² λ©λλ€.
μΈμ μ΄ νΈλμμ μ μμνκ³ DB μμ μ κ²½μ° commitνκΈ° μ κΉμ§ μλμΌλ‘ λ½μ κ°μ§λλ€.
μΈμ 2λ μΈμ 1μ΄ λ½μ κ°μ§κ³ μκΈ° λλ¬Έμ μΈμ 1μ΄ μ»€λ°νκ±°λ λ‘€λ°±νμ§ μμμΌλ―λ‘ λ°μ΄ν°λ₯Ό μμ ν μ μμ΅λλ€.
(commitμ΄λ rollbackμ μνν΄μΌ λ½μ λ°λ©ν©λλ€.)
- SET LOCK_TIMEOUT = 60000 : 60μ΄ λ΄μ λ½μ μ»μ§ λͺ»νλ©΄ μμΈ λ°μ (μ¦ , μΈμ 1μμ 60μ΄ λ΄μ 컀λ°μ΄λ λ‘€λ°±μ μννμ§ μμ κ²½μ° μμΈκ° λ°μ)
DB λ½ - select μ‘°ν case
DB Updateκ° μλ select μ‘°νλ₯Ό ν λμλ updateμ λ¬λ¦¬ μλμΌλ‘ λ½μ κ°μ§ μκΈ° λλ¬Έμ for update ꡬ문μ μΆκ°μ μΌλ‘ μ€μ ν΄ λ½μ μλμΌλ‘ μ»κ² ν΄μ£Όμ΄μΌ ν©λλ€.
select μ‘°νλ₯Ό ν λμ lockμ μ»μ΄μΌ νλ κ²½μ°λ μΈμ μΌκΉμ?
μλ₯Ό λ€μ΄μ μ ν리μΌμ΄μ λ‘μ§μμ memberA μ κΈμ‘μ μ‘°νν λ€μμ μ΄ κΈμ‘ μ λ³΄λ‘ μ ν리μΌμ΄μ μμ μ΄λ€ κ³μ°μ μννλλ°, μ΄ κ³μ°μ΄ λκ³Ό κ΄λ ¨λ λ§€μ° μ€μν κ³μ°μ΄μ΄μ κ³μ°μ μλ£ν λ κΉμ§ memberA μ κΈμ‘μ λ€λ₯Έκ³³μμ λ³κ²½νλ©΄ μλλ κ²½μ° μ΄λ΄ λ μ‘°ν μμ μ λ½μ νλν΄μΌ ν©λλ€.
μ΄λ΄ λλ select for update ꡬ문μ μ¬μ©νλ©΄ λ©λλ€.
μ΄λ κ² νλ©΄ μΈμ 1μ΄ μ‘°ν μμ μ λ½μ κ°μ Έκ°λ²λ¦¬κΈ° λλ¬Έμ λ€λ₯Έ μΈμ μμ ν΄λΉ λ°μ΄ν°λ₯Ό λ³κ²½ν μ μμ΅λλ€.

- μΈμ 1μ΄ commitμ ν΄μΌ μΈμ 2λ‘ λ½μ΄ λμ΄κ° updateλ₯Ό μνν μ μμ΅λλ€.
- μΈμ 2 λν νΈλμμ μ΄λ―λ‘ commitμ μνν΄μΌ updateκ° λ°μλ©λλ€.
μ ν리μΌμ΄μ μμ νΈλμμ μ μ©νκΈ°
μ΄μ μ½λλ₯Ό ν΅ν΄ μ€νλ§ μλ μλ°μμ DBμ μ κ·Όν΄ κ³μ’μ΄μ²΄ νΈλμμ μ ꡬνν΄λ³΄κ³ μ μ μ²λ¦¬λκ²½μ°μ μμΈκ° λ°μν κ²½μ° λ κ°μ§ μΌμ΄μ€μ λν΄ ν μ€νΈ ν΄λ³΄λλ‘ νκ² μ΅λλ€.
- κ³μ’μ΄μ²΄μ κ²½μ° ν νΈλμμ λ΄μμ λ λ©€λ²λ₯Ό μ°Ύμ(findById), λ λ©€λ²μ λμ κ°±μ ν΄μ£Όμ΄μΌ(update) ν©λλ€.
- νμ§λ§ μ΄ λ μ£Όμν΄μΌ ν μ μ νΈλμμ μ΄κΈ° λλ¬Έμ λμΌν 컀λ₯μ λ€μλ§ν΄ κ°μ μΈμ μμ μ΄λ£¨μ΄μ ΈμΌ ν©λλ€.
- λ°λΌμ μ΄μ κ³Ό λ¬λ¦¬ λ©€λ²λ₯Ό μ°Ύκ³ (findById), κ°±μ (update) ν΄ μ€ λμ 컀λ₯μ μ μλ‘ λ¦¬ν΄λ°λ κ²μ΄ μλ ν 컀λ₯μ μ μ μ¬μ©νλλ‘ μ€μ ν΄ μ£Όμ΄μΌ ν©λλ€.
public void accountTransfer(String fromId, String toId, int money) throws SQLException {
Connection con = dataSource.getConnection();
try
{
con.setAutoCommit(false); // νΈλμμ
μμ
buisnesslogic(con, fromId, toId, money);
con.commit();
} catch (Exception e) {
log.error("error detected!");
con.rollback();
throw new IllegalStateException(e);
} finally {
release(con);
}
}
private void release(Connection con) {
if(con !=null)
{
try{
con.setAutoCommit(true); // 컀λ₯μ
ν κ³ λ €
con.close();
}catch (Exception e)
{
log.error("error", e);
}
}
}
private void buisnesslogic(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);
}
- νΈλμμ μ μμνκΈ° μν΄ con.setAutoCommitμ falseλ‘ μ€μ ν©λλ€
- κ³μ’μ΄μ²΄ μν λμ€ μμΈκ° λ°μν κ²½μ° rollbackνλλ‘ μ€μ ν©λλ€.
- νΈλμμ μ΄ μ»€λ°λ μ΄νμλ autocommitμ trueλ‘ λ€μ μ€μ ν΄μ£Όκ³ conμ λ°λ©ν©λλ€.
- validation(toMember) : toMemberμ member_idκ° "ex"μΈ κ²½μ° μμΈλ₯Ό λμ§λλ‘ μ€μ For Test Code
μ°Έκ³ λ‘ findbyIdμ update λ©μλμλ λ©μλ μμλΆλΆμμ 컀λ₯μ μ μμ±νμ§ μκ³ νλΌλ―Έν°λ‘ λ°κ² ν ν, λ‘μ§ μν ν 컀λ₯μ μ λ°λ©νμ§ μλλ‘ μ€μ νμ΅λλ€. κ·Έ μΈ λ‘μ§μ λμΌνκΈ° λλ¬Έμ λ°λ‘ μ½λλ₯Ό 첨λΆνμ§ μμμ΅λλ€.
ν μ€νΈ μ§ν
@BeforeEachμμ
컀λ₯μ μ λ°μμ¬ dataSourceλ‘ Hikari CP μμ± λ° url, usename, password μ€μ
Repository -> HikariCP datasource,
Service -> Repository, Hikari datasource μ£Όμ
@AfterEachμμ repository μ΄κΈ°ν
1. μ μμ μΌλ‘ μ΄μ²΄ λ κ²½μ°
@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);
//when
log.info("START TX");
memberService.accountTransfer(memberA.getMemberId(),memberB.getMemberId(),2000);
log.info("END TX");
// then
Member findMemberA = memberRepository.findById(memberA.getMemberId());
Member findMemberB = memberRepository.findById(memberB.getMemberId());
Assertions.assertThat(findMemberA.getMoney()).isEqualTo(8000);
Assertions.assertThat(findMemberB.getMoney()).isEqualTo(12000);
}
- μ μ μ΄μ²΄λμκΈ° λλ¬Έμ Aμ moneyλ 8000, Bμ moneyλ 12000μΌλ‘ μ€μ λ κ²μ νμΈν μ μμ΅λλ€.
2. μ΄μ²΄μ€ μμΈκ° λ°μν κ²½μ°
@Test
@DisplayName("μ΄μ²΄μ€ μμΈ λ°μ")
void accountTransferEx() throws SQLException {
// given
Member memberA = new Member(MEMBER_A, 10000);
Member memberEx = new Member(MEMBER_EX, 10000);
memberRepository.save(memberA);
memberRepository.save(memberEx);
//when
// memberService.accountTransfer(memberA.getMemberId(),memberEx.getMemberId(),2000);
Assertions.assertThatThrownBy(
() -> memberService.accountTransfer(memberA.getMemberId(),memberEx.getMemberId(),2000))
.isInstanceOf(IllegalStateException.class);
// then
Member findMemberA = memberRepository.findById(memberA.getMemberId());
Member findMemberEx = memberRepository.findById(memberEx.getMemberId());
log.info("findMemberA.money = {} ",findMemberA.getMoney());
log.info("findMemberEx.money = {} ",findMemberEx.getMoney());
Assertions.assertThat(findMemberA.getMoney()).isEqualTo(10000);
Assertions.assertThat(findMemberEx.getMoney()).isEqualTo(10000);
}
- μμΈ λ°μνλ©΄ λ‘€λ°±νλλ‘ μ€μ νκΈ° λλ¬Έμ μ μνλ‘ λ³΅κ΅¬λ κ²μ νμΈν μ μμ΅λλ€.
μ ν리μΌμ΄μ μμ DB νΈλμμ μ μ μ©νλ €λ©΄ μλΉμ€ κ³μΈ΅μ΄ λ§€μ° μ§μ λΆν΄μ§κ³ , μκ°λ³΄λ€ 볡μ‘ν μ½λλ₯Ό μꡬνκ³ μΆκ°λ‘ 컀λ₯μ μ μ μ§νλλ‘ μ½λλ₯Ό λ³κ²½νλ κ²λ μ¬μ΄ μΌμ μλλλ€. λ€μ ν¬μ€ν μμ μ€νλ§μ μ¬μ©ν΄μ μ΄λ° λ¬Έμ λ€μ νλμ© ν΄κ²°ν΄λ³΄λλ‘ νκ² μ΅λλ€.
<μ°Έκ³ μλ£>
μ€νλ§ DB 1νΈ - λ°μ΄ν° μ κ·Ό ν΅μ¬ μ리 - μΈνλ° | κ°μ
λ°±μλ κ°λ°μ νμν DB λ°μ΄ν° μ κ·Ό κΈ°μ μ κΈ°μ΄λΆν° μ΄ν΄νκ³ , μμ±ν μ μμ΅λλ€. μ€νλ§ DB μ κ·Ό κΈ°μ μ μ리μ ꡬ쑰λ₯Ό μ΄ν΄νκ³ , λ κΉμ΄μλ λ°±μλ κ°λ°μλ‘ μ±μ₯ν μ μμ΅λλ€., - κ°μ
www.inflearn.com