package hello.jbdc.service
import hello.jbdc.domain.Member
import hello.jbdc.repository.MemberRepositoryV1
class MemberServiceV1(
private val memberRepository: MemberRepositoryV1
) {
fun accountTransfer(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, toMember.money + money);
}
private fun validation(toMember: Member){
if (toMember.memberId.equals("ex")){
throw IllegalStateException("이체 중 예외 발생")
}
}
}
package hello.jbdc.service
import hello.jbdc.connection.ConnectionConst
import hello.jbdc.domain.Member
import hello.jbdc.repository.MemberRepositoryV1
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.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.springframework.jdbc.datasource.DriverManagerDataSource
import java.sql.SQLException
internal class MemberServiceV1Test{
val MEMBER_A = "memberA"
val MEMBER_B = "memberB"
val MEMBER_EX = "ex"
lateinit var memberRepository: MemberRepositoryV1
lateinit var memberService: MemberServiceV1
@BeforeEach
fun before(){
val dataSource = DriverManagerDataSource(ConnectionConst.URL,
ConnectionConst.USERNAME, ConnectionConst.PASSWORD)
memberRepository = MemberRepositoryV1(dataSource)
memberService = MemberServiceV1(memberRepository)
}
@AfterEach
fun after(){
memberRepository.delete(MEMBER_A);
memberRepository.delete(MEMBER_B);
memberRepository.delete(MEMBER_EX);
}
@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("이체 중 예외 발생")
@Throws(SQLException::class)
fun accountTransferEx(){
//given
val memberA = Member(MEMBER_A, 10000)
val memberEx = Member(MEMBER_EX, 10000)
// 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(8000)
assertThat(findMemberEx.money).isEqualTo(10000)
}
}
package hello.jbdc.repository
import hello.jbdc.Log
import hello.jbdc.domain.Member
import org.springframework.jdbc.support.JdbcUtils
import java.sql.*
import javax.sql.DataSource
import kotlin.jvm.Throws
class MemberRepositoryV2(
dataSource: DataSource,
):Log {
private val dataSource: DataSource
init {
this.dataSource = dataSource
}
fun save(member: Member): Member{
val sql: String = "insert into member(member_id, money) values (?, ?)"
lateinit var con: Connection
lateinit var pstmt: PreparedStatement
try{
con = getConnection()
pstmt = con.prepareStatement(sql)
pstmt.setString(1, member.memberId)
pstmt.setInt(2, member.money)
pstmt.executeUpdate()
return member
} catch(e: SQLException){
logger.error("db error", e)
e.printStackTrace()
throw e
} finally {
close(con, pstmt, null);
}
}
@Throws(SQLException::class)
fun findById(memberId: String): Member{
val sql = "select * from member where member_id = ?"
lateinit var con: Connection
lateinit var pstmt: PreparedStatement
lateinit var rs: ResultSet
try{
con = getConnection()
pstmt = con.prepareStatement(sql)
pstmt.setString(1, memberId)
rs = pstmt.executeQuery()
if (rs.next()){
val member = Member(
memberId = rs.getString("member_id"),
money = rs.getInt("money")
)
return member
}else{
throw NoSuchElementException("member not found memberId=" + memberId)
}
} catch (e: SQLException){
logger.error("db error", e)
throw e
} finally {
close(con, pstmt, null)
}
}
@Throws(SQLException::class)
fun findById(con: Connection, memberId: String): Member{
val sql = "select * from member where member_id = ?"
lateinit var pstmt: PreparedStatement
lateinit var rs: ResultSet
try{
pstmt = con.prepareStatement(sql)
pstmt.setString(1, memberId)
rs = pstmt.executeQuery()
if (rs.next()){
val member = Member(
memberId = rs.getString("member_id"),
money = rs.getInt("money")
)
return member
}else{
throw NoSuchElementException("member not found memberId=" + memberId)
}
} catch (e: SQLException){
logger.error("db error", e)
throw e
} finally {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(pstmt);
}
}
@Throws(SQLException::class)
fun update(memberId: String, money: Int){
val sql = "update member set money=? where member_id=?"
lateinit var con: Connection
lateinit var pstmt: PreparedStatement
try {
con = getConnection();
pstmt = con.prepareStatement(sql);
pstmt.setInt(1, money);
pstmt.setString(2, memberId);
val resultSize = pstmt.executeUpdate()
logger.info("resultSize={}", resultSize)
} catch (e: SQLException){
logger.error("db error", e)
throw e
} finally {
close(con, pstmt, null)
}
}
@Throws(SQLException::class)
fun update(con: Connection, memberId: String, money: Int){
val sql = "update member set money=? where member_id=?"
lateinit var pstmt: PreparedStatement
try {
pstmt = con.prepareStatement(sql);
pstmt.setInt(1, money);
pstmt.setString(2, memberId);
val resultSize = pstmt.executeUpdate()
logger.info("resultSize={}", resultSize)
} catch (e: SQLException){
logger.error("db error", e)
throw e
} finally {
JdbcUtils.closeStatement(pstmt);
}
}
@Throws(SQLException::class)
fun delete(memberId: String){
val sql = "delete from member where member_id=?"
lateinit var con: Connection
lateinit var pstmt: PreparedStatement
try {
con = getConnection();
pstmt = con.prepareStatement(sql);
pstmt.setString(1, memberId);
pstmt.executeUpdate()
} catch (e: SQLException){
logger.error("db error", e)
throw e
} finally {
close(con, pstmt, null);
}
}
private fun getConnection(): Connection {
val con = dataSource.connection
logger.info("get connection={}, class={}", con, con.javaClass)
return con
}
private fun close(con: Connection?, stmt: Statement?, rs: ResultSet?) {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(stmt);
JdbcUtils.closeConnection(con);
}
}
package hello.jbdc.service
import hello.jbdc.Log
import hello.jbdc.domain.Member
import hello.jbdc.repository.MemberRepositoryV2
import javax.sql.DataSource
import java.sql.Connection
import java.sql.SQLException
import kotlin.jvm.Throws
class MemberServiceV2(
private val dataSource: DataSource,
private val memberRepository: MemberRepositoryV2,
) :Log{
fun accountTransfer(fromId: String, toId: String, money: Int){
val con = dataSource.connection
try {
con.autoCommit = false
bizLogic(con, fromId, toId, money)
con.commit()
}catch (e: Exception){
con.rollback()
throw IllegalStateException(e)
}finally {
release(con)
}
}
private fun validation(toMember: Member){
if (toMember.memberId.equals("ex")){
throw IllegalStateException("이체 중 예외 발생")
}
}
private fun release(con: Connection){
try{
con.autoCommit = true
con.close()
} catch (e: Exception){
logger.info("error", e)
}
}
@Throws(SQLException::class)
private fun bizLogic(con: Connection, fromId: String, toId: String, money: Int){
val fromMember = memberRepository.findById(con, fromId)
val toMember = memberRepository.findById(con, toId)
memberRepository.update(con, fromId, fromMember.money - money)
validation(toMember)
memberRepository.update(con, toId, fromMember.money + money)
}
}
package hello.jbdc.service
import hello.jbdc.connection.ConnectionConst
import hello.jbdc.domain.Member
import hello.jbdc.repository.MemberRepositoryV2
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.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.springframework.jdbc.datasource.DriverManagerDataSource
import java.sql.SQLException
internal class MemberServiceV2Test{
val MEMBER_A = "memberA"
val MEMBER_B = "memberB"
val MEMBER_EX = "ex"
lateinit var memberRepository: MemberRepositoryV2
lateinit var memberService: MemberServiceV2
@BeforeEach
fun before(){
val dataSource = DriverManagerDataSource(ConnectionConst.URL,
ConnectionConst.USERNAME, ConnectionConst.PASSWORD)
memberRepository = MemberRepositoryV2(dataSource)
memberService = MemberServiceV2(dataSource, memberRepository)
}
@AfterEach
@Throws(SQLException::class)
fun after(){
memberRepository.delete(MEMBER_A);
memberRepository.delete(MEMBER_B);
memberRepository.delete(MEMBER_EX);
}
@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)
}
}
애플리케이션에서 DB 트랜잭션을 적용하려면 서비스 계층이 매우 지저분해지고, 생각보다 매우 복잡한 코드를 요구한다. 추가로 커넥션을 유지하도록 코드를 변경하는 것도 쉬운 일은 아니다. 다음 시간에는 스프링을 사용해서 이런 문제들을 하나씩 해결해보자.