
회원
@Entity
@Getter @Setter
public class Member {
@Id
@GeneratedValue
private Long id;
private String username;
public Member() {
}
public Member(String username) {
this.username = username;
}
}
@Slf4j
@Repository
@RequiredArgsConstructor
public class MemberRepository {
private final EntityManager em;
@Transactional
public void save(Member member) {
log.info("member 저장");
em.persist(member);
}
public Optional<Member> find(String username) {
return em.createQuery("select m from Member m where m.username=:username", Member.class)
.setParameter("username", username)
.getResultList().stream().findAny();
}
}
DB로그
@Entity
@Getter
@Setter
public class Log {
@Id
@GeneratedValue
private Long id;
private String message;
public Log() {
}
public Log(String message) {
this.message = message;
}
}
@Slf4j
@Repository
@RequiredArgsConstructor
public class LogRepository {
private final EntityManager em;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void save(Log logMessage) {
log.info("log 저장");
em.persist(logMessage);
if (logMessage.getMessage().contains("로그예외")) {
log.info("log 저장시 예외 발생");
throw new RuntimeException("예외 발생");
}
}
public Optional<Log> find(String message) {
return em.createQuery("select l from Log l where l.message = :message",
Log.class)
.setParameter("message", message)
.getResultList().stream().findAny();
}
}
Service
@Slf4j
@Service
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
private final LogRepository logRepository;
@Transactional
public void joinV1(String username) {
Member member = new Member(username);
Log logMessage = new Log(username);
log.info("== memberRepository 호출 시작 ==");
memberRepository.save(member);
log.info("== memberRepository 호출 종료 ==");
log.info("== logRepository 호출 시작 ==");
logRepository.save(logMessage);
log.info("== logRepository 호출 종료 ==");
}
@Transactional
public void joinV2(String username) {
Member member = new Member(username);
Log logMessage = new Log(username);
log.info("== memberRepository 호출 시작 ==");
memberRepository.save(member);
log.info("== memberRepository 호출 종료 ==");
log.info("== logRepository 호출 시작 ==");
try {
logRepository.save(logMessage);
} catch (RuntimeException e) {
log.info("log 저장에 실패했습니다. logMessage={}",
logMessage.getMessage());
log.info("정상 흐름 변환");
}
log.info("== logRepository 호출 종료 ==");
}
}
Test
@Slf4j
@SpringBootTest
class MemberServiceTest {
@Autowired
MemberService memberService;
@Autowired
MemberRepository memberRepository;
@Autowired
LogRepository logRepository;
@Test
@DisplayName("propagation = Propagation.REQUIRED - 서로 다른 트랜잭션 사용 commit 테스트")
void outerTxOff_success() {
String username = "outerTxOff_success";
memberService.joinV1(username);
assertTrue(memberRepository.find(username).isPresent());
assertTrue(logRepository.find(username).isPresent());
}
@Test
@DisplayName("propagation = Propagation.REQUIRED - 서로 다른 물리 트랜잭션 사용 rollback 테스트")
void outerTxOff_fail() {
String username = "로그예외_outerTxOff_fail";
Assertions.assertThatThrownBy(() -> memberService.joinV1(username))
.isInstanceOf(RuntimeException.class);
assertTrue(memberRepository.find(username).isPresent());
assertTrue(logRepository.find(username).isEmpty());
}
@Test
@DisplayName("propagation = Propagation.REQUIRED - 같은 물리 트랜잭션 사용 commit 테스트")
void singleTx() {
String username = "singleTx_success";
memberService.joinV1(username);
assertTrue(memberRepository.find(username).isPresent());
assertTrue(logRepository.find(username).isPresent());
}
@Test
@DisplayName("propagation = Propagation.REQUIRED - 같은 물리 트랜잭션 사용 commit 테스트")
void outerTxOn_success() {
String username = "outerTxOn_success";
memberService.joinV1(username);
assertTrue(memberRepository.find(username).isPresent());
assertTrue(logRepository.find(username).isPresent());
}
@Test
@DisplayName("propagation = Propagation.REQUIRED - 같은 물리 트랜잭션 사용 rollback 테스트")
void outerTxOn_fail() {
String username = "로그예외_outerTxOn_fail";
Assertions.assertThatThrownBy(() -> memberService.joinV1(username))
.isInstanceOf(RuntimeException.class);
assertTrue(memberRepository.find(username).isEmpty());
assertTrue(logRepository.find(username).isEmpty());
}
@Test
@DisplayName("propagation = Propagation.REQUIRED - 같은 물리 트랜잭션 사용 rollback 테스트")
void recoverException_fail() {
String username = "로그예외_recoverException_fail";
Assertions.assertThatThrownBy(() -> memberService.joinV2(username))
.isInstanceOf(UnexpectedRollbackException.class);
assertTrue(memberRepository.find(username).isEmpty());
assertTrue(logRepository.find(username).isEmpty());
}
@Test
@DisplayName("propagation = Propagation.REQUIRES_NEW - 서로 다른 물리 트랜잭션 사용 rollback 테스트")
void recoverException_success() {
String username = "로그예외_recoverException_success";
memberService.joinV2(username);
assertTrue(memberRepository.find(username).isPresent());
assertTrue(logRepository.find(username).isEmpty());
}
}