fix: Unify Bookmarkmanager creation and usage for good.
This commit is contained in:
@@ -737,7 +737,7 @@ 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:
|
||||
The following configuration creates a "noop" variant of the bookmark manager that will be picked up from relevant classes.
|
||||
|
||||
[source,java,indent=0,tabsize=4]
|
||||
.BookmarksDisabledConfig.java
|
||||
@@ -745,25 +745,20 @@ You need to provide the following configuration in your system and make sure tha
|
||||
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) {
|
||||
public Neo4jBookmarkManager neo4jBookmarkManager() {
|
||||
|
||||
Neo4jBookmarkManager bookmarkManager = Neo4jBookmarkManager.noop(); // <.>
|
||||
return new Neo4jTransactionManager(
|
||||
driver, databaseNameProvider, bookmarkManager);
|
||||
return Neo4jBookmarkManager.noop();
|
||||
}
|
||||
}
|
||||
----
|
||||
<.> Get an instance of the Noop bookmark manager
|
||||
|
||||
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.
|
||||
|
||||
[[faq.annotations.specific]]
|
||||
== Do I need to use Neo4j specific annotations?
|
||||
|
||||
@@ -35,12 +35,16 @@ import org.neo4j.driver.Session;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.neo4j.driver.summary.ResultSummary;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.converter.ConverterRegistry;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.data.neo4j.core.convert.Neo4jConversions;
|
||||
import org.springframework.data.neo4j.core.support.BookmarkManagerReference;
|
||||
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
|
||||
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
|
||||
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionUtils;
|
||||
@@ -56,7 +60,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Michael J. Simons
|
||||
* @since 6.0
|
||||
*/
|
||||
final class DefaultNeo4jClient implements Neo4jClient {
|
||||
final class DefaultNeo4jClient implements Neo4jClient, ApplicationContextAware {
|
||||
|
||||
private final Driver driver;
|
||||
private @Nullable final DatabaseSelectionProvider databaseSelectionProvider;
|
||||
@@ -65,14 +69,14 @@ final class DefaultNeo4jClient implements Neo4jClient {
|
||||
private final Neo4jPersistenceExceptionTranslator persistenceExceptionTranslator = new Neo4jPersistenceExceptionTranslator();
|
||||
|
||||
// Local bookmark manager when using outside managed transactions
|
||||
private final Neo4jBookmarkManager bookmarkManager;
|
||||
private final BookmarkManagerReference bookmarkManager;
|
||||
|
||||
DefaultNeo4jClient(Builder builder) {
|
||||
|
||||
this.driver = builder.driver;
|
||||
this.databaseSelectionProvider = builder.databaseSelectionProvider;
|
||||
this.userSelectionProvider = builder.userSelectionProvider;
|
||||
this.bookmarkManager = builder.bookmarkManager != null ? builder.bookmarkManager : Neo4jBookmarkManager.create();
|
||||
this.bookmarkManager = new BookmarkManagerReference(Neo4jBookmarkManager::create, builder.bookmarkManager);
|
||||
|
||||
this.conversionService = new DefaultConversionService();
|
||||
Optional.ofNullable(builder.neo4jConversions).orElseGet(Neo4jConversions::new).registerConvertersIn((ConverterRegistry) conversionService);
|
||||
@@ -82,13 +86,19 @@ final class DefaultNeo4jClient implements Neo4jClient {
|
||||
public QueryRunner getQueryRunner(DatabaseSelection databaseSelection, UserSelection impersonatedUser) {
|
||||
|
||||
QueryRunner queryRunner = Neo4jTransactionManager.retrieveTransaction(driver, databaseSelection, impersonatedUser);
|
||||
Collection<Bookmark> lastBookmarks = bookmarkManager.getBookmarks();
|
||||
Collection<Bookmark> lastBookmarks = bookmarkManager.resolve().getBookmarks();
|
||||
|
||||
if (queryRunner == null) {
|
||||
queryRunner = driver.session(Neo4jTransactionUtils.sessionConfig(false, lastBookmarks, databaseSelection, impersonatedUser));
|
||||
}
|
||||
|
||||
return new DelegatingQueryRunner(queryRunner, lastBookmarks, bookmarkManager::updateBookmarks);
|
||||
return new DelegatingQueryRunner(queryRunner, lastBookmarks, bookmarkManager.resolve()::updateBookmarks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
|
||||
this.bookmarkManager.setApplicationContext(applicationContext);
|
||||
}
|
||||
|
||||
private static class DelegatingQueryRunner implements QueryRunner {
|
||||
|
||||
@@ -26,11 +26,15 @@ import org.neo4j.driver.reactivestreams.ReactiveSession;
|
||||
import org.neo4j.driver.summary.ResultSummary;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.converter.ConverterRegistry;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.neo4j.core.convert.Neo4jConversions;
|
||||
import org.springframework.data.neo4j.core.support.BookmarkManagerReference;
|
||||
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
|
||||
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionUtils;
|
||||
import org.springframework.data.neo4j.core.transaction.ReactiveNeo4jTransactionManager;
|
||||
@@ -59,7 +63,7 @@ import java.util.function.Supplier;
|
||||
* @soundtrack Die Toten Hosen - Im Auftrag des Herrn
|
||||
* @since 6.0
|
||||
*/
|
||||
final class DefaultReactiveNeo4jClient implements ReactiveNeo4jClient {
|
||||
final class DefaultReactiveNeo4jClient implements ReactiveNeo4jClient, ApplicationContextAware {
|
||||
|
||||
private final Driver driver;
|
||||
private @Nullable final ReactiveDatabaseSelectionProvider databaseSelectionProvider;
|
||||
@@ -68,7 +72,7 @@ final class DefaultReactiveNeo4jClient implements ReactiveNeo4jClient {
|
||||
private final Neo4jPersistenceExceptionTranslator persistenceExceptionTranslator = new Neo4jPersistenceExceptionTranslator();
|
||||
|
||||
// Local bookmark manager when using outside managed transactions
|
||||
private final Neo4jBookmarkManager bookmarkManager;
|
||||
private final BookmarkManagerReference bookmarkManager;
|
||||
|
||||
DefaultReactiveNeo4jClient(Builder builder) {
|
||||
|
||||
@@ -78,7 +82,7 @@ final class DefaultReactiveNeo4jClient implements ReactiveNeo4jClient {
|
||||
|
||||
this.conversionService = new DefaultConversionService();
|
||||
Optional.ofNullable(builder.neo4jConversions).orElseGet(Neo4jConversions::new).registerConvertersIn((ConverterRegistry) conversionService);
|
||||
this.bookmarkManager = builder.bookmarkManager != null ? builder.bookmarkManager : Neo4jBookmarkManager.createReactive();
|
||||
this.bookmarkManager = new BookmarkManagerReference(Neo4jBookmarkManager::createReactive, builder.bookmarkManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -88,12 +92,18 @@ final class DefaultReactiveNeo4jClient implements ReactiveNeo4jClient {
|
||||
.flatMap(targetDatabaseAndUser ->
|
||||
ReactiveNeo4jTransactionManager.retrieveReactiveTransaction(driver, targetDatabaseAndUser.getT1(), targetDatabaseAndUser.getT2())
|
||||
.map(ReactiveQueryRunner.class::cast)
|
||||
.zipWith(Mono.just(bookmarkManager.getBookmarks()))
|
||||
.zipWith(Mono.just(bookmarkManager.resolve().getBookmarks()))
|
||||
.switchIfEmpty(Mono.fromSupplier(() -> {
|
||||
Collection<Bookmark> lastBookmarks = bookmarkManager.getBookmarks();
|
||||
Collection<Bookmark> lastBookmarks = bookmarkManager.resolve().getBookmarks();
|
||||
return Tuples.of(driver.session(ReactiveSession.class, Neo4jTransactionUtils.sessionConfig(false, lastBookmarks, targetDatabaseAndUser.getT1(), targetDatabaseAndUser.getT2())), lastBookmarks);
|
||||
})))
|
||||
.map(t -> new DelegatingQueryRunner(t.getT1(), t.getT2(), bookmarkManager::updateBookmarks));
|
||||
.map(t -> new DelegatingQueryRunner(t.getT1(), t.getT2(), bookmarkManager.resolve()::updateBookmarks));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
|
||||
bookmarkManager.setApplicationContext(applicationContext);
|
||||
}
|
||||
|
||||
private static class DelegatingQueryRunner implements ReactiveQueryRunner {
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2011-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.neo4j.core.support;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Don't use outside SDN code. You have been warned.
|
||||
*
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
public final class BookmarkManagerReference implements ApplicationContextAware {
|
||||
|
||||
private final Supplier<Neo4jBookmarkManager> defaultBookmarkManagerSupplier;
|
||||
|
||||
private ObjectProvider<Neo4jBookmarkManager> neo4jBookmarkManagers = new ObjectProvider<Neo4jBookmarkManager>() {
|
||||
@Override
|
||||
public Neo4jBookmarkManager getObject(Object... args) throws BeansException {
|
||||
throw new BeanCreationException("This provider can't create new beans");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Neo4jBookmarkManager getIfAvailable() throws BeansException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Neo4jBookmarkManager getIfUnique() throws BeansException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Neo4jBookmarkManager getObject() throws BeansException {
|
||||
throw new BeanCreationException("This provider can't create new beans");
|
||||
}
|
||||
};
|
||||
|
||||
@Nullable
|
||||
private volatile Neo4jBookmarkManager bookmarkManager;
|
||||
|
||||
private ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
public BookmarkManagerReference(Supplier<Neo4jBookmarkManager> defaultBookmarkManagerSupplier, @Nullable Neo4jBookmarkManager bookmarkManager) {
|
||||
this.defaultBookmarkManagerSupplier = defaultBookmarkManagerSupplier;
|
||||
this.bookmarkManager = bookmarkManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
|
||||
this.neo4jBookmarkManagers = applicationContext.getBeanProvider(Neo4jBookmarkManager.class);
|
||||
this.applicationEventPublisher = applicationContext;
|
||||
if (this.bookmarkManager != null) {
|
||||
this.bookmarkManager.setApplicationEventPublisher(this.applicationEventPublisher);
|
||||
}
|
||||
}
|
||||
|
||||
public Neo4jBookmarkManager resolve() {
|
||||
Neo4jBookmarkManager result = this.bookmarkManager;
|
||||
if (result == null) {
|
||||
synchronized (this) {
|
||||
result = this.bookmarkManager;
|
||||
if (result == null) {
|
||||
this.bookmarkManager = neo4jBookmarkManagers.getIfAvailable(this.defaultBookmarkManagerSupplier);
|
||||
this.bookmarkManager.setApplicationEventPublisher(this.applicationEventPublisher);
|
||||
result = this.bookmarkManager;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import org.springframework.data.neo4j.core.DatabaseSelection;
|
||||
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
|
||||
import org.springframework.data.neo4j.core.UserSelection;
|
||||
import org.springframework.data.neo4j.core.UserSelectionProvider;
|
||||
import org.springframework.data.neo4j.core.support.BookmarkManagerReference;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionException;
|
||||
@@ -136,7 +137,7 @@ public final class Neo4jTransactionManager extends AbstractPlatformTransactionMa
|
||||
*/
|
||||
private final UserSelectionProvider userSelectionProvider;
|
||||
|
||||
private final Neo4jBookmarkManager bookmarkManager;
|
||||
private final BookmarkManagerReference bookmarkManager;
|
||||
|
||||
/**
|
||||
* This will create a transaction manager for the default database.
|
||||
@@ -181,14 +182,13 @@ public final class Neo4jTransactionManager extends AbstractPlatformTransactionMa
|
||||
this.userSelectionProvider = builder.userSelectionProvider == null ?
|
||||
UserSelectionProvider.getDefaultSelectionProvider() :
|
||||
builder.userSelectionProvider;
|
||||
this.bookmarkManager =
|
||||
builder.bookmarkManager == null ? Neo4jBookmarkManager.create() : builder.bookmarkManager;
|
||||
this.bookmarkManager = new BookmarkManagerReference(Neo4jBookmarkManager::create, builder.bookmarkManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
|
||||
this.bookmarkManager.setApplicationEventPublisher(applicationContext);
|
||||
this.bookmarkManager.setApplicationContext(applicationContext);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -298,7 +298,7 @@ public final class Neo4jTransactionManager extends AbstractPlatformTransactionMa
|
||||
try {
|
||||
// Prepare configuration data
|
||||
Neo4jTransactionContext context = new Neo4jTransactionContext(
|
||||
databaseSelectionProvider.getDatabaseSelection(), userSelectionProvider.getUserSelection(), bookmarkManager.getBookmarks());
|
||||
databaseSelectionProvider.getDatabaseSelection(), userSelectionProvider.getUserSelection(), bookmarkManager.resolve().getBookmarks());
|
||||
|
||||
// Configure and open session together with a native transaction
|
||||
Session session = this.driver.session(
|
||||
@@ -341,7 +341,7 @@ public final class Neo4jTransactionManager extends AbstractPlatformTransactionMa
|
||||
Neo4jTransactionObject transactionObject = extractNeo4jTransaction(status);
|
||||
Neo4jTransactionHolder transactionHolder = transactionObject.getRequiredResourceHolder();
|
||||
Collection<Bookmark> newBookmarks = transactionHolder.commit();
|
||||
this.bookmarkManager.updateBookmarks(transactionHolder.getBookmarks(), newBookmarks);
|
||||
this.bookmarkManager.resolve().updateBookmarks(transactionHolder.getBookmarks(), newBookmarks);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.springframework.data.neo4j.core.DatabaseSelection;
|
||||
import org.springframework.data.neo4j.core.ReactiveDatabaseSelectionProvider;
|
||||
import org.springframework.data.neo4j.core.ReactiveUserSelectionProvider;
|
||||
import org.springframework.data.neo4j.core.UserSelection;
|
||||
import org.springframework.data.neo4j.core.support.BookmarkManagerReference;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.transaction.NoTransactionException;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
@@ -133,7 +134,7 @@ public final class ReactiveNeo4jTransactionManager extends AbstractReactiveTrans
|
||||
*/
|
||||
private final ReactiveUserSelectionProvider userSelectionProvider;
|
||||
|
||||
private final Neo4jBookmarkManager bookmarkManager;
|
||||
private final BookmarkManagerReference bookmarkManager;
|
||||
|
||||
/**
|
||||
* This will create a transaction manager for the default database.
|
||||
@@ -178,14 +179,13 @@ public final class ReactiveNeo4jTransactionManager extends AbstractReactiveTrans
|
||||
this.userSelectionProvider = builder.userSelectionProvider == null ?
|
||||
ReactiveUserSelectionProvider.getDefaultSelectionProvider() :
|
||||
builder.userSelectionProvider;
|
||||
this.bookmarkManager =
|
||||
builder.bookmarkManager == null ? Neo4jBookmarkManager.createReactive() : builder.bookmarkManager;
|
||||
this.bookmarkManager = new BookmarkManagerReference(Neo4jBookmarkManager::createReactive, builder.bookmarkManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
|
||||
this.bookmarkManager.setApplicationEventPublisher(applicationContext);
|
||||
this.bookmarkManager.setApplicationContext(applicationContext);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,7 +292,7 @@ public final class ReactiveNeo4jTransactionManager extends AbstractReactiveTrans
|
||||
userSelectionProvider
|
||||
.getUserSelection()
|
||||
.switchIfEmpty(Mono.just(UserSelection.connectedUser())),
|
||||
(databaseSelection, userSelection) -> new Neo4jTransactionContext(databaseSelection, userSelection, bookmarkManager.getBookmarks()))
|
||||
(databaseSelection, userSelection) -> new Neo4jTransactionContext(databaseSelection, userSelection, bookmarkManager.resolve().getBookmarks()))
|
||||
.map(context -> Tuples.of(context, this.driver.session(ReactiveSession.class, Neo4jTransactionUtils.sessionConfig(readOnly, context.getBookmarks(), context.getDatabaseSelection(), context.getUserSelection()))))
|
||||
.flatMap(contextAndSession -> Mono.fromDirect(contextAndSession.getT2().beginTransaction(transactionConfig)).single()
|
||||
.map(nativeTransaction -> new ReactiveNeo4jTransactionHolder(contextAndSession.getT1(),
|
||||
@@ -325,7 +325,7 @@ public final class ReactiveNeo4jTransactionManager extends AbstractReactiveTrans
|
||||
ReactiveNeo4jTransactionHolder holder = extractNeo4jTransaction(genericReactiveTransaction)
|
||||
.getRequiredResourceHolder();
|
||||
return holder.commit()
|
||||
.doOnNext(bookmark -> bookmarkManager.updateBookmarks(holder.getBookmarks(), bookmark))
|
||||
.doOnNext(bookmark -> bookmarkManager.resolve().updateBookmarks(holder.getBookmarks(), bookmark))
|
||||
.then();
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.data.neo4j.core.DatabaseSelection;
|
||||
import org.springframework.data.neo4j.core.UserSelection;
|
||||
import org.springframework.data.neo4j.core.Neo4jClient;
|
||||
import org.springframework.data.neo4j.core.support.BookmarkManagerReference;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.jta.JtaTransactionManager;
|
||||
@@ -152,7 +153,7 @@ class Neo4jTransactionManagerTest {
|
||||
throws NoSuchFieldException, IllegalAccessException {
|
||||
Field bookmarkManager = Neo4jTransactionManager.class.getDeclaredField("bookmarkManager");
|
||||
bookmarkManager.setAccessible(true);
|
||||
bookmarkManager.set(txManager, value);
|
||||
bookmarkManager.set(txManager, new BookmarkManagerReference(Neo4jBookmarkManager::create, value));
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
||||
@@ -46,6 +46,7 @@ import org.neo4j.driver.reactivestreams.ReactiveSession;
|
||||
import org.neo4j.driver.reactivestreams.ReactiveTransaction;
|
||||
import org.springframework.data.neo4j.core.DatabaseSelection;
|
||||
import org.springframework.data.neo4j.core.UserSelection;
|
||||
import org.springframework.data.neo4j.core.support.BookmarkManagerReference;
|
||||
import org.springframework.data.r2dbc.connectionfactory.R2dbcTransactionManager;
|
||||
import org.springframework.transaction.reactive.TransactionSynchronizationManager;
|
||||
import org.springframework.transaction.reactive.TransactionalOperator;
|
||||
@@ -169,7 +170,7 @@ class ReactiveNeo4jTransactionManagerTest {
|
||||
throws NoSuchFieldException, IllegalAccessException {
|
||||
Field bookmarkManager = ReactiveNeo4jTransactionManager.class.getDeclaredField("bookmarkManager");
|
||||
bookmarkManager.setAccessible(true);
|
||||
bookmarkManager.set(txManager, value);
|
||||
bookmarkManager.set(txManager, new BookmarkManagerReference(Neo4jBookmarkManager::createReactive, value));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,6 @@ class CustomQueriesIT {
|
||||
static void setupData(@Autowired Driver driver, @Autowired BookmarkCapture bookmarkCapture) throws IOException {
|
||||
|
||||
try (Session session = driver.session(bookmarkCapture.createSessionConfig())) {
|
||||
session.run("MATCH (n) DETACH DELETE n").consume();
|
||||
session.run("MATCH (n) DETACH DELETE n").consume();
|
||||
CypherUtils.loadCypherFromResource("/data/movies.cypher", session);
|
||||
bookmarkCapture.seedWith(session.lastBookmarks());
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2011-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.neo4j.integration.bookmarks;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.Session;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.data.neo4j.integration.movies.shared.CypherUtils;
|
||||
|
||||
/**
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
public final class DatabaseInitializer implements InitializingBean {
|
||||
|
||||
private final Driver driver;
|
||||
|
||||
public DatabaseInitializer(Driver driver) {
|
||||
this.driver = driver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
try (Session session = driver.session()) {
|
||||
session.run("MATCH (n) DETACH DELETE n").consume();
|
||||
CypherUtils.loadCypherFromResource("/data/movies.cypher", session);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2011-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.neo4j.integration.bookmarks;
|
||||
|
||||
import org.springframework.data.neo4j.core.schema.GeneratedValue;
|
||||
import org.springframework.data.neo4j.core.schema.Id;
|
||||
import org.springframework.data.neo4j.core.schema.Node;
|
||||
|
||||
/**
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
@Node
|
||||
public class Person {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private String id;
|
||||
|
||||
private String name;
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2011-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.neo4j.integration.bookmarks.imperative;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mockito;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.SessionConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
|
||||
import org.springframework.data.neo4j.integration.bookmarks.DatabaseInitializer;
|
||||
import org.springframework.data.neo4j.integration.bookmarks.Person;
|
||||
import org.springframework.data.neo4j.repository.Neo4jRepository;
|
||||
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
|
||||
import org.springframework.data.neo4j.repository.query.Query;
|
||||
import org.springframework.data.neo4j.test.Neo4jExtension;
|
||||
import org.springframework.data.neo4j.test.Neo4jImperativeTestConfiguration;
|
||||
import org.springframework.data.neo4j.test.Neo4jIntegrationTest;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
/**
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
@Neo4jIntegrationTest
|
||||
public class NoopBookmarkmanagerIT {
|
||||
|
||||
protected static Neo4jExtension.Neo4jConnectionSupport neo4jConnectionSupport;
|
||||
|
||||
@Configuration
|
||||
@EnableNeo4jRepositories(considerNestedRepositories = true)
|
||||
@EnableTransactionManagement
|
||||
@ComponentScan
|
||||
@EnableAsync
|
||||
static class Config extends Neo4jImperativeTestConfiguration {
|
||||
|
||||
@Bean
|
||||
DatabaseInitializer databaseInitializer(Driver driver) {
|
||||
return new DatabaseInitializer(driver);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Driver driver() {
|
||||
var driver = neo4jConnectionSupport.getDriver();
|
||||
return Mockito.spy(driver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Neo4jBookmarkManager bookmarkManager() {
|
||||
return Neo4jBookmarkManager.noop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCypher5Compatible() {
|
||||
return neo4jConnectionSupport.isCypher5SyntaxCompatible();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void mustNotUseBookmarks(@Autowired PersonService personService, @Autowired Driver driver) throws ExecutionException, InterruptedException {
|
||||
|
||||
var movies = personService.getMoviesByActorNameLike("Bill");
|
||||
assertThat(movies).hasSize(5);
|
||||
var sessionConfigCaptor = ArgumentCaptor.forClass(SessionConfig.class);
|
||||
verify(driver, times(5)).session(any(), sessionConfigCaptor.capture());
|
||||
assertThat(sessionConfigCaptor.getAllValues())
|
||||
.allMatch(cfg -> {
|
||||
var bookmarks = new ArrayList<>();
|
||||
if (cfg.bookmarks() != null) {
|
||||
cfg.bookmarks().forEach(bookmarks::add);
|
||||
}
|
||||
return bookmarks.isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
interface PersonRepository extends Neo4jRepository<Person, String> {
|
||||
|
||||
@Async
|
||||
@Query("MATCH (p:Person) WHERE p.name =~ (('.*' + $name) + '.*') RETURN p.name")
|
||||
CompletableFuture<List<String>> findMatchingNames(String name);
|
||||
|
||||
@Query("MATCH (m:Movie)<-[:ACTED_IN]-(p:Person) WHERE p.name= $name return m.title")
|
||||
CompletableFuture<List<String>> getPersonMovies(String name);
|
||||
}
|
||||
|
||||
@Service
|
||||
static class PersonService {
|
||||
private final PersonRepository personRepository;
|
||||
|
||||
PersonService(PersonRepository personRepository) {
|
||||
this.personRepository = personRepository;
|
||||
}
|
||||
|
||||
public List<String> getMoviesByActorNameLike(String namePattern) throws ExecutionException, InterruptedException {
|
||||
|
||||
CompletableFuture<List<String>> completableFutureCompletableFuture = personRepository.findMatchingNames(namePattern)
|
||||
.thenCompose(names -> {
|
||||
List<String> result = Collections.synchronizedList(new ArrayList<String>());
|
||||
var futures = names.stream().map(personRepository::getPersonMovies)
|
||||
.map(cf -> cf.thenAccept(result::addAll))
|
||||
.toArray(CompletableFuture[]::new);
|
||||
return CompletableFuture.allOf(futures)
|
||||
.thenApply(__ -> result);
|
||||
});
|
||||
return completableFutureCompletableFuture.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright 2011-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.neo4j.integration.bookmarks.reactive;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mockito;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.SessionConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
|
||||
import org.springframework.data.neo4j.integration.bookmarks.DatabaseInitializer;
|
||||
import org.springframework.data.neo4j.integration.bookmarks.Person;
|
||||
import org.springframework.data.neo4j.repository.ReactiveNeo4jRepository;
|
||||
import org.springframework.data.neo4j.repository.config.EnableReactiveNeo4jRepositories;
|
||||
import org.springframework.data.neo4j.repository.query.Query;
|
||||
import org.springframework.data.neo4j.test.Neo4jExtension;
|
||||
import org.springframework.data.neo4j.test.Neo4jIntegrationTest;
|
||||
import org.springframework.data.neo4j.test.Neo4jReactiveTestConfiguration;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
/**
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
@Neo4jIntegrationTest
|
||||
@Tag(Neo4jExtension.NEEDS_REACTIVE_SUPPORT)
|
||||
public class ReactiveNoopBookmarkmanagerIT {
|
||||
|
||||
protected static Neo4jExtension.Neo4jConnectionSupport neo4jConnectionSupport;
|
||||
|
||||
@Configuration
|
||||
@EnableReactiveNeo4jRepositories(considerNestedRepositories = true)
|
||||
@EnableTransactionManagement
|
||||
@ComponentScan
|
||||
static class Config extends Neo4jReactiveTestConfiguration {
|
||||
|
||||
@Bean
|
||||
DatabaseInitializer databaseInitializer(Driver driver) {
|
||||
return new DatabaseInitializer(driver);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Driver driver() {
|
||||
var driver = neo4jConnectionSupport.getDriver();
|
||||
return Mockito.spy(driver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Neo4jBookmarkManager bookmarkManager() {
|
||||
return Neo4jBookmarkManager.noop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCypher5Compatible() {
|
||||
return neo4jConnectionSupport.isCypher5SyntaxCompatible();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void mustNotUseBookmarks(@Autowired PersonService personService, @Autowired Driver driver) {
|
||||
|
||||
AtomicReference<List<String>> result = new AtomicReference<>();
|
||||
personService.getMoviesByActorNameLike("Bill")
|
||||
.as(StepVerifier::create)
|
||||
.consumeNextWith(result::set)
|
||||
.verifyComplete();
|
||||
|
||||
assertThat(result)
|
||||
.hasValueSatisfying(movies -> assertThat(movies).hasSize(5));
|
||||
|
||||
var sessionConfigCaptor = ArgumentCaptor.forClass(SessionConfig.class);
|
||||
verify(driver, times(5)).session(any(), sessionConfigCaptor.capture());
|
||||
assertThat(sessionConfigCaptor.getAllValues())
|
||||
.allMatch(cfg -> {
|
||||
var bookmarks = new ArrayList<>();
|
||||
if (cfg.bookmarks() != null) {
|
||||
cfg.bookmarks().forEach(bookmarks::add);
|
||||
}
|
||||
return bookmarks.isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
interface PersonRepository extends ReactiveNeo4jRepository<Person, String> {
|
||||
|
||||
@Query("MATCH (p:Person) WHERE p.name =~ (('.*' + $name) + '.*') RETURN p.name")
|
||||
Flux<String> findMatchingNames(String name);
|
||||
|
||||
@Query("MATCH (m:Movie)<-[:ACTED_IN]-(p:Person) WHERE p.name= $name return m.title")
|
||||
Flux<String> getPersonMovies(String name);
|
||||
}
|
||||
|
||||
@Service
|
||||
static class PersonService {
|
||||
private final PersonRepository personRepository;
|
||||
|
||||
PersonService(PersonRepository personRepository) {
|
||||
this.personRepository = personRepository;
|
||||
}
|
||||
|
||||
public Mono<List<String>> getMoviesByActorNameLike(String namePattern) {
|
||||
return personRepository.findMatchingNames(namePattern)
|
||||
.flatMap(personRepository::getPersonMovies, 2)
|
||||
.collectList();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user