ν΄λΉ κΈμ μλ₯μλ₯ κΈ°μ λΈλ‘κ·Έμ μμ±λ κΈκ³Ό λμΌν©λλ€.
HashtagServiceTest(μλΉμ€μ ν μ€νΈ) μ½λλ₯Ό μ§λ λμ€μ΄μμ΅λλ€.
μ 체 ν μ€νΈλ€μ μ€νν μ, HashtagServiceTestκ° λλ€ νλ₯ λ‘ ν΅κ³Όνκ±°λ μ€ν¨νλ μν©μ΄ λ°λ³΅λμμ΅λλ€. λλ΅ 5λ² ν μ€νΈλ₯Ό μ€νν μ 1λ² κΌ΄λ‘ ν μ€νΈκ° μ€ν¨νμ΅λλ€.
λ§μ HashtagServiceTestλ§ λλ Έμ λ ν μ€νΈκ° λ¬Έμ μμ΄ ν΅κ³Όνμ΅λλ€.
λλ€ν νλ₯ λ‘ ν μ€νΈκ° μ€ν¨ν λ κ³ λ €ν΄μΌ ν λΆλΆμ λ€μκ³Ό κ°μ΅λλ€.
λλ€ κ²°κ΅ ν μ€νΈ 격리μ λ¬Έμ μ λλ€.
μ΄μ€ μ λ 2λ², ν μ€νΈ νκ²½μ λ¬Έμ λΌκ³ νλ¨νμ΅λλ€.
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
public class AcceptanceTest {...}
μ°μ , HashtagAcceptanceTest μ΄κΈ°νλ₯Ό μν΄ μ¬μ©ν @DirtiesContext μ΄λ Έν μ΄μ μ λν΄ μ°Ύμλ΄€μ΅λλ€.
ν΄λΉ μ΄λ Έν μ΄μ μ¬μ© μ ν μ€νΈ κ²©λ¦¬κ° μ΄λ€μ§μ§ μμ λκ° μλ€λ κΈμ λ³΄κ² λμμ΅λλ€.
Beware of @DirtiesContext - Codecleaner
μ€νλ§ κΉνλΈμμ ν΄λΉ λ¬Έμ μ κ΄λ ¨λ μ΄μλ₯Ό μ°Ύμλ΄€μ΅λλ€.
After a long digging, we just found, that if a method or class is annotated withΒ @DirtiesContext , it doesn't clean up all the objects. It seems, that the "old" application context (or part of it) remains in the memory and can not be cleared up by the GC.
l had a similar issue using HSQLDB with Spring 4.1 (using Boot).
Between tests (with @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) enabled) I noticed that the data was cleaned - because I used @Transactional in my tests) but that HSQLDB autoincrement numbering was not reset.
This had to do that HSQLDB was not shutdown between tests.
After adding ;shutdown=true to my jdbc url everything was working as expected.
@DirtiesContext (HashtagAcceptanceTest μμ μ¬μ©)μ @Transactional (HashtagServiceTestμμ μ¬μ©)μ μ¬μ©ν ν μ€νΈλ€μ λμμ λ릴 μ λ¬Έμ κ° μμ μ μλ€λ μ½λ©νΈκ° μμμ΅λλ€.
BEFORE application.yml
spring:
datasource: //μ΄ λΆλΆ μμ
url: jdbc:h2:mem:db?serverTimezone=Asia/Seoul;MODE=MYSQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
driver-class-name: org.h2.Driver
username: sa
jpa:
...
AFTER application.yml
spring:
//μμ
jpa:
hibernate:
...
ν μ€νΈμ©Β application.ymlμμ μμ dataSourceΒ ν λΉνλ λΆλΆμ μμ νλλ μ μμ μΌλ‘ ν μ€νΈκ° λμκ°μ΅λλ€!
(Thanks To μ€μ°!)
κ·Έλ°λ° @DirtiesContextλ κ°νΈνμ§λ§ ν μ€νΈκ° λ립λλ€. ν μ€νΈ λ©μλ λ³λ‘ 컨ν μ€νΈλ₯Ό λ€μ λμμΌ νκΈ° λλ¬Έμ λ§μ μκ°μ΄ μμλ©λλ€.
μΈμν μ€νΈμμ ν μ€νΈ 격리νκΈ°
κ³ λ―Ό λμ, μΈμν μ€νΈμμ @Sql μ΄λ Έν μ΄μ λ° truncate.sqlλ¬Έμ ν΅ν΄ μ΄κΈ°νλ₯Ό μ§ννλλ‘ λ³κ²½νμ΅λλ€.
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@Sql(
scripts = {"classpath:truncate.sql"},
executionPhase = BEFORE_TEST_METHOD)
public class AcceptanceTest {...}
public class HashtagAcceptanceTest extends AcceptanceTest {...}
ν μ€νΈμ©Β application.ymlΒ μμ κΈ°μ‘΄μΒ DBλ₯Ό λͺ μνλ ννλ‘ λ‘€λ°±νμ΅λλ€.
spring:
datasource:
url: jdbc:h2:mem:db?serverTimezone=Asia/Seoul;MODE=MYSQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
driver-class-name: org.h2.Driver
username: sa
jpa:
...
BEFORE(HashtagServiceTest)
@SpringBootTest
@Transactional
class HashtagServiceTest {...}
μλΉμ€ν μ€νΈ 격리λ₯Ό μν΄ μ¬μ©ν @Transactional μ΄λ Έν μ΄μ μ λν΄ μ°Ύμλ΄€μ΅λλ€.
λ€μκ³Ό κ°μ κΈμ μ°Ύμ μ μμμ΅λλ€.
JPA μ¬μ©μ ν μ€νΈ μ½λμμ @Transactional μ£ΌμνκΈ°
@Transactional μ΄λ Έν μ΄μ κ³Ό JPAλ₯Ό μ¬μ©νλ ServiceTest μ¬μ΄μ νΈνμ±μ λ¬Έμ κ° μκΈ° λλ¬Έμ
ν΄λΉ μ΄κΈ°ν λ°©μμ κΆμ₯νμ§ μλλ€κ³ ν©λλ€.
μ΄μΒ ServiceTestμμλ
@TransactionalΒ μ΄λ Έν μ΄μ λμ ,
@SqlΒ μ΄λ Έν μ΄μ λ°Β truncate.sqlμ μ¬μ©ν΄ μ΄κΈ°ννλλ‘ λ³κ²½νμ΅λλ€.
AFTER(HashtagServiceTest)
@SpringBootTest
@Sql(
scripts = {"classpath:truncate.sql"},//sql μ€μ
executionPhase = BEFORE_TEST_METHOD)
class HashtagServiceTest {...}
(+ μΆκ°)
νμ¬ truncate.sqlμ΄ DB μ€ν€λ§ λ³κ²½μ μμ‘΄μ μΈ λ¬Έμ κ° μμ΄Β DatabaseCleanerλ‘ λ³κ²½λ μνμ λλ€.
ServiceTestμμΒ @TransactionalΒ μ΄λ Έν μ΄μ μ λΉΌλ LazyInitializationException μλ¬κ° ν°μ‘μ΅λλ€.
could not initialize proxy [com.wooteco.sokdak.hashtag.domain.Hashtag#3] - no Session
org.hibernate.LazyInitializationException: could not initialize proxy [com.wooteco.sokdak.hashtag.domain.Hashtag#3] - no Session
...
μμΈμ postHashtag μ°κ²° μν°ν°μμ Hashtag, Postλ₯Ό μ μ₯ν λ
Lazy λ‘λ©μΌλ‘ κ°μ Έμ¨λ€λ λ°μ μμμ΅λλ€.
μλΉμ€ λ©μλλ₯Ό νΈμΆνκ³ , νΈλμμ μ΄ λ«ν ν, λ€μ Lazy λ‘λ©μ κ±Έμ΄ κ°μ κ°μ Έμ€λ €κ³ νλ
DBμ μ°κ²°λ Connectionμ΄ μμ΄μ μκΈ°λ μ€λ₯μμ΅λλ€.
[Hibernate / JPA ] LazyLoading | No Session μλ¬μ λν΄μ
ServiceTestμ νμ ν΄
@Transactional μ΄λ Έν μ΄μ κ³Ό @Sql μ΄λ Έν μ΄μ μ ν¨κ» μ¬μ©νλ
λ€μ ν μ€νΈκ° λ¬Έμ μμ΄ λμκ°μ΅λλ€...
@SpringBootTest
@Sql(
scripts = {"classpath:truncate.sql"},
executionPhase = BEFORE_TEST_METHOD)
@Transactional
class HashtagServiceTest {...}
ServiceTest μ νμ νμ§λ§, @Transactional ,@Sql μ΄λ Έν μ΄μ μ ν¨κ» μ¬μ©νλ€λ κ²μ
λ λ»μ λλ€.
νμ¬λ ν΄λΉ μ¬μ§μ λ©μλ μ νΈλμμ μ κ²μ¦ν μ μμ΅λλ€.
μ μμ μΈ λ°©λ²μ Lazy loadingμΌλ‘ λ€μ΄μ€λ κ°μ 리ν΄νλ κ²½μ°, 쿼리λ₯Ό fetch joinμΌλ‘ λ°κΏμ£Όλ κ²μ λλ€. κ·Έλ¬λ©΄ λ μ΄μ Lazy loadingμ΄ νμνμ§ μκ² λλ©΄μ μ μμ μΌλ‘ ν μ€νΈ μ½λκ° ν΅κ³Όνκ² λ©λλ€.
public interface PostHashtagRepository extends JpaRepository<PostHashtag, Long> {
@Query("SELECT ph from PostHashtag ph JOIN FETCH ph.hashtag h JOIN FETCH ph.post p WHERE p.id = :id")
List<PostHashtag> findAllByPostId(Long id);
νμ§λ§ μ΄λ₯Ό μν΄μλ SpringDataJPAμ μ΄μ μ μΌλΆ ν¬κΈ°ν΄μΌ ν©λλ€.
κ΄λ ¨ Repository λ©μλμ λͺ¨λ fetch joinμ κ±Έμ΄μ£Όλ 쿼리λ₯Ό μ€μ νκ² λ©λλ€.
μ΄λ¬ν λΆνΈν¨μ ν΄κ²°νκΈ° μν΄ @EntityGraph μ΄λ Έν μ΄μ μ μ¬μ©ν μλ μμ΅λλ€.
public interface PostHashtagRepository extends JpaRepository<PostHashtag, Long> {
@EntityGraph(attributePaths = {"post", "hashtag"})
List<PostHashtag> findAllByPostId(Long id);
λ€λ§ EntityGraph μ μ© μ Outer Joinμ΄ λλ λ¬Έμ κ° μμ΅λλ€.
μΌλ λ€ λ§€νμμ μ λ λ°©λ²μ μ μ© μ νμ΄μ§λ€μ΄μ μ μ¬μ©ν μ μμ΅λλ€. μ΄λ₯Ό κ³ λ €ν μ κ·Όμ΄ νμν©λλ€.