Spring์ ์ฌ์ฉํ์ง ์๊ณ Transaction ํด๊ฒฐ
by rlaehddnd0422์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ํ ํธ๋์ญ์ ์ ์ํํ๊ธฐ ์ํด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์๋ฒ์ ์ฐ๊ฒฐ์ ์์ฒญํด ์ปค๋ฅ์ ์ ์ป๋๋ค๊ณ ๋ฐฐ์ ์ต๋๋ค.
์ด ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์๋ฒ๋ ์ฌ์ค ๋ด๋ถ์ ์ธ์ ์ ๋ง๋ญ๋๋ค. ๊ทธ๋ฆฌ๊ณ ํด๋น ์ปค๋ฅ์ ์ ์ด์ฉํ ๋ชจ๋ ์์ฒญ์ ๊ฐ ์ปค๋ฅ์ ์ ์ธ์ ์ ํตํด์ ์คํํ๊ฒ ๋ฉ๋๋ค. ๋ง์ฝ ์ปค๋ฅ์ ํ์ ์ด์ฉํ๋ค๋ฉด ์ปค๋ฅ์ ํ์ด 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 ํธ๋์ญ์ ์ ์ ์ฉํ๋ ค๋ฉด ์๋น์ค ๊ณ์ธต์ด ๋งค์ฐ ์ง์ ๋ถํด์ง๊ณ , ์๊ฐ๋ณด๋ค ๋ณต์กํ ์ฝ๋๋ฅผ ์๊ตฌํ๊ณ ์ถ๊ฐ๋ก ์ปค๋ฅ์ ์ ์ ์งํ๋๋ก ์ฝ๋๋ฅผ ๋ณ๊ฒฝํ๋ ๊ฒ๋ ์ฌ์ด ์ผ์ ์๋๋๋ค. ๋ค์ ํฌ์คํ ์์ ์คํ๋ง์ ์ฌ์ฉํด์ ์ด๋ฐ ๋ฌธ์ ๋ค์ ํ๋์ฉ ํด๊ฒฐํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
<์ฐธ๊ณ ์๋ฃ>
'๐ Backend > DB Access' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
ํธ๋์ญ์ AOP + ์์ธ ์ ํ ์ ์ฉํ Service ๊ณ์ธต ๋ถ๋ฆฌ (0) | 2023.04.06 |
---|---|
Spring ํธ๋์ญ์ AOP - @Transactional ์ฌ์ฉ (0) | 2023.04.05 |
ํธ๋์ญ์ ์ธํฐํ์ด์ค, ํธ๋์ญ์ ํ ํ๋ฆฟ์ ์ด์ฉํ Service ๊ณ์ธต์ Transaction ๋ฌธ์ ํด๊ฒฐ (0) | 2023.04.05 |
์ปค๋ฅ์ ํ by using DataSource Interface (0) | 2023.04.03 |
JDBC by using DriverManager (0) | 2023.04.02 |
๋ธ๋ก๊ทธ์ ์ ๋ณด
Study Repository
rlaehddnd0422