본문 바로가기

IT/데이터베이스

[JPA] failed to lazily initialize a collection

반응형

0. 에러 상황

  • TestCode 작성 중 단순 save - find 코드를 작성했는데 아래와 같은 에러가 발생
failed to lazily initialize a collection of role: com.demo.jpastudy.entity.UserEntity.addressList, could not initialize proxy - no Session
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.demo.jpastudy.entity.UserEntity.addressList, could not initialize proxy - no Session
	at app//org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:614)
	at app//org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:218)
	at app//org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:591)
	at app//org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:149)
	at app//org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:621)
    ...
  • 에러를 확인해보면 지연 생성된 proxy를 no Session이라는 이유로 생성하지 못했다는 에러이다.
  • 아래는 Test Code와 Entity Code 이다

 

Test Code

@SpringBootTest
internal class UserServiceTest {

    @Autowired
    lateinit var userRepository: UserRepository

    @BeforeEach
    fun createUser() {
        val user = UserEntity(
            name = "demo"
        )

        val address = AddressEntity(
            address = "address1",
            user = user
        )
        user.addressList.add(address)

        val saveUser = userRepository.save(user)
    }

    @Test
    fun getAddressByUserId() {
        val demo = userRepository.findByName("demo")
        println("demo: ${demo?.addressList}")
    }
}

Repository

import com.demo.jpastudy.entity.AddressEntity
import com.demo.jpastudy.entity.UserEntity
import org.springframework.data.jpa.repository.JpaRepository

interface UserRepository : JpaRepository<UserEntity, Long> {
    fun findByName(name: String): UserEntity?
}
interface AddressRepository : JpaRepository<AddressEntity, Long> {
    fun findByAddress(address: String): AddressEntity
}

Entity

import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
import javax.persistence.JoinColumn
import javax.persistence.ManyToOne
import javax.persistence.OneToMany
import javax.persistence.Table

@Table(name = "user")
@Entity
class UserEntity(

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,

    @Column(name = "name")
    val name: String? = null,

    @OneToMany(mappedBy = "user", cascade = [CascadeType.ALL])
    val addressList: List<AddressEntity> = listOf()
) {
    override fun toString(): String {
        return "UserEntity(id = $id, name = $name, addressList = $addressList)"
    }
}

@Table(name = "address")
@Entity
class AddressEntity(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,

    @ManyToOne
    @JoinColumn(name = "user_id")
    val user: UserEntity? = null,

    @Column(name = "address")
    val address: String? = null
) {
    override fun toString(): String {
        return "AddressEntity(id = $id, address = $address)"
    }
}

 

  • 눈치가 빠른 사람이라면 바로 알아차릴 수 있을 것이다.
  • Address는 UserEntity 내부에서 toString() 메소드에서 Lazy 로딩으로 불러오는 상황이고, 영속성 컨텍스트의 범위는 Transaction 단위이다.
  • 존재하지 않는 영속성 컨텍스트에서 Address Entity 를 가지고 오려고 하니 no Session 이라는 에러와 함께 lazily initialize proxy를 불러오는데 실패한다고 알려주고 있다.

 

1. 해결 방법

FetchType.EAGER 사용

  • lazy loading방식이 아니라 eager loading 방식으로 변경하면 동일한 영속성 컨텍스트에서 연관 entity를 모두 불러올 수 있기 때문에 해당 에러를 해결할 수 있다.
@OneToMany(mappedBy = "user", cascade = [CascadeType.ALL], fetch = FetchType.EAGER)
    val addressList: MutableList<AddressEntity> = mutableListOf()

 

@Transactional annotation 사용

  • 단일 Transaction 묶어 lazy 로딩 시에도 동일한 영속성 컨텍스트에서 Address Entity를 불로 올 수 있게 한다.
@Transactional
@Test
fun getAddressByUserId() {
    val demo = userRepository.findByName("demo")
    println("demo: $demo")
}

 

@DataJpaTest  annotation 사용

  • DataJpaTest annotation을 확인해보면 이미 @Transactional annotation 이 포함된 걸 볼 수 있다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(DataJpaTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)
@OverrideAutoConfiguration(enabled = false)
@TypeExcludeFilters(DataJpaTypeExcludeFilter.class)
@Transactional
@AutoConfigureCache
@AutoConfigureDataJpa
@AutoConfigureTestDatabase
@AutoConfigureTestEntityManager
@ImportAutoConfiguration
public @interface DataJpaTest {
   ...
}

 

반응형

'IT > 데이터베이스' 카테고리의 다른 글

[JPA] Null value was assigned to a property exception  (0) 2022.09.18
JPA 낙관적 잠금 (Optimistic locking)을 알아보자  (0) 2022.09.13
JPA 공부 - 6  (0) 2021.02.25
JPA 공부 - 3  (0) 2021.02.25
JPA 공부 - 5  (0) 2021.02.07