데이터베이스 트랜잭션 경계와 동기화 테스트
이전 트랜잭션 경계와 동기화 에서 알아본 내용을 실제로 테스트 해 보겠습니다.
전체 코드는 이 링크 를 참고해 주세요.
1. 테스트 구성
JDBC 와 MySql 을 사용해 테스트 했습니다.
name 과 seq 컬ㄹ머을 가진 간단한 테이블에 여러 사용자를 같은 트랜잭션에서 추가하고 추가하는 도중 예외 발생 시 모든 사용자 추가 요청이 롤백 되는지 확인 했습니다.
트랜잭션 경계는 UserService 에서 설정하고 데이터 액세스는 UserDao 에서 JDBC 를 통해 수행합니다.
각 테스트가 끝난 다음 테이블에 추가된 row 들을 지웁니다.
2. 서비스, DAO 코드
테스트에 사용되는 UserService, UserDao 코드는 아래와 같습니다.
public class UserDao {
@Autowired
JdbcTemplate jdbcTemplate;
void addUser(String name) {
int seq = jdbcTemplate.queryForObject("select count(*) from test_user", Integer.class) + 1;
jdbcTemplate.execute("insert into test_user values (" + seq + ", '" + name + "')");
}
void addUserWithException(String name) {
int seq = jdbcTemplate.queryForObject("select count(*) from test_user", Integer.class) + 1;
jdbcTemplate.execute("insert into test_user values (" + seq + ", '" + name + "')");
throw new IllegalArgumentException("Query Exception" + name);
}
int getUserCount() { return jdbcTemplate.queryForObject("select count(*) from test_user", Integer.class); }
void deleteAll() { jdbcTemplate.execute("delete from test_user"); }
void updateUser(int seq, String name) {
jdbcTemplate.execute("update test_user set name = '" + name + "' where id = " + seq);
}
void updateUserWithException(int seq, String name) {
jdbcTemplate.execute("update test_user set name = '" + name + "' where id = " + seq);
throw new IllegalArgumentException("Query Exception" + name);
}
void deleteUser(int seq) {
jdbcTemplate.execute("delete from test_user where id = " + seq);
}
}
@Service
public class UserService {
@Autowired UserDao userDao;
private TransactionTemplate transactionTemplate;
@Autowired
public void init(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
...
public void addUserAllWithException(List<String> nameList) {
this.transactionTemplate.execute(status -> {
String exceptionName = nameList.get(nameList.size() - 1);
for (String name : nameList) {
if (name.equals(exceptionName)) {
userDao.addUserWithException(name);
} else {
userDao.addUser(name);
}
}
System.out.println("after add user count : " + userDao.getUserCount());
return null;
});
}
public void addAllAndUpdateUserWithException(List<String> nameList) {
this.transactionTemplate.execute(status -> {
for (String name : nameList) {
userDao.addUser(name);
}
int count1 = userDao.getUserCount();
System.out.println("after add user all count : " + count1);
userDao.deleteUser(3);
int count2 = userDao.getUserCount();
System.out.println("after delete user count : " + count2);
userDao.updateUserWithException(2, "update2");
return null;
});
}
...
}
@Configuration
public class DataAccessConfig {
@Bean
UserDao userDao() { return new UserDao(); }
@Bean(name="transactionManager")
PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
2. 사용자 추가 테스트
트랜잭션 경계 내에서 4명의 사용자를 추가하고 트랜잭션 경계 없이 4명의 사용자를 추가하는 테스트를 수행합니다.
4번 째 사용자를 추가할 때 UserDao 의 메소드에서 롤백을 위해 IllegalArgumentException 런타임 예외를 발생 시킵니다.
@SpringBootTest
class JdbctemplatedemoApplicationTests {
@Autowired UserDao userDao;
@Autowired UserService userService;
@BeforeEach
public void afterEach() {
userDao.deleteAll();
}
...
@Test
void addAllOrNothing() {
try {
userService.addUserAllWithException(Arrays.asList("test1", "test2", "test3", "test4"));
Assertions.assertThat(userDao.getUserCount()).isEqualTo(0);
} catch (IllegalArgumentException e) {
Assertions.assertThat(userDao.getUserCount()).isEqualTo(0);
}
}
@Test
void addAllOrNothingWithoutTransaction() {
try {
userService.addUserAllWithExceptionNotAppliedTx(Arrays.asList("test1", "test2", "test3", "test4"));
Assertions.assertThat(userDao.getUserCount()).isEqualTo(4);
} catch (IllegalArgumentException e) {
Assertions.assertThat(userDao.getUserCount()).isEqualTo(4);
}
}
...
}
테스트를 수행한 결과는 아래와 같습니다.
UserService 에 트랜잭션 경계를 설정하지 않은 addAllOrNothing 테스트는 마지막 사용자 추가 후 발생한 런타임 에러로 인해 트랜잭션 경계 내 모든 사용자 추가 요청이 롤백되어 테스트 테이블의 row 개수가 0 인 것을 확인할 수 있습니다.
UserService 에 트랜잭션 경계를 설정하지 않은 addAllOrNothingWithoutTransaction 테스트는 마지막 사용자 추가 후 런타임 에러가 발생 했지만 롤백이 되지 않아 4명의 사용자 모두 추가된 것을 확인할 수 있습니다.
콘솔 출력을 보면 아래와 같이 트랜잭션 경계 내 사용자 추가 쿼리가 커밋이 되지 않았는데 테이블의 row 가 조회되는 것을 확인 할 수 있습니다.
after add user count : 1
after add user count : 2
after add user count : 3
커밋되지 않은 row 가 보인 이유는 같은 커넥션에서 조회했기 때문 입니다.
MySql 은 트랜잭션의 격리 수준이 기본적으로 REPEATABLE READ 입니다. REPEATABLE READ 는 트랜잭션의 버전을 확인해 언두 영역에 커밋되기 전의 데이터를 보여줍니다.
사용자를 추가한 트랜잭션과 사용자의 수를 조회하는 트랜잭션이 같은 버전이기 때문에 커밋되지 않은 사용자가 조회되는 것 입니다.
사용자 추가 요청이 커밋되지 않은 상태에서 다른 커넥션을 통해 사용자 수를 조회하면 0 건이 조회됩니다.
자세한 내용은 트랜잭션 격리 수준 문서를 참고해 주세요.
3. 사용자 추가, 수정, 삭제 테스트
트랜잭션 경계 내에서 4명의 사용자를 추가 후 수정, 삭제하는 테스트를 수행하고 트랜잭션 경계없이 같은 테스트를 수행합니다.
4번 째 사용자를 추가할 때 UserDao 의 메소드에서 롤백을 위해 IllegalArgumentException 런타임 예외를 발생 시킵니다.
사용자 추가 테스트와 마찬가지로 트랜잭션 경계를 설정하고 수행한 쿼리는 모두 롤백 되었지만 트랜잭션 경계를 설정하지 않고 수행한 쿼리는 롤백되지 않은 것을 확인 할 수 있습니다.