문제 상황
- 서버에 배포된 낙찰 api 에서 가장 높은 가격으로 입찰한 입찰자에게 상품이 낙찰되는것이 아니라 가장 먼저 입찰한 입찰자에게 상품이 낙찰되는 문제가 발생했습니다.
- Product도메인의 selectWinner() 메서드에서는 해당 상품의 모든 입찰 정보를 biddings 리스트로 가져와서 리스트의 첫번째 값을 기반으로 낙찰자를 선정합니다.
- 여기서 문제점은 상품의 낙찰 리스트를 “높은 가격순으로 정렬" 하지 않기 때문에
- 최고 가격을 제시한 입찰자가 낙찰되지 못하는 문제가 발생합니다.
- 조회해온 리스트 정렬
- DB에서 조회해온 컬렉션은 불변 리스트라 정렬 불가능 - (제프 의견)
- 그래서 제프가 biddings 리스트를 가격순으로 정렬해서 리스트의 첫번째 값을 가져오는식으로 해결했습니다.
- 그런데 여기서 또 문제가 되는점이 하이버네이트에서 컬렉션 타입은 영속화 될 때org.hibernate.collection.internal.PersistentBag 타입으로 래핑됩니다.
- 참고:
- 그래서 다음과 같은 에러가 발생합니다.

시도했던 방법1
시도했던 방법2


하이버네이트는 PersistentBag을 이용하여 영속적 전이(cascade)와 고아 객체(orphanremoval) 를 추적하는데, 임의로 새로운 ArrayList를 생성하여 참조를 변경하니 PersistentBag 인스턴스가 엔티티와의 참조가 끊어져 버려 문제가 발생하게 된 것입니다.
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
해결한 방법
- 발생하는 쿼리 확인
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