HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
🪠
jpa 엔티티 연관관계 리스트 초기화 문제
🪠

jpa 엔티티 연관관계 리스트 초기화 문제

작성자
태그
Dev
속성
Aug 11, 2022 01:30 PM

문제 상황

  • 서버에 배포된 낙찰 api 에서 가장 높은 가격으로 입찰한 입찰자에게 상품이 낙찰되는것이 아니라 가장 먼저 입찰한 입찰자에게 상품이 낙찰되는 문제가 발생했습니다.
  • Product도메인의 selectWinner() 메서드에서는 해당 상품의 모든 입찰 정보를 biddings 리스트로 가져와서 리스트의 첫번째 값을 기반으로 낙찰자를 선정합니다.
    • notion image
    • 여기서 문제점은 상품의 낙찰 리스트를 “높은 가격순으로 정렬" 하지 않기 때문에
    • 최고 가격을 제시한 입찰자가 낙찰되지 못하는 문제가 발생합니다.
    •  

      시도했던 방법1

    • 조회해온 리스트 정렬
      • DB에서 조회해온 컬렉션은 불변 리스트라 정렬 불가능 - (제프 의견)

      시도했던 방법2

    • 그래서 제프가 biddings 리스트를 가격순으로 정렬해서 리스트의 첫번째 값을 가져오는식으로 해결했습니다.
      • notion image
    • 그런데 여기서 또 문제가 되는점이 하이버네이트에서 컬렉션 타입은 영속화 될 때org.hibernate.collection.internal.PersistentBag 타입으로 래핑됩니다.
      • notion image
        하이버네이트는 PersistentBag을 이용하여 영속적 전이(cascade)와 고아 객체(orphanremoval) 를 추적하는데, 임의로 새로운 ArrayList를 생성하여 참조를 변경하니 PersistentBag 인스턴스가 엔티티와의 참조가 끊어져 버려 문제가 발생하게 된 것입니다.
      • 참고:
      • [JPA-Hibernate] orphanRemoval 옵션 사용시 Collection 참조를 변경하지 말자
        orphanRemoval 옵션으로 설정된 컬렉션을 연관관계로 가지는 경우, 컬렉션이 empty가 아닐때 참조 인스턴스를 변경하면 오류가 발생합니다. 이번 포스팅에선, 우아한 테크 캠프 Pro 3기 - subway 미션 중 경험한 문제를 기록하고자 합니다. 이번 미션의 주제는 ATDD 기반으로 지하철 노선 도메인을 정의하고 구간 추가, 삭제 등의 기능을 추가해보는 것입니다.
        https://jerry92k.tistory.com/44
    • 그래서 다음과 같은 에러가 발생합니다.
      • org.springframework.orm.jpa.JpaSystemException: A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: com.saiko.bidmarket.product.entity.Product.biddings; nested exception is org.hibernate.HibernateException: A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: com.saiko.bidmarket.product.entity.Product.biddings at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:331) ~[spring-orm-5.3.22.jar:5.3.22] at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:233) ~[spring-orm-5.3.22.jar:5.3.22] at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:566) ~[spring-orm-5.3.22.jar:5.3.22] at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:743) ~[spring-tx-5.3.22.jar:5.3.22] at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:711) ~[spring-tx-5.3.22.jar:5.3.22] at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:654) ~[spring-tx-5.3.22.jar:5.3.22] at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:407) ~[spring-tx-5.3.22.jar:5.3.22] at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.22.jar:5.3.22] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.22.jar:5.3.22] at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763) ~[spring-aop-5.3.22.jar:5.3.22] at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708) ~[spring-aop-5.3.22.jar:5.3.22] at com.saiko.bidmarket.common.config.ScheduledConfig$Scheduler$$EnhancerBySpringCGLIB$$3ae9458d.closeProduct(<generated>) ~[main/:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na] at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84) ~[spring-context-5.3.22.jar:5.3.22] at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) ~[spring-context-5.3.22.jar:5.3.22] at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:95) ~[spring-context-5.3.22.jar:5.3.22] at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) ~[na:na] at java.base/java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:264) ~[na:na] at java.base/java.util.concurrent.FutureTask.run(FutureTask.java) ~[na:na] at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) ~[na:na] at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na] at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na] at java.base/java.lang.Thread.run(Thread.java:829) ~[na:na] Caused by: org.hibernate.HibernateException: A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: com.saiko.bidmarket.product.entity.Product.biddings at org.hibernate.engine.internal.Collections.processDereferencedCollection(Collections.java:100) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.hibernate.engine.internal.Collections.processUnreachableCollection(Collections.java:51) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.hibernate.event.internal.AbstractFlushingEventListener.lambda$flushCollections$1(AbstractFlushingEventListener.java:251) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.hibernate.engine.internal.StatefulPersistenceContext.forEachCollectionEntry(StatefulPersistenceContext.java:1136) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.hibernate.event.internal.AbstractFlushingEventListener.flushCollections(AbstractFlushingEventListener.java:248) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:94) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1407) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:489) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3290) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2425) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:449) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:183) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$300(JdbcResourceLocalTransactionCoordinatorImpl.java:40) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:281) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:101) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:562) ~[spring-orm-5.3.22.jar:5.3.22] ... 23 common frames omitted
         
         

      해결한 방법

    • Product.biddings 에 @OrderBy 어노테이션을 붙여서 해결했습니다.
      • 발생하는 쿼리 확인
        • Hibernate: select biddings0_.product_id as product_7_0_1_, biddings0_.id as id1_0_1_, biddings0_.id as id1_0_0_, biddings0_.created_at as created_2_0_0_, biddings0_.updated_at as updated_3_0_0_, biddings0_.`bidder_id` as bidder_i6_0_0_, biddings0_.bidding_price as bidding_4_0_0_, biddings0_.product_id as product_7_0_0_, biddings0_.won as won5_0_0_ from bidding biddings0_ where biddings0_.product_id in ( ?, ?, ?, ?, ?, ?, ?, ?, ? ) order by biddings0_.bidding_price desc
      • 참고:
      • Hibernate Advanced : javax.persistence @OrderBy 사용하기
        1. 이 포스트는 javax.persistence @OrderBy annotation을 설명한다. 2. @OrderBy는 데이터베이스의 Query 구문의 Order by 구문과 동일한 효과를 가진다. 2-0 @OrderBy("fieldname [ASC | DESC] ") 형식으로 사용된다. 2-1 데이터베이스 실행 시 가장 끝에 order by가 붙으며 지정된 field와 정렬 순서가 그대로 삽입된다. 2-2 가지고 올 때도 순서를 지정하기 때문에 List, Set, Map에서는 의미가 있다.
        Hibernate Advanced : javax.persistence @OrderBy 사용하기
        https://kogle.tistory.com/140
        Hibernate Advanced : javax.persistence @OrderBy 사용하기