Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

탐구개발

MySQL과 JPA를 사용하는 transaction 테스트 코드 본문

프로젝트

MySQL과 JPA를 사용하는 transaction 테스트 코드

탐구개발자 2023. 11. 12. 19:06

 

TransactionManager 설정 필요여부

master, slave 분기를 위한 데이터베이스 설정 시
DataSource 뿐 아니라 JpaTransactionManager도 함께 설정하는 예제가 많았습니다.

 

@EnableTransactionManagement
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
@EnableJpaRepositories(basePackages = {"com.test.somoimapi"})


   (생략)

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(@Qualifier("dataSource") DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
        entityManagerFactoryBean.setDataSource(dataSource(dataSource));
        entityManagerFactoryBean.setPackagesToScan("com.test.somoimapi");
        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        entityManagerFactoryBean.setJpaVendorAdapter(vendorAdapter);

        return entityManagerFactoryBean;
    }
    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager tm = new JpaTransactionManager();
        tm.setEntityManagerFactory(entityManagerFactory);
        return tm;
    }

 

 

하지만 실제로 테스트 해봤을 때 JpaTransactionManager 설정 없이도 동작하는 것을 확인했고 
JpaTransactionManager 수동 설정 필요여부에 대한 자료를 찾아보았지만 찾을 수 없었습니다.

 

불필요한 코드로 판단해서 추가하지 않았고

대신 테스트코드를 작성하여 의도한대로 동작하는지 확인했습니다.

 

분산 트랜잭션같은 복잡한 요구사항이 없기 때문에

단순하게 로컬 JPA 트랜잭션이 동작하는지만 확인했습니다.

 

 

JpaTransactionManagerTestService.java

@Service
@RequiredArgsConstructor
public class JpaTransactionManagerTestService {

    private final MeetingRepository meetingRepository;

    private final UserRepository userRepository;

    public static final String THROW_EXCEPTION_KEYWORD = "예외발생";

    public void saveWithoutTransactional(Meeting meeting, User user) {
        meetingRepository.save(meeting);

        userRepository.save(user);

        throwException(user);
    }

    @Transactional
    public void saveWithTransactional(Meeting meeting, User user) {
        meetingRepository.save(meeting);

        userRepository.save(user);

        throwException(user);
    }

    private void throwException(User user) {
        if (THROW_EXCEPTION_KEYWORD.equals(user.getName())) {
            throw new RuntimeException(user.getName());
        }
    }

}

 

참고로 내부 구현으로 SimpleJpaRepository에 @Transactional이 적용되어있습니다.

 

 

 

JpaTransactionManagerTestServiceTest.java

@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureTestDatabase
public class JpaTransactionManagerTestServiceTest {

    @Autowired
    private JpaTransactionManagerTestService jpaTransactionManagerTestService;

    @Autowired
    private MeetingRepository meetingRepository;

    @Autowired
    private UserRepository userRepository;

    @AfterEach
    public void deleteAll() {
        meetingRepository.deleteAll();
        userRepository.deleteAll();
    }

    @Test
    public void transactional이_없을_경우_에러가_없다면_롤백되지않는다() {
        // given
        long expectedTotalPostCount = 1;
        User User = user("에러없음");
        Meeting meeting = meeting();

        // when
        jpaTransactionManagerTestService.saveWithoutTransactional(meeting, User);

        // then
        assertThat(meetingRepository.findAll().size())
            .isEqualTo(expectedTotalPostCount);
       
        assertThat(userRepository.findAll().size())
            .isEqualTo(expectedTotalPostCount);
    }

    @Test
    public void transactional이_없을_경우_에러가_있어도_롤백되지않는다() {
        // given
        long expectedTotalPostCount = 1;
        User User = user(THROW_EXCEPTION_KEYWORD);
        Meeting meeting = meeting();

        // when
        assertThrows(
                RuntimeException.class,
                () -> jpaTransactionManagerTestService.saveWithoutTransactional(meeting, User)
        );

        // then
        assertThat(meetingRepository.findAll().size())
                .isEqualTo(expectedTotalPostCount);

        assertThat(userRepository.findAll().size())
                .isEqualTo(expectedTotalPostCount);
    }

    @Test
    public void transactional이_있을_경우_에러가_없다면_롤백되지않는다() {
        // given
        long expectedTotalPostCount = 1;
        User User = user("에러없음");
        Meeting meeting = meeting();

        // when
        jpaTransactionManagerTestService.saveWithTransactional(meeting, User);

        // then
        assertThat(meetingRepository.findAll().size())
                .isEqualTo(expectedTotalPostCount);

        assertThat(userRepository.findAll().size())
                .isEqualTo(expectedTotalPostCount);
    }

    @Test
    public void transactional이_있을_경우_에러가_있다면_롤백된다() {
        // given
        long expectedTotalPostCount = 0;
        User user = user(THROW_EXCEPTION_KEYWORD);
        Meeting meeting = meeting();

        // when
        assertThrows(
                RuntimeException.class,
                () -> jpaTransactionManagerTestService.saveWithTransactional(meeting, user)
        );

        // then
        assertThat(meetingRepository.findAll().size())
                .isEqualTo(expectedTotalPostCount);

        assertThat(userRepository.findAll().size())
                .isEqualTo(expectedTotalPostCount);
    }

    private User user(String name) {
        return User.builder()
                .name(name)
                .build();
    }

    private Meeting meeting() {
        return Meeting.builder()
                .date(LocalDate.now())
                .groupId(1L)
                .memberId(1L)
                .build();
    }
}