Skip to content

Commit a1c9c8c

Browse files
fix: Unify Bookmarkmanager creation and usage for good.
1 parent 1f35f58 commit a1c9c8c

File tree

13 files changed

+506
-79
lines changed

13 files changed

+506
-79
lines changed

src/main/asciidoc/faq/index.adoc

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -734,33 +734,28 @@ WARNING: Use this bookmark manager at your own risk, it will effectively disable
734734
In a cluster this can be a sensible approach only and if only you can tolerate stale reads and are not in danger of
735735
overwriting old data.
736736

737-
You need to provide the following configuration in your system and make sure that SDN uses the transaction manager:
737+
The following configuration creates a "noop" variant of the bookmark manager that will be picked up from relevant classes.
738738

739739
[source,java,indent=0,tabsize=4]
740740
.BookmarksDisabledConfig.java
741741
----
742742
import org.neo4j.driver.Driver;
743743
import org.springframework.context.annotation.Bean;
744744
import org.springframework.context.annotation.Configuration;
745-
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
746745
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
747-
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
748-
import org.springframework.transaction.PlatformTransactionManager;
749746
750747
@Configuration
751748
public class BookmarksDisabledConfig {
752749
753750
@Bean
754-
public PlatformTransactionManager transactionManager(
755-
Driver driver, DatabaseSelectionProvider databaseNameProvider) {
751+
public Neo4jBookmarkManager neo4jBookmarkManager() {
756752
757-
Neo4jBookmarkManager bookmarkManager = Neo4jBookmarkManager.noop(); // <.>
758-
return new Neo4jTransactionManager(
759-
driver, databaseNameProvider, bookmarkManager);
753+
return Neo4jBookmarkManager.noop();
760754
}
761755
}
762756
----
763-
<.> Get an instance of the Noop bookmark manager
757+
758+
You can configure the pairs of `Neo4jTransactionManager/Neo4jClient` and `ReactiveNeo4jTransactionManager/ReactiveNeo4jClient` individually, but we recommend in doing so only when you already configuring them for specific database selection needs.
764759

765760
[[faq.annotations.specific]]
766761
== Do I need to use Neo4j specific annotations?

src/main/java/org/springframework/data/neo4j/core/DefaultNeo4jClient.java

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,9 @@
1616
package org.springframework.data.neo4j.core;
1717

1818
import java.util.Collection;
19-
import java.util.Collections;
20-
import java.util.HashSet;
2119
import java.util.Map;
2220
import java.util.Objects;
2321
import java.util.Optional;
24-
import java.util.Set;
25-
import java.util.concurrent.locks.ReentrantReadWriteLock;
2622
import java.util.function.BiConsumer;
2723
import java.util.function.BiFunction;
2824
import java.util.function.Function;
@@ -39,12 +35,17 @@
3935
import org.neo4j.driver.Value;
4036
import org.neo4j.driver.summary.ResultSummary;
4137
import org.neo4j.driver.types.TypeSystem;
38+
import org.springframework.beans.BeansException;
39+
import org.springframework.context.ApplicationContext;
40+
import org.springframework.context.ApplicationContextAware;
4241
import org.springframework.core.convert.ConversionService;
4342
import org.springframework.core.convert.converter.ConverterRegistry;
4443
import org.springframework.core.convert.support.DefaultConversionService;
4544
import org.springframework.dao.DataAccessException;
4645
import org.springframework.dao.support.PersistenceExceptionTranslator;
4746
import org.springframework.data.neo4j.core.convert.Neo4jConversions;
47+
import org.springframework.data.neo4j.core.support.BookmarkManagerReference;
48+
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
4849
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
4950
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionUtils;
5051
import org.springframework.lang.Nullable;
@@ -59,23 +60,23 @@
5960
* @author Michael J. Simons
6061
* @since 6.0
6162
*/
62-
final class DefaultNeo4jClient implements Neo4jClient {
63+
final class DefaultNeo4jClient implements Neo4jClient, ApplicationContextAware {
6364

6465
private final Driver driver;
6566
private @Nullable final DatabaseSelectionProvider databaseSelectionProvider;
6667
private @Nullable final UserSelectionProvider userSelectionProvider;
6768
private final ConversionService conversionService;
6869
private final Neo4jPersistenceExceptionTranslator persistenceExceptionTranslator = new Neo4jPersistenceExceptionTranslator();
6970

70-
// Basically a local bookmark manager
71-
private final Set<Bookmark> bookmarks = new HashSet<>();
72-
private final ReentrantReadWriteLock bookmarksLock = new ReentrantReadWriteLock();
71+
// Local bookmark manager when using outside managed transactions
72+
private final BookmarkManagerReference bookmarkManager;
7373

7474
DefaultNeo4jClient(Builder builder) {
7575

7676
this.driver = builder.driver;
7777
this.databaseSelectionProvider = builder.databaseSelectionProvider;
7878
this.userSelectionProvider = builder.userSelectionProvider;
79+
this.bookmarkManager = new BookmarkManagerReference(Neo4jBookmarkManager::create, null);
7980

8081
this.conversionService = new DefaultConversionService();
8182
Optional.ofNullable(builder.neo4jConversions).orElseGet(Neo4jConversions::new).registerConvertersIn((ConverterRegistry) conversionService);
@@ -85,29 +86,19 @@ final class DefaultNeo4jClient implements Neo4jClient {
8586
public QueryRunner getQueryRunner(DatabaseSelection databaseSelection, UserSelection impersonatedUser) {
8687

8788
QueryRunner queryRunner = Neo4jTransactionManager.retrieveTransaction(driver, databaseSelection, impersonatedUser);
88-
Collection<Bookmark> lastBookmarks = Collections.emptySet();
89+
Collection<Bookmark> lastBookmarks = bookmarkManager.resolve().getBookmarks();
90+
8991
if (queryRunner == null) {
90-
ReentrantReadWriteLock.ReadLock lock = bookmarksLock.readLock();
91-
try {
92-
lock.lock();
93-
lastBookmarks = new HashSet<>(bookmarks);
94-
queryRunner = driver.session(Neo4jTransactionUtils.sessionConfig(false, lastBookmarks, databaseSelection, impersonatedUser));
95-
} finally {
96-
lock.unlock();
97-
}
92+
queryRunner = driver.session(Neo4jTransactionUtils.sessionConfig(false, lastBookmarks, databaseSelection, impersonatedUser));
9893
}
9994

100-
return new DelegatingQueryRunner(queryRunner, lastBookmarks, (usedBookmarks, newBookmarks) -> {
95+
return new DelegatingQueryRunner(queryRunner, lastBookmarks, bookmarkManager.resolve()::updateBookmarks);
96+
}
97+
98+
@Override
99+
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
101100

102-
ReentrantReadWriteLock.WriteLock lock = bookmarksLock.writeLock();
103-
try {
104-
lock.lock();
105-
bookmarks.removeAll(usedBookmarks);
106-
bookmarks.addAll(newBookmarks);
107-
} finally {
108-
lock.unlock();
109-
}
110-
});
101+
this.bookmarkManager.setApplicationContext(applicationContext);
111102
}
112103

113104
private static class DelegatingQueryRunner implements QueryRunner {

src/main/java/org/springframework/data/neo4j/core/DefaultReactiveNeo4jClient.java

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,16 @@
2626
import org.neo4j.driver.summary.ResultSummary;
2727
import org.neo4j.driver.types.TypeSystem;
2828
import org.reactivestreams.Publisher;
29+
import org.springframework.beans.BeansException;
30+
import org.springframework.context.ApplicationContext;
31+
import org.springframework.context.ApplicationContextAware;
2932
import org.springframework.core.convert.ConversionService;
3033
import org.springframework.core.convert.converter.ConverterRegistry;
3134
import org.springframework.core.convert.support.DefaultConversionService;
3235
import org.springframework.dao.DataAccessException;
3336
import org.springframework.data.neo4j.core.convert.Neo4jConversions;
37+
import org.springframework.data.neo4j.core.support.BookmarkManagerReference;
38+
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
3439
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionUtils;
3540
import org.springframework.data.neo4j.core.transaction.ReactiveNeo4jTransactionManager;
3641
import org.springframework.lang.Nullable;
@@ -43,12 +48,8 @@
4348
import reactor.util.function.Tuples;
4449

4550
import java.util.Collection;
46-
import java.util.Collections;
47-
import java.util.HashSet;
4851
import java.util.Map;
4952
import java.util.Optional;
50-
import java.util.Set;
51-
import java.util.concurrent.locks.ReentrantReadWriteLock;
5253
import java.util.function.BiConsumer;
5354
import java.util.function.BiFunction;
5455
import java.util.function.Function;
@@ -62,17 +63,16 @@
6263
* @soundtrack Die Toten Hosen - Im Auftrag des Herrn
6364
* @since 6.0
6465
*/
65-
final class DefaultReactiveNeo4jClient implements ReactiveNeo4jClient {
66+
final class DefaultReactiveNeo4jClient implements ReactiveNeo4jClient, ApplicationContextAware {
6667

6768
private final Driver driver;
6869
private @Nullable final ReactiveDatabaseSelectionProvider databaseSelectionProvider;
6970
private @Nullable final ReactiveUserSelectionProvider userSelectionProvider;
7071
private final ConversionService conversionService;
7172
private final Neo4jPersistenceExceptionTranslator persistenceExceptionTranslator = new Neo4jPersistenceExceptionTranslator();
7273

73-
// Basically a local bookmark manager
74-
private final Set<Bookmark> bookmarks = new HashSet<>();
75-
private final ReentrantReadWriteLock bookmarksLock = new ReentrantReadWriteLock();
74+
// Local bookmark manager when using outside managed transactions
75+
private final BookmarkManagerReference bookmarkManager;
7676

7777
DefaultReactiveNeo4jClient(Builder builder) {
7878

@@ -82,6 +82,7 @@ final class DefaultReactiveNeo4jClient implements ReactiveNeo4jClient {
8282

8383
this.conversionService = new DefaultConversionService();
8484
Optional.ofNullable(builder.neo4jConversions).orElseGet(Neo4jConversions::new).registerConvertersIn((ConverterRegistry) conversionService);
85+
this.bookmarkManager = new BookmarkManagerReference(Neo4jBookmarkManager::create, null);
8586
}
8687

8788
@Override
@@ -91,27 +92,18 @@ public Mono<ReactiveQueryRunner> getQueryRunner(Mono<DatabaseSelection> database
9192
.flatMap(targetDatabaseAndUser ->
9293
ReactiveNeo4jTransactionManager.retrieveReactiveTransaction(driver, targetDatabaseAndUser.getT1(), targetDatabaseAndUser.getT2())
9394
.map(ReactiveQueryRunner.class::cast)
94-
.zipWith(Mono.just(Collections.<Bookmark>emptySet()))
95+
.zipWith(Mono.just(bookmarkManager.resolve().getBookmarks()))
9596
.switchIfEmpty(Mono.fromSupplier(() -> {
96-
ReentrantReadWriteLock.ReadLock lock = bookmarksLock.readLock();
97-
try {
98-
lock.lock();
99-
Set<Bookmark> lastBookmarks = new HashSet<>(bookmarks);
100-
return Tuples.of(driver.session(ReactiveSession.class, Neo4jTransactionUtils.sessionConfig(false, lastBookmarks, targetDatabaseAndUser.getT1(), targetDatabaseAndUser.getT2())), lastBookmarks);
101-
} finally {
102-
lock.unlock();
103-
}
97+
Collection<Bookmark> lastBookmarks = bookmarkManager.resolve().getBookmarks();
98+
return Tuples.of(driver.session(ReactiveSession.class, Neo4jTransactionUtils.sessionConfig(false, lastBookmarks, targetDatabaseAndUser.getT1(), targetDatabaseAndUser.getT2())), lastBookmarks);
10499
})))
105-
.map(t -> new DelegatingQueryRunner(t.getT1(), t.getT2(), (usedBookmarks, newBookmarks) -> {
106-
ReentrantReadWriteLock.WriteLock lock = bookmarksLock.writeLock();
107-
try {
108-
lock.lock();
109-
bookmarks.removeAll(usedBookmarks);
110-
bookmarks.addAll(newBookmarks);
111-
} finally {
112-
lock.unlock();
113-
}
114-
}));
100+
.map(t -> new DelegatingQueryRunner(t.getT1(), t.getT2(), bookmarkManager.resolve()::updateBookmarks));
101+
}
102+
103+
@Override
104+
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
105+
106+
bookmarkManager.setApplicationContext(applicationContext);
115107
}
116108

117109
private static class DelegatingQueryRunner implements ReactiveQueryRunner {
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright 2011-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.neo4j.core.support;
17+
18+
import java.util.function.Supplier;
19+
20+
import org.springframework.beans.BeansException;
21+
import org.springframework.beans.factory.BeanCreationException;
22+
import org.springframework.beans.factory.ObjectProvider;
23+
import org.springframework.context.ApplicationContext;
24+
import org.springframework.context.ApplicationContextAware;
25+
import org.springframework.context.ApplicationEventPublisher;
26+
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
27+
import org.springframework.lang.Nullable;
28+
29+
/**
30+
* Don't use outside SDN code. You have been warned.
31+
*
32+
* @author Michael J. Simons
33+
*/
34+
public final class BookmarkManagerReference implements ApplicationContextAware {
35+
36+
private final Supplier<Neo4jBookmarkManager> defaultBookmarkManagerSupplier;
37+
38+
private ObjectProvider<Neo4jBookmarkManager> neo4jBookmarkManagers = new ObjectProvider<Neo4jBookmarkManager>() {
39+
@Override
40+
public Neo4jBookmarkManager getObject(Object... args) throws BeansException {
41+
throw new BeanCreationException("This provider can't create new beans");
42+
}
43+
44+
@Override
45+
public Neo4jBookmarkManager getIfAvailable() throws BeansException {
46+
return null;
47+
}
48+
49+
@Override
50+
public Neo4jBookmarkManager getIfUnique() throws BeansException {
51+
return null;
52+
}
53+
54+
@Override
55+
public Neo4jBookmarkManager getObject() throws BeansException {
56+
throw new BeanCreationException("This provider can't create new beans");
57+
}
58+
};
59+
60+
@Nullable
61+
private volatile Neo4jBookmarkManager bookmarkManager;
62+
63+
private ApplicationEventPublisher applicationEventPublisher;
64+
65+
public BookmarkManagerReference(Supplier<Neo4jBookmarkManager> defaultBookmarkManagerSupplier, @Nullable Neo4jBookmarkManager bookmarkManager) {
66+
this.defaultBookmarkManagerSupplier = defaultBookmarkManagerSupplier;
67+
this.bookmarkManager = bookmarkManager;
68+
}
69+
70+
@Override
71+
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
72+
73+
this.neo4jBookmarkManagers = applicationContext.getBeanProvider(Neo4jBookmarkManager.class);
74+
this.applicationEventPublisher = applicationContext;
75+
if (this.bookmarkManager != null) {
76+
this.bookmarkManager.setApplicationEventPublisher(this.applicationEventPublisher);
77+
}
78+
}
79+
80+
public Neo4jBookmarkManager resolve() {
81+
Neo4jBookmarkManager result = this.bookmarkManager;
82+
if (result == null) {
83+
synchronized (this) {
84+
result = this.bookmarkManager;
85+
if (result == null) {
86+
this.bookmarkManager = neo4jBookmarkManagers.getIfAvailable(this.defaultBookmarkManagerSupplier);
87+
this.bookmarkManager.setApplicationEventPublisher(this.applicationEventPublisher);
88+
result = this.bookmarkManager;
89+
}
90+
}
91+
}
92+
return result;
93+
}
94+
}

src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jTransactionManager.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
3131
import org.springframework.data.neo4j.core.UserSelection;
3232
import org.springframework.data.neo4j.core.UserSelectionProvider;
33+
import org.springframework.data.neo4j.core.support.BookmarkManagerReference;
3334
import org.springframework.lang.Nullable;
3435
import org.springframework.transaction.TransactionDefinition;
3536
import org.springframework.transaction.TransactionException;
@@ -136,7 +137,7 @@ public Neo4jTransactionManager build() {
136137
*/
137138
private final UserSelectionProvider userSelectionProvider;
138139

139-
private final Neo4jBookmarkManager bookmarkManager;
140+
private final BookmarkManagerReference bookmarkManager;
140141

141142
/**
142143
* This will create a transaction manager for the default database.
@@ -181,14 +182,13 @@ private Neo4jTransactionManager(Builder builder) {
181182
this.userSelectionProvider = builder.userSelectionProvider == null ?
182183
UserSelectionProvider.getDefaultSelectionProvider() :
183184
builder.userSelectionProvider;
184-
this.bookmarkManager =
185-
builder.bookmarkManager == null ? Neo4jBookmarkManager.create() : builder.bookmarkManager;
185+
this.bookmarkManager = new BookmarkManagerReference(Neo4jBookmarkManager::create, builder.bookmarkManager);
186186
}
187187

188188
@Override
189189
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
190190

191-
this.bookmarkManager.setApplicationEventPublisher(applicationContext);
191+
this.bookmarkManager.setApplicationContext(applicationContext);
192192
}
193193

194194
/**
@@ -298,7 +298,7 @@ protected void doBegin(Object transaction, TransactionDefinition definition) thr
298298
try {
299299
// Prepare configuration data
300300
Neo4jTransactionContext context = new Neo4jTransactionContext(
301-
databaseSelectionProvider.getDatabaseSelection(), userSelectionProvider.getUserSelection(), bookmarkManager.getBookmarks());
301+
databaseSelectionProvider.getDatabaseSelection(), userSelectionProvider.getUserSelection(), bookmarkManager.resolve().getBookmarks());
302302

303303
// Configure and open session together with a native transaction
304304
Session session = this.driver.session(
@@ -341,7 +341,7 @@ protected void doCommit(DefaultTransactionStatus status) throws TransactionExcep
341341
Neo4jTransactionObject transactionObject = extractNeo4jTransaction(status);
342342
Neo4jTransactionHolder transactionHolder = transactionObject.getRequiredResourceHolder();
343343
Collection<Bookmark> newBookmarks = transactionHolder.commit();
344-
this.bookmarkManager.updateBookmarks(transactionHolder.getBookmarks(), newBookmarks);
344+
this.bookmarkManager.resolve().updateBookmarks(transactionHolder.getBookmarks(), newBookmarks);
345345
}
346346

347347
@Override

0 commit comments

Comments
 (0)