-
7. 마지막 세션Kotlin/실전! 코틀린과 스프링 부트로 ... 개발 2023. 8. 8. 20:29
추가 - 테스트와 영속성 컨텍스트
- 테스트 코드와 LazyInitializationException
ㆍ테스트 코드에서 A 엔티티를 불러오고 B객체에 접근을 하면
영속성 컨텍스트가 종료되어 버려서 지연 로딩 객체를 초기화할 수 없게 될 경우
- 테스트 코드에서 영속성 컨텍스트를 쓰고 싶은 상황이 분명 있음!
@Transactional fun saveUserAndLoanTwoBooks() { val newUser = User("A", 123) val books = bookRepository.saveAll(listOf(Book("책1", COMPUTER), Book("책2", COMPUTER))) books.forEach { book -> newUser.loanBook(book) } // UserLoanHistory 2개 생길겁니다! userRepository.save(newUser) }
@SpringBootTest class TempTest @Autowired constructor( private val userService: UserService, private val userRepository: UserRepository, ) { @Test fun `유저 1명과 책 2권을 저장하고 대출한다`() { // when userService.saveUserAndLoanTwoBooks() // then val users = userRepository.findAll() assertThat(users).hasSize(1) assertThat(users[0].userLoanHistories).hasSize(2) } }
[방법1] @Transactional
- 테스트 코드에 @Transactional 을 붙인다.
@Transactional @Test fun `유저 1명과 책 2권을 저장하고 대출한다`() { // when userService.saveUserAndLoanTwoBooks() // then val users = userRepository.findAll() assertThat(users).hasSize(1) assertThat(users[0].userLoanHistories).hasSize(2) }
- 장점
ㆍ간결하다! 롤백도 된다!
ㆍ트랜잭션별로 테스트를 격리할 수 있어, 병렬 테스트도 가능하다!
- 단점
ㆍ테스트 내성이 떨어진다.
* 트랜잭션널을 사용하지 않는다면?! (데이터 삭제 자동화)
@SpringBootTest class CleaningSpringBootTest { @Autowired private lateinit var repositories: List<JpaRepository<*, *>> @AfterEach fun clean() { val currentMillis = System.currentTimeMillis() repositories.forEach { it.deleteAll() } println("소요 시간 : ${System.currentTimeMillis() - currentMillis}") } }
- CleaningSpringBootTest를 상속받게 하면 자동으로 데이터들을 삭제해준다.
[방법2] N쪽의 Repository를 활용한다.
@Test fun `유저 1명과 책 2권을 저장하고 대출한다`() { // when userService.saveUserAndLoanTwoBooks() // then val users = userRepository.findAll() assertThat(users).hasSize(1) val histories = userLoanHistoryRepository.findAll() assertThat(histories).hasSize(2) assertThat(histories[0].user.id).isEqualTo(users[0].id) }
[방법 3] fetch join을 사용해 미리 불러온다
@Test fun `유저 1명과 책 2권을 저장하고 대출한다`() { // when userService.saveUserAndLoanTwoBooks() // then val users = userRepository.findAllWithHistories() assertThat(users).hasSize(1) assertThat(users[0].userLoanHistories).hasSize(2) }
- fetch join은 1개의 N에 대해서만 사용할 수 있다.
[방법 4] TxHelper를 이용한다
@Component class TxHelper { @Transactional fun exec(block: () -> Unit) { block() } }
// then txHelper.exec { val users = userRepository.findAllWithHistories() assertThat(users).hasSize(1) assertThat(users[0].userLoanHistories).hasSize(2) }
추가 - 코프링과 플러그인
1. 스프링 컴포넌트와 spring 플러그인
id 'org.jetbrains.kotlin.plugin.spring' version '1.6.21'
- @Component, @Transactional 이 붙어있는 함수나 클래스를 자동으로 열어준다.
왜 오픈되어야 하는가?
@Service class AService { @Transactional fun saveEntity() { // 로직 } }
- Service는 왜 Open 되어야 하는가!
ㆍ프록시라는 것 때문!
- 스프링이 시작될 때 프록시 객체가 AService를 상속받고 오버라이드한 함수가 생긴다.
ㆍ그렇기에 오픈 되어야 한다.
2. JPA 객체와 기본 생성자
id 'org.jetbrains.kotlin.plugin.jpa' version '1.6.21'
- 엔티티객체, 매퍼슈퍼클래스, 임베디드 객체에 기본 생성자를 만들어주는 역할
3. JPA 객체와 open
id 'org.jetbrains.kotlin.plugin.allopen' version '1.6.21' allOpen { annotation("javax.persistence.Entity") annotation("javax.persistence.MappedSuperclass") annotation("javax.persistence.Embeddable") }
- 프록시 때문에 오픈해준다.
'Kotlin > 실전! 코틀린과 스프링 부트로 ... 개발' 카테고리의 다른 글
6. 네 번째 요구사항 추가하기 - Querydsl (0) 2023.08.08 5. 세 번째 요구사항 추가하기 - 책 통계 (0) 2023.08.08 4. 두 번째 요구사항 추가하기 - 도서 대출 현황 (0) 2023.08.07 3. 첫 번째 요구사항 추가하기 - 책의 분야 (0) 2023.08.05 2. Java 서버를 Kotlin 서버로 리팩토링 (0) 2023.08.02