토비의 스프링을 보고 정리한 내용이다. 서비스 추상화에 대해서 포스팅 하고자 한다.
문제가 있을 시, lshn1007@hanyant.ac.kr 로 메일 바랍니다.
Connection c = dataSource.getConnection();
c.setAutoCommit(false); // 트랜잭션 시작
try {
PreparedStatement ps1 = c.preparedStatement("update users ... ");
ps1.executeUpdate();
PreparedStatement ps2 = c.preparedStatement("delete users ... ");
ps2.executeUpdate();
c.commit(); // 트랜잭션 커밋
} catch (Exception e) {
c.rollback(); // 트랜잭션 롤백
}
c.close();
Connection
을 가져와 사용하고 닫는 사이에서 일어난다. 트랜잭션의 시작과 종료는 Connection
오브젝트를 통해 이뤄지기 때문이다.commit()
또는 rollback()
메소드가 호출될 때까지의 작업이 하나의 트랜잭션으로 묶인다.setAutoCommit(false)
로 트랜잭션의 시작을 선언하고 commit()
또는 rollback()
으로 트랜잭션을 종료하는 작업을 트랜잭션의 경계 설정
이라고 한다.로컬 트랜잭션
이라고도 한다.public void upgradeLevels() throws Exception {
TransactionSynchronizationManager.initSynchronization();
Connection c = DataSourceUtils.getConnection(dataSource);
c.setAutoCommit(false);
try {
List<User> users = userDao.getAll();
for (User user : users) {
upgrade(level);
}
c.commit();
} catch (Exception e) {
c.rollback();
throw e;
} finally {
DataSourceUtils.releaseConnection(c, dataSource);
TransactionSynchronizationManager.unbindResource(this.dataSource);
TransactionSynchronizationManager.clearSynchronization();
}
}
TransactionSynchronizationManager
이다. 이 클래스를 이용해 먼저 트랜잭션 동기화 작업을 초기화하도록 요청한다.DataSourceUtils
의 getConnection()
메소드를 사용하면 DB 커넥션을 생성하고, 이 Connection 오브젝트를 트랜잭션 동기화에 사용하도록 저장소에 바인딩해준다.JTA(Java Transaction API)
를 제공하고 있다.InitialContext ctx = new InitialContext();
// JNDI를 이용해 서버의 UserTransaction Object를 가져온다.
UserTransaction tx = (UserTransaction) ctx.lookup(USER_TX_JNDI_NAME);
tx.begin();
// JNDI로 가져온 DataSource를 사용해야 한다.
Connection c = dataSource.getConnection();
try {
/**
* Data access 코드
*/
tx.commit();
} catch (Exception e) {
tx.rollback();
throw e;
} finally {
c.close();
}
외울 필요는 없고 방법이 존재한다는 것만 기억하자
PlatformTransactionManager
이다. JDBC의 로컬 트랜잭션을 이용한다면 이 인터페이스를 구현한 DataSourceTransactionManager
를 사용하면 된다.package org.springframework.mail;
...
public interface MailSender {
void send(SimpleMailMessage simpleMessage) throws MailException;
void send(SimpleMailMessage[] simpleMessages) throws MailException;
}
- 이 인터페이스는
SimpleMailMessage
라는 인터페이스를 구현한 클래스에 담긴 메일 메시지를 전송하는 메소드로 구성되어 있다.- 이를 구체화한
JavaMailSenderImpl
를 구현해서 사용하면 된다.
private void sendUpgradeEmail(User user) {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost("mail.server.com");
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(user.getEmail());
message.setFrom("kswoeqwelas@naver.com");
message.setSubject("Upgrade 안내");
message.setText("사용자님의 등급이 " + user.getLevel() + "로 업그레이드 되었습니다.");
mailSender.send(message);
}
테스트 동안에 실제로 동작하는 것처럼 보이게 만들어 놓은 더미 객체로 아무런 기능을 수행하지 않는다.
- 행위를 검증하기 위해 사용되는 객체로 테스트 대상 오브젝트의 메소드가 돌려주는 결과뿐 아니라 테스트 오브젝트가 간접적으로 의존 오브젝트에 넘기는 값과 그 행위 자체에 대해서도 검증하고 싶다면, assertThat() 만으로는 검증이 불가능하다. 이런 경우에는 테스트 대상 오브젝트와 의존 오브젝트 사이에서 일어나는 일을 검증할 수 있도록 특별히 설계된 목 오브젝트(mock object)를 사용해야 한다.
- 목 오브젝트는 테스트 오브젝트와 자신의 사이에서 일어나는 커뮤니케이션 내용을 저장해뒀다가 테스트 결과를 검증하는 데 활용할 수 있다.
static class MockMailSender implements MailSender {
private List<String> requests = new ArrayList<>();
public List<String> getRequests() {
return requests;
}
// UserService로부터 전송 요청을 받은 메일 주소를 저장해두고 이를 읽을 수 있게 한다.
public void send(SimpleMailMessage mailMessage) throws MailException {
requests.add(mailMessage.getTo()[0]);
}
public void send(SimpleMailMessage[] mailMessages) throws MailException {
}
}
위 MockMailSender 클래스를 활용한 테스트 케이스이다.
@Test
void upgradeLevels() throws Exception {
userDao.deleteAll();
for (User user : users) userDao.add(user);
// 메일 발송 결과를 테스트할 수 있도록 목 오브젝트를 만들어 UserService에 주입해준다.
MockMailSender mockMailSender = new MockMailSender();
userService.setMailSender(mockMailSender);
userService.upgradeLevels();
this.checkLevelUpgraded(users.get(0), false);
this.checkLevelUpgraded(users.get(1), true);
this.checkLevelUpgraded(users.get(2), false);
this.checkLevelUpgraded(users.get(3), true);
this.checkLevelUpgraded(users.get(4), false);
// 목 오브젝트에 저장된 메일 수신자 목록을 가져와 업그레이드 대상과 일치하는지 확인한다.
List<String> request = mockMailSender.getRequests();
assertEquals(2, request.size());
assertEquals(users.get(1).getEmail(), request.get(0));
assertEquals(users.get(3).getEmail(), request.get(1));
}