위의 사진을 보면 프레젠테이션은 서블릿이나 MVC 같은 기술에 의존하고
데이터 접근 계층은 JDBC나 JPA같은 기술에 의존한다.
하지만 서비스 계층은 특정 기술에 의족하지 않고, 순수 자바 코드로 작성해야 한다.
컨트롤러나 리포지토리는 의존한느 기술의 트렌드가 바뀌면 수정해야 하나, 서비스 계층은 핵심 비즈니스 로직이 들어있어 최대한 변경 없이 유지해야 한다
위의 동작 방식은 다음과 같은데
1. 트랜잭션 메니저가 커넥션 생성 후 트랜잭션 시작
2. 트랜잭션 매니저는 커넥션을 트랜잭션 동기화 매니저에 저장
3. 리포지토리는 트랜잭션 동기화 매니저에 있는 커넥션을 꺼내서 사용
4. 트랜잭션 매니저는 트랜잭션 동기화 매니저에 있는 커넥션을 통해 트랜잭션을 종료하고, 커넥션을 닫음
// 리포지토리
private void close(Connection con, Statement stmt, ResultSet rs) {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(stmt);
//주의! 트랜잭션 동기화를 사용하려면 DataSourceUtils를 사용해야 한다.
DataSourceUtils.releaseConnection(con, dataSource);
}
private Connection getConnection() throws SQLException {
// 주의! 트랜잭션 동기화를 사용하려면 DataSourceUtils를 사용해야 한다.
Connection con = DataSourceUtils.getConnection(dataSource);
log.info("get connection={}, class={}", con, con.getClass());
return con;
}
// 서비스
private final PlatformTransactionManager transactionManager;
private final MemberRepositoryV3 memberRepository;
public void accountTrancefer(String fromId, String toId, int money) throws SQLException {
//트랜잭션 시작
TransactionStatus status = transactionManager
.getTransaction(new DefaultTransactionDefinition());
try {
//비즈니스 로직
bizLogic(fromId, toId, money);
transactionManager.commit(status); // 성공시 커밋
} catch (Exception e) {
transactionManager.rollback(status); // 실패시 롤백
throw new IllegalStateException(e);
}
}
// 컨트롤러
DriverManagerDataSource dataSource = new DriverManagerDataSource(URL,USERNAME, PASSWORD);
// 트랜잭션 매니저 생성
PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
memberRepository = new MemberRepositoryV3(dataSource);
memberService = new MemberServiceV3_1(transactionManager, memberRepository);
트랜잭션 매니저의 인자로 DataSource를 필요로 함
그런데 위의 코드를 보면 getConnection, commit, rollback 등 반복되는 코드가 있음
// 서비스
private final TransactionTemplate txTemplate;
private final MemberRepositoryV3 memberRepository;
public MemberServiceV3_2(PlatformTransactionManager transactionManager, MemberRepositoryV3 memberRepository) {
this.txTemplate = new TransactionTemplate(transactionManager);
this.memberRepository = memberRepository;
}
public void accountTrancefer(String fromId, String toId, int money) throws SQLException {
txTemplate.executeWithoutResult((status) ->{
try {
bizLogic(fromId, toId, money);
} catch (SQLException e) {
throw new IllegalStateException(e);
}
});
}
프록시를 사용하여 트랜잭션 처리 로직과 비즈니스 로직을 처리하는 객체를 나눌 수 있다.
// 서비스
@Transactional
public void accountTransfer(String fromId, String toId, int money) throws SQLException {
bizLogic(fromId, toId, money);
}
@Autowired
private MemberRepositoryV3 memberRepository;
@Autowired
private MemberServiceV3_3 memberService;
@TestConfiguration
static class TestConfig{
@Bean
DataSource dataSource() {
return new DriverManagerDataSource(URL, USERNAME, PASSWORD);
}
@Bean
PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
@Bean
MemberRepositoryV3 memberRepositoryV3() {
return new MemberRepositoryV3(dataSource());
}
@Bean
MemberServiceV3_3 memberServiceV3_3() {
return new MemberServiceV3_3(memberRepositoryV3());
}
}
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa
spring.datasource.password=
@Autowired
private MemberRepositoryV3 memberRepository;
@Autowired
private MemberServiceV3_3 memberService;
@TestConfiguration
static class TestConfig{
private final DataSource dataSource;
public TestConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
MemberRepositoryV3 memberRepositoryV3() {
return new MemberRepositoryV3(dataSource);
}
@Bean
MemberServiceV3_3 memberServiceV3_3() {
return new MemberServiceV3_3(memberRepositoryV3());
}
}