diff --git a/src/main/java/org/springframework/data/mapping/callback/EntityCallbackDiscoverer.java b/src/main/java/org/springframework/data/mapping/callback/EntityCallbackDiscoverer.java index ad65c6de09..f80a1da226 100644 --- a/src/main/java/org/springframework/data/mapping/callback/EntityCallbackDiscoverer.java +++ b/src/main/java/org/springframework/data/mapping/callback/EntityCallbackDiscoverer.java @@ -44,6 +44,7 @@ /** * @author Mark Paluch * @author Christoph Strobl + * @author Myeonghyeon Lee * @since 2.2 */ class EntityCallbackDiscoverer { @@ -383,9 +384,13 @@ Collection> getEntityCallbacks() { if (this.entityCallbackBeans.isEmpty()) { if (cachedEntityCallbacks.size() != entityCallbacks.size()) { - cachedEntityCallbacks.clear(); - cachedEntityCallbacks.addAll(entityCallbacks); - AnnotationAwareOrderComparator.sort(cachedEntityCallbacks); + List> entityCallbacks = new ArrayList<>(this.entityCallbacks.size()); + AnnotationAwareOrderComparator.sort(entityCallbacks); + + synchronized(this) { + cachedEntityCallbacks.clear(); + cachedEntityCallbacks.addAll(entityCallbacks); + } } return cachedEntityCallbacks; diff --git a/src/test/java/org/springframework/data/mapping/callback/EntityCallbackDiscovererUnitTests.java b/src/test/java/org/springframework/data/mapping/callback/EntityCallbackDiscovererUnitTests.java index 17dfd9b1cc..5d9c24bca9 100644 --- a/src/test/java/org/springframework/data/mapping/callback/EntityCallbackDiscovererUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/callback/EntityCallbackDiscovererUnitTests.java @@ -18,6 +18,9 @@ import static org.assertj.core.api.Assertions.*; import java.util.Collection; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -33,6 +36,7 @@ /** * @author Christoph Strobl + * @author Myeonghyeon Lee */ class EntityCallbackDiscovererUnitTests { @@ -49,6 +53,40 @@ void shouldDiscoverCallbackType() { assertThat(entityCallbacks).hasSize(1).element(0).isInstanceOf(MyBeforeSaveCallback.class); } + @Test // DATACMNS-1735 + void shouldDiscoverCallbackTypeConcurrencyCache() throws InterruptedException { + + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfig.class); + + EntityCallbackDiscoverer discoverer = new EntityCallbackDiscoverer(ctx); + + int concurrencyCount = 4000; + CountDownLatch startLatch = new CountDownLatch(concurrencyCount); + CountDownLatch doneLatch = new CountDownLatch(concurrencyCount); + + List exceptions = new CopyOnWriteArrayList<>(); + for (int i = 0; i < concurrencyCount; i++) { + Thread thread = new Thread(() -> { + try { + startLatch.countDown(); + startLatch.await(); + + discoverer.getEntityCallbacks(PersonDocument.class, + ResolvableType.forType(BeforeSaveCallback.class)); + } catch (Exception ex) { + exceptions.add(ex); + } finally { + doneLatch.countDown(); + } + }); + thread.start(); + } + + doneLatch.await(); + + assertThat(exceptions).isEmpty(); + } + @Test // DATACMNS-1467 void shouldDiscoverCallbackTypeByName() {