diff --git a/src/main/asciidoc/appendix/migrating.adoc b/src/main/asciidoc/appendix/migrating.adoc index f1c944c79..30c873f72 100644 --- a/src/main/asciidoc/appendix/migrating.adoc +++ b/src/main/asciidoc/appendix/migrating.adoc @@ -138,6 +138,8 @@ interface and its only implementation `org.springframework.data.neo4j.bookmark.C SDN uses Bookmarks for all transactions, without configuration. You can remove the bean declaration of `CaffeineBookmarkManager` as well as the dependency to `com.github.ben-manes.caffeine:caffeine`. +If you absolutely must, you can disable the automatic bookmark management by following <>. + [[migrating.autoindex]] === Automatic creation of constraints and indexes diff --git a/src/main/asciidoc/faq/faq.adoc b/src/main/asciidoc/faq/faq.adoc index a45baea0a..edc439cae 100644 --- a/src/main/asciidoc/faq/faq.adoc +++ b/src/main/asciidoc/faq/faq.adoc @@ -382,7 +382,7 @@ No, you don't. SDN uses Neo4j Causal Cluster bookmarks internally without any configuration on your side required. Transactions in the same thread or the same reactive stream following each other will be able to read their previously changed values as you would expect. -[[faq.bookmarks]] +[[faq.bookmarks.seeding]] == Can I retrieve the latest Bookmarks or seed the transaction manager? As mentioned briefly in <>, there is no need to configure anything with regard to bookmarks. @@ -422,6 +422,8 @@ import org.neo4j.driver.Driver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.neo4j.core.DatabaseSelectionProvider; +import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager; +import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager; import org.springframework.transaction.PlatformTransactionManager; @Configuration @@ -452,6 +454,46 @@ public class BookmarkSeedingConfig { WARNING: There is *no* need to do any of these things above, unless your application has the need to access or provide this data. If in doubt, don't do either. +[[faq.bookmarks.noop]] +== Can I disable bookmark management? + +We provide a Noop bookmark manager that effectively disables bookmark management. + +WARNING: Use this bookmark manager at your own risk, it will effectively disable any bookmark management by dropping all + bookmarks and never supplying any. In a cluster you will be at a high risk of experiencing stale reads. In a single + instance it will most likely not make any difference. + + + In a cluster this can be a sensible approach only and if only you can tolerate stale reads and are not in danger of + overwriting old data. + +You need to provide the following configuration in your system and make sure that SDN uses the transaction manager: + +[source,java,indent=0,tabsize=4] +.BookmarksDisabledConfig.java +---- +import org.neo4j.driver.Driver; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.neo4j.core.DatabaseSelectionProvider; +import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager; +import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager; +import org.springframework.transaction.PlatformTransactionManager; + +@Configuration +public class BookmarksDisabledConfig { + + @Bean + public PlatformTransactionManager transactionManager( + Driver driver, DatabaseSelectionProvider databaseNameProvider) { + + Neo4jBookmarkManager bookmarkManager = Neo4jBookmarkManager.noop(); // <.> + return new Neo4jTransactionManager( + driver, databaseNameProvider, bookmarkManager); + } +} +---- +<.> Get an instance of the Noop bookmark manager + [[faq.annotations.specific]] == Do I need to use Neo4j specific annotations? diff --git a/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jBookmarkManager.java b/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jBookmarkManager.java index 2915db5be..3e2a4a538 100644 --- a/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jBookmarkManager.java +++ b/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jBookmarkManager.java @@ -60,6 +60,24 @@ public final class Neo4jBookmarkManager { return new Neo4jBookmarkManager(bookmarksSupplier); } + /** + * Use this bookmark manager at your own risk, it will effectively disable any bookmark management by dropping all + * bookmarks and never supplying any. In a cluster you will be at a high risk of experiencing stale reads. In a single + * instance it will most likely not make any difference. + *

+ * In a cluster this can be a sensible approach only and if only you can tolerate stale reads and are not in danger of + * overwriting old data. + * + * @return A noop bookmark manager, dropping new bookmarks immediately, never supplying bookmarks. + * @since 6.1.11 + */ + @API(status = API.Status.STABLE, since = "6.1.11") + public static Neo4jBookmarkManager noop() { + return new Neo4jBookmarkManager(null, true); + } + + private final boolean noop; + private final Set bookmarks = new HashSet<>(); private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); @@ -72,11 +90,20 @@ public final class Neo4jBookmarkManager { private ApplicationEventPublisher applicationEventPublisher; private Neo4jBookmarkManager(@Nullable Supplier> bookmarksSupplier) { + this(bookmarksSupplier, false); + } + + private Neo4jBookmarkManager(@Nullable Supplier> bookmarksSupplier, boolean noop) { this.bookmarksSupplier = bookmarksSupplier == null ? () -> Collections.emptySet() : bookmarksSupplier; + this.noop = noop; } Collection getBookmarks() { + if (noop) { + return Collections.emptyList(); + } + try { read.lock(); HashSet bookmarksToUse = new HashSet<>(this.bookmarks); @@ -88,6 +115,11 @@ public final class Neo4jBookmarkManager { } void updateBookmarks(Collection usedBookmarks, Bookmark lastBookmark) { + + if (noop) { + return; + } + try { write.lock(); bookmarks.removeAll(usedBookmarks); diff --git a/src/test/java/org/springframework/data/neo4j/core/transaction/Neo4jBookmarkManagerTest.java b/src/test/java/org/springframework/data/neo4j/core/transaction/Neo4jBookmarkManagerTest.java index 45683e07a..adf35394d 100644 --- a/src/test/java/org/springframework/data/neo4j/core/transaction/Neo4jBookmarkManagerTest.java +++ b/src/test/java/org/springframework/data/neo4j/core/transaction/Neo4jBookmarkManagerTest.java @@ -24,6 +24,7 @@ import java.util.HashSet; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.neo4j.driver.Bookmark; @@ -119,6 +120,37 @@ class Neo4jBookmarkManagerTest { assertThat(bookmarkManager.getBookmarks()).containsExactly(newBookmark); } + @Nested + class NoopTests { + + + @Test + void shouldAlwaysReturnEmptyList() { + + Neo4jBookmarkManager bookmarkManager = Neo4jBookmarkManager.noop(); + assertThat(bookmarkManager.getBookmarks()) + .isSameAs(Collections.emptyList()) // Might not be that sane to check that but alas + .isEmpty(); + } + + + @Test + void shouldNeverAcceptBookmarks() { + + BookmarkForTesting bookmark = new BookmarkForTesting(Collections.singleton("a")); + AtomicBoolean asserted = new AtomicBoolean(false); + + final Neo4jBookmarkManager bookmarkManager = Neo4jBookmarkManager.noop(); + bookmarkManager.setApplicationEventPublisher(event -> { + assertThat(((Neo4jBookmarksUpdatedEvent) event).getBookmarks()).containsExactly(bookmark); + asserted.set(true); + }); + + bookmarkManager.updateBookmarks(new HashSet<>(), bookmark); + assertThat(asserted).isFalse(); + } + } + static private class BookmarkForTesting implements Bookmark { private final Set values;