여기서 가장 중요한 곳은 어디일까? 바로 핵심 비즈니스 로직이 들어있는 서비스 계층이다. 시간이 흘러서 UI(웹)와 관련된 부분이 변하고, 데이터 저장 기술을 다른 기술로 변경해도, 비즈니스 로직은 최대한 변경없이 유지되어야 한다.
정리하자면 서비스 계층은 가급적 비즈니스 로직만 구현하고 특정 구현 기술에 직접 의존해서는 안된다. 이렇게 하면 향후 구현 기술이 변경될 때 변경의 영향 범위를 최소화 할 수 있다.
package hello.jbdc.service
import hello.jbdc.repository.MemberRepositoryV1
import java.sql.SQLException
import kotlin.jvm.Throws
class MemberServiceV1(
private val memberRepository: MemberRepositoryV1
) {
@Throws(SQLException::class)
fun accountTransfer(fromId: String, toId: String, money: Int){
val fromMember = memberRepository.findById(fromId)
val toMember = memberRepository.findById(toId)
memberRepository.update(fromId, fromMember.money - money);
memberRepository.update(toId, toMember.money + money);
}
}
MemberServiceV1 은 특정 기술에 종속적이지 않고, 순수한 비즈니스 로직만 존재한다.
특정 기술과 관련된 코드가 거의 없어서 코드가 깔끔하고, 유지보수 하기 쉽다.
향후 비즈니스 로직의 변경이 필요하면 이 부분을 변경하면 된다.
사실 여기에도 문제점은 있다. 서비스 코드가 SQLException이라는 JDBC 기술에 의존한다는 점이다.
이 부분은 memberRepository 에서 올라오는 예외이기 때문에 memberRepository 에서 해결해야 한다.
또한 MemberRepositoryV1 이라는 구체 클래스에 직접 의존하고 있다. MemberRepository 인터페이스를 도입하면 향후 MemberService 의 코드의 변경 없이 다른 구현 기술로 손쉽게 변경할 수 있다.
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{
@Throws(SQLException::class)
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)
}
}
@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)
}
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)
}
}
}
JDBC 구현 기술이 서비스 계층에 누수되는 문제
트랜잭션 동기화 문제
트랜잭션 적용 반복 문제
public void accountTransfer(String fromId, String toId, int money) throws
SQLException {
Connection con = dataSource.getConnection();
try {
con.setAutoCommit(false); //트랜잭션 시작
//비즈니스 로직
bizLogic(con, fromId, toId, money); con.commit(); //성공시 커밋
} catch (Exception e) { con.rollback(); //실패시 롤백
throw new IllegalStateException(e);
} finally {
release(con);
}
}
public static void main(String[] args) {
//엔티티 매니저 팩토리 생성
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("jpabook");
EntityManager em = emf.createEntityManager(); //엔티티 매니저 생성 EntityTransaction tx = em.getTransaction(); //트랜잭션 기능 획득
try {
tx.begin(); //트랜잭션 시작
logic(em); //비즈니스 로직
tx.commit();//트랜잭션 커밋
} catch (Exception e) {
tx.rollback(); //트랜잭션 롤백
} finally {
em.close(); //엔티티 매니저 종료
}
emf.close(); //엔티티 매니저 팩토리 종료
}
interface Txmanager{
begin();
commit();
rollback();
}
package org.springframework.transaction;
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
앞으로 PlatformTransactionManager 인터페이스와 구현체를 포함해서 트랜잭션 매니저로 줄여서 이야기하겠다.