public class TransactionProxy {
private MemberService target;
public void logic() { //트랜잭션 시작
TransactionStatus status = transactionManager.getTransaction(..);
try {
//실제 대상 호출
target.logic();
transactionManager.commit(status); //성공시 커밋
} catch (Exception e) {
transactionManager.rollback(status); //실패시 롤백
throw new IllegalStateException(e);
}
}
}
public class Service {
public void logic() {
//트랜잭션 관련 코드 제거, 순수 비즈니스 로직만 남음
bizLogic(fromId, toId, money);
}
}
org.springframework.transaction.annotation.Transactional
어드바이저: BeanFactoryTransactionAttributeSourceAdvisor
포인트컷: TransactionAttributeSourcePointcut
어드바이스: TransactionInterceptor
package hello.jbdc.service
import hello.jbdc.Log
import hello.jbdc.domain.Member
import hello.jbdc.repository.MemberRepositoryV3
import org.springframework.transaction.annotation.Transactional
import java.sql.SQLException
import kotlin.jvm.Throws
/*
트랜잭션 - 트랜잭션 매니저
*/
@Transactional
class MemberServiceV3_3(
private val memberRepository: MemberRepositoryV3
) :Log{
@Throws(SQLException::class)
fun accountTransfer(fromId: String, toId: String, money: Int){
bizLogic(fromId, toId, money)
}
@Throws(SQLException::class)
private fun bizLogic(fromId: String, toId: String, money: Int){
val fromMember = memberRepository.findById(fromId)
val toMember = memberRepository.findById(toId)
memberRepository.update(fromId, fromMember.money - money)
validation(toMember)
memberRepository.update(toId, fromMember.money + money)
}
private fun validation(toMember: Member){
if (toMember.memberId.equals("ex")){
throw IllegalStateException("이체 중 예외 발생")
}
}
}
package hello.jbdc.service
import hello.jbdc.Log
import hello.jbdc.connection.ConnectionConst
import hello.jbdc.domain.Member
import hello.jbdc.repository.MemberRepositoryV3
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.jdbc.datasource.DataSourceTransactionManager
import org.springframework.jdbc.datasource.DriverManagerDataSource
import java.sql.SQLException
@SpringBootTest
internal class MemberServiceV3_3Test: Log{
val MEMBER_A = "memberA"
val MEMBER_B = "memberB"
val MEMBER_EX = "ex"
@Autowired
lateinit var memberRepository: MemberRepositoryV3
@Autowired
lateinit var memberService: MemberServiceV3_3
@AfterEach
@Throws(SQLException::class)
fun after(){
memberRepository.delete(MEMBER_A);
memberRepository.delete(MEMBER_B);
memberRepository.delete(MEMBER_EX);
}
@TestConfiguration
class TestConfig{
@Bean
fun dataSource() = DriverManagerDataSource(ConnectionConst.URL, ConnectionConst.USERNAME, ConnectionConst.PASSWORD)
@Bean
fun transactionManager() = DataSourceTransactionManager(dataSource())
@Bean
fun memberRepositoryV3() = MemberRepositoryV3(dataSource())
@Bean
fun memberServiceV3_3() = MemberServiceV3_3(memberRepositoryV3())
}
@Test
fun AopCheck() {
logger.info("memberService class={}", memberService.javaClass)
logger.info("memberRepository class={}", memberRepository.javaClass)
Assertions.assertThat(AopUtils.isAopProxy(memberService)).isTrue()
Assertions.assertThat(AopUtils.isAopProxy(memberRepository)).isFalse()
}
@Test
@DisplayName("정상 이체")
@Throws(SQLException::class)
fun accountTransfer() {
//given
val memberA = Member(MEMBER_A, 10000)
val memberB = Member(MEMBER_B, 10000)
memberRepository.save(memberA)
memberRepository.save(memberB)
//when
memberService.accountTransfer(memberA.memberId,
memberB.memberId, 2000)
// //then
val findMemberA: Member = memberRepository.findById(memberA.memberId)
val findMemberB: Member = memberRepository.findById(memberB.memberId)
assertThat(findMemberA.money).isEqualTo(8000)
assertThat(findMemberB.money).isEqualTo(12000)
}
@Test
@DisplayName("이체 중 예외 발생")
fun accountTransferEx(){
//given
val memberA = Member(MEMBER_A, 10000)
val memberEx = Member(MEMBER_EX, 10000)
memberRepository.save(memberA)
memberRepository.save(memberEx)
// when
assertThatThrownBy {
memberService.accountTransfer(memberA.memberId, memberEx.memberId,
2000)
}
.isInstanceOf(IllegalStateException::class.java)
// then
val findMemberA = memberRepository.findById(memberA.memberId)
val findMemberEx = memberRepository.findById(memberEx.memberId)
assertThat(findMemberA.money).isEqualTo(10000)
assertThat(findMemberEx.money).isEqualTo(10000)
}
}
@Test
fun AopCheck() {
logger.info("memberService class={}", memberService.javaClass)
logger.info("memberRepository class={}", memberRepository.javaClass)
Assertions.assertThat(AopUtils.isAopProxy(memberService)).isTrue()
Assertions.assertThat(AopUtils.isAopProxy(memberRepository)).isFalse()
}
memberService class=class hello.jdbc.service.MemberServiceV3_3$
$EnhancerBySpringCGLIB$$...
memberRepository class=class hello.jdbc.repository.MemberRepositoryV3