diff --git a/pom.xml b/pom.xml index 410fed75e..2644d3a61 100644 --- a/pom.xml +++ b/pom.xml @@ -744,25 +744,13 @@ ${asciidoctorj-diagram.version} - - html - book - img - ${project.basedir}/src/main/asciidoc - index.adoc - coderay + - font - left - - - ${spring-data-commons-docs.dir} asciidoctor-diagram - ${project.build.docs} diff --git a/src/main/asciidoc/appendix/conversions.adoc b/src/main/asciidoc/appendix/conversions.adoc index dca89604d..7f36a7f66 100644 --- a/src/main/asciidoc/appendix/conversions.adoc +++ b/src/main/asciidoc/appendix/conversions.adoc @@ -1,6 +1,9 @@ [[conversions]] = Conversions +[[build-in.conversions]] +== Built-in conversions + We support a broad range of conversions out of the box. Find the list of supported cypher types in the official drivers manual: https://neo4j.com/docs/driver-manual/current/cypher-values/[Working with Cypher values]. diff --git a/src/main/asciidoc/appendix/index.adoc b/src/main/asciidoc/appendix/index.adoc index 6b038e0d9..49637ddd3 100644 --- a/src/main/asciidoc/appendix/index.adoc +++ b/src/main/asciidoc/appendix/index.adoc @@ -1,9 +1,7 @@ -:leveloffset: +1 - [[sdn-appendix]] -= Spring Data Neo4j Appendix +[appendix] += Spring Data Neo4j -:numbered!: :leveloffset: +1 include::conversions.adoc[] @@ -21,4 +19,3 @@ include::migrating.adoc[] include::build.adoc[] :leveloffset: -1 -:leveloffset: -1 diff --git a/src/main/asciidoc/appendix/migrating.adoc b/src/main/asciidoc/appendix/migrating.adoc index 63179cd92..51686180a 100644 --- a/src/main/asciidoc/appendix/migrating.adoc +++ b/src/main/asciidoc/appendix/migrating.adoc @@ -125,7 +125,7 @@ You're then ready to replace annotations: |No replacement, not needed |`org.springframework.data.neo4j.annotation.QueryResult` -|Use <>; arbitrary result mapping not supported anymore +|Use <>; arbitrary result mapping not supported anymore |=== diff --git a/src/main/asciidoc/faq/faq.adoc b/src/main/asciidoc/faq/faq.adoc deleted file mode 100644 index 61e13c856..000000000 --- a/src/main/asciidoc/faq/faq.adoc +++ /dev/null @@ -1,1420 +0,0 @@ -[[faq]] -= Frequently Asked Questions - -Here are a couple of more frequently asked question in addition to the ones in the <>. - -[[faq.change-driver-version]] -== Which Neo4j Java Driver can be used and how? - -SDN6 relies on the Neo4j Java Driver version 4.x. -Each SDN6 release uses a Neo4j Java Driver version compatible with the latest Neo4j available at the time of its -release. -While patch versions of the Neo4j Java Driver are usually drop-in replacements, SDN6 makes sure that even minor versions -are interchangeable as it checks for the presence or absence of methods or interface changes if necessary. - -Therefore, you are able to use any 4.x Neo4j Java Driver with any SDN 6.x version. - -=== With Spring Boot - -These days, a Spring boot deployment is the most likely deployment of a Spring Data based applications. Please use -Spring Boots dependency management to change the driver version like this: - -.Change the driver version from Maven (pom.xml) -[source,xml] ----- - - 4.4.3 - ----- - -Or - -.Change the driver version from Gradle (gradle.properties) -[source,properties] ----- -neo4j-java-driver.version = 4.4.3 ----- - -=== Without Spring Boot - -Without Spring Boot, you would just manually declare the dependency. For Maven, we recommend using the `` -section like this: - -.Change the driver version without Spring Boot from Maven (pom.xml) ----- - - - org.neo4j.driver - neo4j-java-driver - 4.4.3 - - ----- - -[[faq.multidatabase]] -== Neo4j 4.0 supports multiple databases - How can I use them? - -You can either statically configure the database name or run your own database name provider. -Bear in mind that SDN will not create the databases for you. -You can do this with the help of a https://github.com/michael-simons/neo4j-migrations[migrations tool] -or of course with a simple script upfront. - -[[faq.multidatabase.statically]] -=== Statically configured - -Configure the database name to use in your Spring Boot configuration like this (the same property applies of course for YML or environment based configuration, with Spring Boot's conventions applied): - -[source,properties] ----- -spring.data.neo4j.database = yourDatabase ----- - -With that configuration in place, all queries generated by all instances of SDN repositories (both reactive and imperative) and by the `ReactiveNeo4jTemplate` respectively `Neo4jTemplate` will be executed against the database `yourDatabase`. - -[[faq.multidatabase.dynamically]] -=== Dynamically configured - -Provide a bean with the type `Neo4jDatabaseNameProvider` or `ReactiveDatabaseSelectionProvider` depending on the type of your Spring application. - -That bean could use for example Spring's security context to retrieve a tenant. -Here is a working example for an imperative application secured with Spring Security: - -[source,java] -[[faq.databaseSelectionProvider]] -.Neo4jConfig.java ----- -import org.neo4j.springframework.data.core.DatabaseSelection; -import org.neo4j.springframework.data.core.DatabaseSelectionProvider; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.User; - -include::../../../../src/test/java/org/springframework/data/neo4j/documentation/Neo4jConfig.java[tags=faq.multidatabase] ----- - -NOTE: Be careful that you don't mix up entities retrieved from one database with another database. -The database name is requested for each new transaction, so you might end up with less or more entities than expected when changing the database name in between calls. -Or worse, you could inevitably store the wrong entities in the wrong database. - -[[faq.multidatabase.health]] -=== The Spring Boot Neo4j health indicator targets the default database, how can I change that? - -Spring Boot comes with both imperative and reactive Neo4j https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-health[health indicators.] -Both variants are able to detect multiple beans of `org.neo4j.driver.Driver` inside the application context and provide -a contribution to the overall health for each instance. -The Neo4j driver however does connect to a server and not to a specific database inside that server. -Spring Boot is able to configure the driver without Spring Data Neo4j and as the information which database is to be used -is tied to Spring Data Neo4j, this information is not available to the built-in health indicator. - -This is most likely not a problem in many deployment scenarios. -However, if configured database user does not have at least access rights to the default database, the health checks will fail. - -This can be mitigated by custom Neo4j health contributors that are aware of the database selection. - -==== Imperative variant - -[[faq.multidatabase.health.imperative]] -[source,java,indent=0,tabsize=4] ----- -import java.util.Optional; - -import org.neo4j.driver.Driver; -import org.neo4j.driver.Result; -import org.neo4j.driver.SessionConfig; -import org.neo4j.driver.summary.DatabaseInfo; -import org.neo4j.driver.summary.ResultSummary; -import org.neo4j.driver.summary.ServerInfo; -import org.springframework.boot.actuate.health.AbstractHealthIndicator; -import org.springframework.boot.actuate.health.Health; -import org.springframework.data.neo4j.core.DatabaseSelection; -import org.springframework.data.neo4j.core.DatabaseSelectionProvider; -import org.springframework.util.StringUtils; - -public class DatabaseSelectionAwareNeo4jHealthIndicator extends AbstractHealthIndicator { - - private final Driver driver; - - private final DatabaseSelectionProvider databaseSelectionProvider; - - public DatabaseSelectionAwareNeo4jHealthIndicator( - Driver driver, DatabaseSelectionProvider databaseSelectionProvider - ) { - this.driver = driver; - this.databaseSelectionProvider = databaseSelectionProvider; - } - - @Override - protected void doHealthCheck(Health.Builder builder) { - try { - SessionConfig sessionConfig = Optional - .ofNullable(databaseSelectionProvider.getDatabaseSelection()) - .filter(databaseSelection -> databaseSelection != DatabaseSelection.undecided()) - .map(DatabaseSelection::getValue) - .map(v -> SessionConfig.builder().withDatabase(v).build()) - .orElseGet(SessionConfig::defaultConfig); - - class Tuple { - String edition; - ResultSummary resultSummary; - - Tuple(String edition, ResultSummary resultSummary) { - this.edition = edition; - this.resultSummary = resultSummary; - } - } - - String query = - "CALL dbms.components() YIELD name, edition WHERE name = 'Neo4j Kernel' RETURN edition"; - Tuple health = driver.session(sessionConfig) - .writeTransaction(tx -> { - Result result = tx.run(query); - String edition = result.single().get("edition").asString(); - return new Tuple(edition, result.consume()); - }); - - addHealthDetails(builder, health.edition, health.resultSummary); - } catch (Exception ex) { - builder.down().withException(ex); - } - } - - static void addHealthDetails(Health.Builder builder, String edition, ResultSummary resultSummary) { - ServerInfo serverInfo = resultSummary.server(); - builder.up() - .withDetail( - "server", serverInfo.version() + "@" + serverInfo.address()) - .withDetail("edition", edition); - DatabaseInfo databaseInfo = resultSummary.database(); - if (StringUtils.hasText(databaseInfo.name())) { - builder.withDetail("database", databaseInfo.name()); - } - } -} ----- - -This uses the available database selection to run the same query that Boot runs to check whether a connection is healthy or not. -Use the following configuration to apply it: - -[[faq.multidatabase.health.imperative.config]] -[source,java,indent=0,tabsize=4] ----- -import java.util.Map; - -import org.neo4j.driver.Driver; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.boot.actuate.health.CompositeHealthContributor; -import org.springframework.boot.actuate.health.HealthContributor; -import org.springframework.boot.actuate.health.HealthContributorRegistry; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.neo4j.core.DatabaseSelectionProvider; - -@Configuration(proxyBeanMethods = false) -public class Neo4jHealthConfig { - - @Bean // <.> - DatabaseSelectionAwareNeo4jHealthIndicator databaseSelectionAwareNeo4jHealthIndicator( - Driver driver, DatabaseSelectionProvider databaseSelectionProvider - ) { - return new DatabaseSelectionAwareNeo4jHealthIndicator(driver, databaseSelectionProvider); - } - - @Bean // <.> - HealthContributor neo4jHealthIndicator( - Map customNeo4jHealthIndicators) { - return CompositeHealthContributor.fromMap(customNeo4jHealthIndicators); - } - - @Bean // <.> - InitializingBean healthContributorRegistryCleaner( - HealthContributorRegistry healthContributorRegistry, - Map customNeo4jHealthIndicators - ) { - return () -> customNeo4jHealthIndicators.keySet() - .stream() - .map(HealthContributorNameFactory.INSTANCE) - .forEach(healthContributorRegistry::unregisterContributor); - } -} ----- -<.> If you have multiple drivers and database selection providers, you would need to create one indicator per combination -<.> This makes sure that all of those indicators are grouped under Neo4j, replacing the default Neo4j health indicator -<.> This prevents the individual contributors showing up in the health endpoint directly - -==== Reactive variant - -The reactive variant is basically the same, using reactive types and the corresponding reactive infrastructure classes: - -[[faq.multidatabase.health.reactive]] -[source,java,indent=0,tabsize=4] ----- -import reactor.core.publisher.Mono; -import reactor.util.function.Tuple2; - -import org.neo4j.driver.Driver; -import org.neo4j.driver.SessionConfig; -import org.neo4j.driver.reactive.RxResult; -import org.neo4j.driver.reactive.RxSession; -import org.neo4j.driver.summary.DatabaseInfo; -import org.neo4j.driver.summary.ResultSummary; -import org.neo4j.driver.summary.ServerInfo; -import org.reactivestreams.Publisher; -import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator; -import org.springframework.boot.actuate.health.Health; -import org.springframework.data.neo4j.core.DatabaseSelection; -import org.springframework.data.neo4j.core.ReactiveDatabaseSelectionProvider; -import org.springframework.util.StringUtils; - -public final class DatabaseSelectionAwareNeo4jReactiveHealthIndicator - extends AbstractReactiveHealthIndicator { - - private final Driver driver; - - private final ReactiveDatabaseSelectionProvider databaseSelectionProvider; - - public DatabaseSelectionAwareNeo4jReactiveHealthIndicator( - Driver driver, - ReactiveDatabaseSelectionProvider databaseSelectionProvider - ) { - this.driver = driver; - this.databaseSelectionProvider = databaseSelectionProvider; - } - - @Override - protected Mono doHealthCheck(Health.Builder builder) { - String query = - "CALL dbms.components() YIELD name, edition WHERE name = 'Neo4j Kernel' RETURN edition"; - return databaseSelectionProvider.getDatabaseSelection() - .map(databaseSelection -> databaseSelection == DatabaseSelection.undecided() ? - SessionConfig.defaultConfig() : - SessionConfig.builder().withDatabase(databaseSelection.getValue()).build() - ) - .flatMap(sessionConfig -> - Mono.usingWhen( - Mono.fromSupplier(() -> driver.rxSession(sessionConfig)), - s -> { - Publisher> f = s.readTransaction(tx -> { - RxResult result = tx.run(query); - return Mono.from(result.records()) - .map((record) -> record.get("edition").asString()) - .zipWhen((edition) -> Mono.from(result.consume())); - }); - return Mono.fromDirect(f); - }, - RxSession::close - ) - ).map((result) -> { - addHealthDetails(builder, result.getT1(), result.getT2()); - return builder.build(); - }); - } - - static void addHealthDetails(Health.Builder builder, String edition, ResultSummary resultSummary) { - ServerInfo serverInfo = resultSummary.server(); - builder.up() - .withDetail( - "server", serverInfo.version() + "@" + serverInfo.address()) - .withDetail("edition", edition); - DatabaseInfo databaseInfo = resultSummary.database(); - if (StringUtils.hasText(databaseInfo.name())) { - builder.withDetail("database", databaseInfo.name()); - } - } -} - ----- - -And of course, the reactive variant of the configuration. It needs two different registry cleaners, as Spring Boot will -wrap existing reactive indicators to be used with the non-reactive actuator endpoint, too. - -[[faq.multidatabase.health.reactive.config]] -[source,java,indent=0,tabsize=4] ----- -import java.util.Map; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.boot.actuate.health.CompositeReactiveHealthContributor; -import org.springframework.boot.actuate.health.HealthContributorNameFactory; -import org.springframework.boot.actuate.health.HealthContributorRegistry; -import org.springframework.boot.actuate.health.ReactiveHealthContributor; -import org.springframework.boot.actuate.health.ReactiveHealthContributorRegistry; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration(proxyBeanMethods = false) -public class Neo4jHealthConfig { - - @Bean - ReactiveHealthContributor neo4jHealthIndicator( - Map customNeo4jHealthIndicators) { - return CompositeReactiveHealthContributor.fromMap(customNeo4jHealthIndicators); - } - - @Bean - InitializingBean healthContributorRegistryCleaner(HealthContributorRegistry healthContributorRegistry, - Map customNeo4jHealthIndicators) { - return () -> customNeo4jHealthIndicators.keySet() - .stream() - .map(HealthContributorNameFactory.INSTANCE) - .forEach(healthContributorRegistry::unregisterContributor); - } - - @Bean - InitializingBean reactiveHealthContributorRegistryCleaner( - ReactiveHealthContributorRegistry healthContributorRegistry, - Map customNeo4jHealthIndicators) { - return () -> customNeo4jHealthIndicators.keySet() - .stream() - .map(HealthContributorNameFactory.INSTANCE) - .forEach(healthContributorRegistry::unregisterContributor); - } -} ----- - -[[faq.impersonation]] -== Neo4j 4.4 supports impersonation of different users - How can I use them? - -User impersonation is especially interesting in big multi-tenant settings, in which one physically connected (or technical) -user can impersonate many tenants. Depending on your setup this will drastically reduce the number of physical driver instances needed. - -The feature requires Neo4j Enterprise 4.4+ on the server side and a 4.4+ driver on the client side (`org.neo4j.driver:neo4j-java-driver:4.4.0` or higher). - -For both imperative and reactive versions you need to provide a `UserSelectionProvider` respectively a `ReactiveUserSelectionProvider`. -The same instance needs to be passed along to the `Neo4Client` and `Neo4jTransactionManager` respectively their reactive variants. - -In <> and <> configurations you just need to provide a bean of the -type in question: - -[[faq.impersonation.userselectionbean]] -.User selection provider bean -[source,java] ----- -import org.springframework.data.neo4j.core.UserSelection; -import org.springframework.data.neo4j.core.UserSelectionProvider; - -public class CustomConfig { - - @Bean - public UserSelectionProvider getUserSelectionProvider() { - return () -> UserSelection.impersonate("someUser"); - } -} ----- - -In a typical Spring Boot scenario this feature requires a bit more work, as Boot supports also SDN versions without that feature. -So given the bean in <>, you would need fully customize the client and transaction manager: - -[[faq.impersonation.boot]] -.Necessary customization for Spring Boot -[source,java] ----- -import org.neo4j.driver.Driver; - -import org.springframework.data.neo4j.core.DatabaseSelectionProvider; -import org.springframework.data.neo4j.core.Neo4jClient; -import org.springframework.data.neo4j.core.UserSelectionProvider; -import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager; - -import org.springframework.transaction.PlatformTransactionManager; - -public class CustomConfig { - - @Bean - public Neo4jClient neo4jClient( - Driver driver, - DatabaseSelectionProvider databaseSelectionProvider, - UserSelectionProvider userSelectionProvider - ) { - - return Neo4jClient.with(driver) - .withDatabaseSelectionProvider(databaseSelectionProvider) - .withUserSelectionProvider(userSelectionProvider) - .build(); - } - - @Bean - public PlatformTransactionManager transactionManager( - Driver driver, - DatabaseSelectionProvider databaseSelectionProvider, - UserSelectionProvider userSelectionProvider - ) { - - return Neo4jTransactionManager - .with(driver) - .withDatabaseSelectionProvider(databaseSelectionProvider) - .withUserSelectionProvider(userSelectionProvider) - .build(); - } -} ----- - -[[faq.cluster]] -== Using a Neo4j cluster instance from Spring Data Neo4j - -The following questions apply to Neo4j AuraDB as well as to Neo4j on-premise cluster instances. - -[[faq.transactions.cluster]] -=== Do I need specific configuration so that transactions work seamless with a Neo4j Causal Cluster? - -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.transactions.cluster.rw]] -=== Is it important to use read-only transactions for Neo4j cluster? - -Yes, it is. -The Neo4j cluster architecture is a causal clustering architecture, and it distinguishes between primary and secondary servers. -Primary server either are single instances or core instances. Both of them can answer to read and write operations. -Write operations are propagated from the core instances to read replicas inside the cluster. -Those read replicas are secondary servers. -Secondary servers don't answer to write operations. - -In a standard deployment scenario you'll have some core instances and many read replicas inside a cluster. -Therefor it is important to mark operations or queries as read-only to scale your cluster in such a way that leaders are -never overwhelmed and queries are propagated as much as possible to read replicas. - -Neither Spring Data Neo4j nor the underlying Java driver do Cypher parsing and both building blocks assume -write operations by default. This decision has been made to support all operations out of the box. If something in the -stack would assume read-only by default, the stack might end up sending write queries to read replicas and fail -on executing them. - -NOTE: All `findById`, `findAllById`, `findAll` and predefined existential methods are marked as read-only by default. - -Some options are described below: - -.Making a whole repository read-only -[source,java] ----- -import org.springframework.data.neo4j.repository.Neo4jRepository; -import org.springframework.transaction.annotation.Transactional; - -@Transactional(readOnly = true) -interface PersonRepository extends Neo4jRepository { -} ----- - -.Making selected repository methods read-only -[source,java] ----- -import org.springframework.data.neo4j.repository.Neo4jRepository; -import org.springframework.data.neo4j.repository.query.Query; -import org.springframework.transaction.annotation.Transactional; - -interface PersonRepository extends Neo4jRepository { - - @Transactional(readOnly = true) - Person findOneByName(String name); // <.> - - @Transactional(readOnly = true) - @Query(""" - CALL apoc.search.nodeAll('{Person: "name",Movie: ["title","tagline"]}','contains','her') - YIELD node AS n RETURN n""") - Person findByCustomQuery(); // <.> -} ----- -<.> Why isn't this read-only be default? While it would work for the derived finder above (which we actually know to be read-only), - we often have seen cases in which user add a custom `@Query` and implement it via a `MERGE` construct, - which of course is a write operation. -<.> Custom procedures can do all kinds of things, there's no way at the moment to check for read-only vs write here for us. - -.Orchestrate calls to a repository from a service -[source,java] ----- -import java.util.Optional; - -import org.springframework.data.neo4j.repository.Neo4jRepository; -import org.springframework.transaction.annotation.Transactional; - -interface PersonRepository extends Neo4jRepository { -} - -interface MovieRepository extends Neo4jRepository { - List findByLikedByPersonName(String name); -} - -public class PersonService { - - private final PersonRepository personRepository; - private final MovieRepository movieRepository; - - public PersonService(PersonRepository personRepository, - MovieRepository movieRepository) { - this.personRepository = personRepository; - this.movieRepository = movieRepository; - } - - @Transactional(readOnly = true) - public Optional getPerson(Long id) { // <.> - return this.repository.findById(id) - .map(person -> { - var movies = this.movieRepository - .findByLikedByPersonName(person.getName()); - return new PersonDetails(person, movies); - }); - } -} ----- -<.> Here, several calls to multiple repositories are wrapped in one single, read-only transaction. - - -.Using Springs `TransactionTemplate` inside private service methods and / or with the Neo4j client -[source,java] ----- -import java.util.Collection; - -import org.neo4j.driver.types.Node; -import org.springframework.data.neo4j.core.Neo4jClient; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.support.TransactionTemplate; - -public class PersonService { - - private final TransactionTemplate readOnlyTx; - - private final Neo4jClient neo4jClient; - - public PersonService(PlatformTransactionManager transactionManager, Neo4jClient neo4jClient) { - - this.readOnlyTx = new TransactionTemplate(transactionManager, // <.> - new TransactionDefinition() { - @Override public boolean isReadOnly() { - return true; - } - } - ); - this.neo4jClient = neo4jClient; - } - - void internalOperation() { // <.> - - Collection nodes = this.readOnlyTx.execute(state -> { - return neo4jClient.query("MATCH (n) RETURN n").fetchAs(Node.class) // <.> - .mappedBy((types, record) -> record.get(0).asNode()) - .all(); - }); - } -} ----- -<.> Create an instance of the `TransactionTemplate` with the characteristics you need. - Of course, this can be a global bean, too. -<.> Reason number one for using the transaction template: Declarative transactions don't work - in package private or private methods and also not in inner method calls (imagine another method - in this service calling `internalOperation`) due to their nature being implemented with Aspects - and proxies. -<.> The `Neo4jClient` is a fixed utility provided by SDN. It cannot be annotated, but it integrates with Spring. - So it gives you everything you would do with the pure driver and without automatic mapping and with - transactions. It also obeys declarative transactions. - -[[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. -It may however be useful to retrieve the latest bookmark the SDN transaction system received from a database. -You can add a `@Bean` like `BookmarkCapture` to do this: - -[source,java,indent=0,tabsize=4] -.BookmarkCapture.java ----- -import java.util.Set; - -import org.neo4j.driver.Bookmark; -import org.springframework.context.ApplicationListener; - -public final class BookmarkCapture - implements ApplicationListener { - - @Override - public void onApplicationEvent(Neo4jBookmarksUpdatedEvent event) { - // We make sure that this event is called only once, - // the thread safe application of those bookmarks is up to your system. - Set latestBookmarks = event.getBookmarks(); - } -} ----- - -For seeding the transaction system, a customized transaction manager like the following is required: - -[source,java,indent=0,tabsize=4] -.BookmarkSeedingConfig.java ----- -import java.util.Set; -import java.util.function.Supplier; - -import org.neo4j.driver.Bookmark; -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 BookmarkSeedingConfig { - - @Bean - public PlatformTransactionManager transactionManager( - Driver driver, DatabaseSelectionProvider databaseNameProvider) { // <.> - - Supplier> bookmarkSupplier = () -> { // <.> - Bookmark a = null; - Bookmark b = null; - return Set.of(a, b); - }; - - Neo4jBookmarkManager bookmarkManager = - Neo4jBookmarkManager.create(bookmarkSupplier); // <.> - return new Neo4jTransactionManager( - driver, databaseNameProvider, bookmarkManager); // <.> - } -} ----- -<.> Let Spring inject those -<.> This supplier can be anything that holds the latest bookmarks you want to bring into the system -<.> Create the bookmark manager with it -<.> Pass it on to the customized transaction manager - -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? - -No. -You are free to use the following, equivalent Spring Data annotations: - -[cols="4*",options="header"] -|=== - -|SDN specific annotation -|Spring Data common annotation -|Purpose -|Difference - -|`org.springframework.data.neo4j.core.schema.Id` -|`org.springframework.data.annotation.Id` -|Marks the annotated attribute as the unique id. -|Specific annotation has no additional features. - -|`org.springframework.data.neo4j.core.schema.Node` -|`org.springframework.data.annotation.Persistent` -|Marks the class as persistent entity. -|`@Node` allows customizing the labels - -|=== - -[[faq.ids.assignment]] -== How do I use assigned ids? - -Just use `@Id` without `@GeneratedValue` and fill your id attribute via a constructor parameter or a setter or _wither_. -See this https://medium.com/neo4j/neo4j-ogm-and-spring-data-neo4j-a55a866df68c[blog post] for some general remarks about finding good ids. - -[[faq.ids.externally]] -== How do I use externally generated ids? - -We provide the interface `org.springframework.data.neo4j.core.schema.IdGenerator`. -Implement it in any way you want and configure your implementation like this: - -[source,java] -.ThingWithGeneratedId.java ----- -@Node -public class ThingWithGeneratedId { - - @Id @GeneratedValue(TestSequenceGenerator.class) - private String theId; -} ----- - -If you pass in the name of a class to `@GeneratedValue`, this class must have a no-args default constructor. -You can however use a string as well: - -[source,java] -.ThingWithIdGeneratedByBean.java ----- -@Node -public class ThingWithIdGeneratedByBean { - - @Id @GeneratedValue(generatorRef = "idGeneratingBean") - private String theId; -} ----- - -With that, `idGeneratingBean` refers to a bean in the Spring context. -This might be useful for sequence generating. - -NOTE: Setters are not required on non-final fields for the id. - -[[template-support]] -== Do I have to create repositories for each domain class? - -No. -Have a look at the <> and find the `Neo4jTemplate` or the `ReactiveNeo4jTemplate`. - -Those templates know your domain and provide all necessary basic CRUD methods for retrieving, writing and counting entities. - -This is our canonical movie example with the imperative template: - -[source,java] -[[imperative-template-example]] -.TemplateExampleTest.java ----- -include::../../../../src/test/java/org/springframework/data/neo4j/documentation/spring_boot/TemplateExampleTest.java[tags=faq.template-imperative-pt1] -@DataNeo4jTest -include::../../../../src/test/java/org/springframework/data/neo4j/documentation/spring_boot/TemplateExampleTest.java[tags=faq.template-imperative-pt2] ----- - -And here is the reactive version, omitting the setup for brevity: - -[source,java] -[[reactive-template-example]] -.ReactiveTemplateExampleTest.java ----- -include::../../../../src/test/java/org/springframework/data/neo4j/documentation/spring_boot/ReactiveTemplateExampleTest.java[tags=faq.template-reactive-pt1] -@DataNeo4jTest -include::../../../../src/test/java/org/springframework/data/neo4j/documentation/spring_boot/ReactiveTemplateExampleTest.java[tags=faq.template-reactive-pt2] ----- - -Please note that both examples use `@DataNeo4jTest` from Spring Boot. - -[[faq.custom-queries-with-page-and-slice]] -== How do I use custom queries with repository methods returning `Page` or `Slice`? - -While you don't have to provide anything else apart a `Pageable` as a parameter on derived finder methods -that return a `Page` or a `Slice`, you must prepare your custom query to handle the pageable. -<> gives you an overview about what's needed. - -[[custom-queries-with-page-and-slice-examples]] -[source,java] -.Pages and Slices ----- -import org.springframework.data.domain.Pageable; -import org.springframework.data.neo4j.repository.Neo4jRepository; -import org.springframework.data.neo4j.repository.query.Query; - -public interface MyPersonRepository extends Neo4jRepository { - - Page findByName(String name, Pageable pageable); // <.> - - @Query("" - + "MATCH (n:Person) WHERE n.name = $name RETURN n " - + "ORDER BY n.name ASC SKIP $skip LIMIT $limit" - ) - Slice findSliceByName(String name, Pageable pageable); // <.> - - @Query( - value = "" - + "MATCH (n:Person) WHERE n.name = $name RETURN n " - + "ORDER BY n.name ASC SKIP $skip LIMIT $limit", - countQuery = "" - + "MATCH (n:Person) WHERE n.name = $name RETURN count(n)" - ) - Page findPageByName(String name, Pageable pageable); // <.> -} ----- -<.> A derived finder method that creates a query for you. - It handles the `Pageable` for you. - You should use a sorted pageable. -<.> This method uses `@Query` to define a custom query. It returns a `Slice`. - A slice does not know about the total number of pages, so the custom query - doesn't need a dedicated count query. SDN will notify you that it estimates the next slice. - The Cypher template must spot both `$skip` and `$limit` Cypher parameter. - If you omit them, SDN will issue a warning. The will probably not match your expectations. - Also, the `Pageable` should be unsorted and you should provide a stable order. - We won't use the sorting information from the pageable. -<.> This method returns a page. A page knows about the exact number of total pages. - Therefore, you must specify an additional count query. - All other restrictions from the second method apply. - -[[faq.path-mapping]] -== Can I map named paths? - -A series of connected nodes and relationships is called a "path" in Neo4j. -Cypher allows paths to be named using an identifier, as exemplified by: - -[source,cypher] ----- -p = (a)-[*3..5]->(b) ----- - -or as in the infamous Movie graph, that includes the following path (in that case, one of the shortest path between two actors): - -[[bacon-distance]] -[source,cypher] -.The "Bacon" distance ----- -MATCH p=shortestPath((bacon:Person {name:"Kevin Bacon"})-[*]-(meg:Person {name:"Meg Ryan"})) -RETURN p ----- - -Which looks like this: - -image::bacon-distance.png[] - -We find 3 nodes labeled `Person` and 2 nodes labeled `Movie`. Both can be mapped with a custom query. -Assume there's a node entity for both `Person` and `Movie` as well as `Actor` taking care of the relationship: - - -[source,java] -."Standard" movie graph domain model ----- -@Node -public final class Person { - - @Id @GeneratedValue - private final Long id; - - private final String name; - - private Integer born; - - @Relationship("REVIEWED") - private List reviewed = new ArrayList<>(); -} - -@RelationshipProperties -public final class Actor { - - @RelationshipId - private final Long id; - - @TargetNode - private final Person person; - - private final List roles; -} - -@Node -public final class Movie { - - @Id - private final String title; - - @Property("tagline") - private final String description; - - @Relationship(value = "ACTED_IN", direction = Direction.INCOMING) - private final List actors; -} ----- - -When using a query as shown in <> for a domain class of type `Person` like this - -[source,java] ----- -interface PeopleRepository extends Neo4jRepository { - @Query("" - + "MATCH p=shortestPath((bacon:Person {name: $person1})-[*]-(meg:Person {name: $person2}))\n" - + "RETURN p" - ) - List findAllOnShortestPathBetween(@Param("person1") String person1, @Param("person2") String person2); -} ----- - -it will retrieve all people from the path and map them. -If there are relationship types on the path like `REVIEWED` that are also present on the domain, these -will be filled accordingly from the path. - -WARNING: Take special care when you use nodes hydrated from a path based query to save data. -If not all relationships are hydrated, data will be lost. - -The other way round works as well. The same query can be used with the `Movie` entity. -It then will only populate movies. -The following listing shows how todo this as well as how the query can be enriched with additional data -not found on the path. That data is used to correctly populate the missing relationships (in that case, all the actors) - -[source,java] ----- -interface MovieRepository extends Neo4jRepository { - - @Query("" - + "MATCH p=shortestPath(\n" - + "(bacon:Person {name: $person1})-[*]-(meg:Person {name: $person2}))\n" - + "WITH p, [n IN nodes(p) WHERE n:Movie] AS x\n" - + "UNWIND x AS m\n" - + "MATCH (m) <-[r:DIRECTED]-(d:Person)\n" - + "RETURN p, collect(r), collect(d)" - ) - List findAllOnShortestPathBetween(@Param("person1") String person1, @Param("person2") String person2); -} ----- - -The query returns the path plus all relationships and related nodes collected so that the movie entities are fully hydrated. - -The path mapping works for single paths as well for multiple records of paths (which are returned by the `allShortestPath` function.) - -TIP: Named paths can be used efficiently to populate and return more than just a root node, see <>. - - -[[faq.custom-queries-and-custom-mappings]] -== Is `@Query` the only way to use custom queries? - -No, `@Query` is *not* the only way to run custom queries. -The annotation is comfortable in situations in which your custom query fills your domain completely. -Please remember that SDN 6 assumes your mapped domain model to be the truth. -That means if you use a custom query via `@Query` that only fills a model partially, you are in danger of using the same -object to write the data back which will eventually erase or overwrite data you didn't consider in your query. - -So, please use repositories and declarative methods with `@Query` in all cases where the result is shaped like your domain -model or you are sure you don't use a partially mapped model for write commands. - -What are the alternatives? - -* <> might be already enough to shape your *view* on the graph: They can be used to define - the depth of fetching properties and related entities in an explicit way: By modelling them. -* If your goal is to make only the conditions of your queries *dynamic*, then have a look at the <> - but especially our own variant of it, the `CypherdslConditionExecutor`. Both <> allow adding conditions to - the full queries we create for you. Thus, you will have the domain fully populated together with custom conditions. - Of course, your conditions must work with what we generate. Find the names of the root node, the related nodes and more - <>. -* Use the http://neo4j-contrib.github.io/cypher-dsl/current/[Cypher-DSL] via the `CypherdslStatementExecutor` or the `ReactiveCypherdslStatementExecutor`. - The Cypher-DSL is predestined to create dynamic queries. In the end, it's what SDN uses under the hood anyway. The corresponding - mixins work both with the domain type of a repository itself as well as with projections (something that the mixins for adding - conditions don't). - -If you think that you can solve your problem with a partially dynamic query or a full dynamic query together with a projection, -please jump back now to the chapter <>. - -Otherwise, please read up on two things: <> -the <> we offer in SDN 6. - -Why speaking about custom repository fragments now? - -* You might have more complex situation in which more than one dynamic query is required, but the queries still belong - conceptually in a repository and not in the service layer -* Your custom queries return a graph shaped result that fits not quite to your domain model - and therefore the custom query should be accompanied by a custom mapping as well -* You have the need for interacting with the driver, i.e. for bulk loads that should not go through object mapping. - -Assume the following repository _declaration_ that basically aggregates one base repository plus 3 fragments: - -[source,java,indent=0,tabsize=4] -[[aggregating-repository]] -.A repository composed from several fragments ----- -include::../../../../src/test/java/org/springframework/data/neo4j/documentation/repositories/custom_queries/MovieRepository.java[tags=aggregating-interface] ----- - -The repository contains <> as shown in <>. - -The additional interface from which the repository extends (`DomainResults`, `NonDomainResults` and `LowlevelInteractions`) -are the fragments that addresses all the concerns above. - -=== Using complex, dynamic custom queries but still returning domain types - -The fragment `DomainResults` declares one additional method `findMoviesAlongShortestPath`: - -[source,java,indent=0,tabsize=4] -[[domain-results]] -.DomainResults fragment ----- -include::../../../../src/test/java/org/springframework/data/neo4j/documentation/repositories/custom_queries/MovieRepository.java[tags=domain-results] ----- - -This method is annotated with `@Transactional(readOnly = true)` to indicate that readers can answer it. -It cannot be derived by SDN but would need a custom query. -This custom query is provided by the one implementation of that interface. -The implementation has the same name with the suffix `Impl`: - -[source,java,indent=0,tabsize=4] -[[domain-results-impl]] -.A fragment implementation using the Neo4jTemplate ----- -include::../../../../src/test/java/org/springframework/data/neo4j/documentation/repositories/custom_queries/MovieRepository.java[tags=domain-results-impl] ----- -<.> The `Neo4jTemplate` is injected by the runtime through the constructor of `DomainResultsImpl`. No need for `@Autowired`. -<.> The Cypher-DSL is used to build a complex statement (pretty much the same as shown in <>.) - The statement can be passed directly to the template. - -The template has overloads for String-based queries as well, so you could write down the query as String as well. -The important takeaway here is: - -* The template "knows" your domain objects and maps them accordingly -* `@Query` is not the only option to define custom queries -* They can be generated in various ways -* The `@Transactional` annotation is respected - -=== Using custom queries and custom mappings - -Often times a custom query indicates custom results. -Should all of those results be mapped as `@Node`? Of course not! Many times those objects represents read commands -and are not meant to be used as write commands. -It is also not unlikely that SDN 6 cannot or want not map everything that is possible with Cypher. -It does however offer several hooks to run your own mapping: On the `Neo4jClient`. -The benefit of using the SDN 6 `Neo4jClient` over the driver: - -* The `Neo4jClient` is integrated with Springs transaction management -* It has a fluent API for binding parameters -* It has a fluent API exposing both the records and the Neo4j type system so that you can access - everything in your result to execute the mapping - -Declaring the fragment is exactly the same as before: - -[source,java,indent=0,tabsize=4] -[[non-domain-results]] -.A fragment declaring non-domain-type results ----- -include::../../../../src/test/java/org/springframework/data/neo4j/documentation/repositories/custom_queries/MovieRepository.java[tags=non-domain-results] ----- -<.> This is a made up non-domain result. A real world query result would probably look more complex. -<.> The method this fragment adds. Again, the method is annotated with Spring's `@Transactional` - -Without an implementation for that fragment, startup would fail, so here it is: - -[source,java,indent=0,tabsize=4] -[[non-domain-results-impl]] -.A fragment implementation using the Neo4jClient ----- -include::../../../../src/test/java/org/springframework/data/neo4j/documentation/repositories/custom_queries/MovieRepository.java[tags=non-domain-results-impl] ----- -<.> Here we use the `Neo4jClient`, as provided by the infrastructure. -<.> The client takes only in Strings, but the Cypher-DSL can still be used when rendering into a String -<.> Bind one single value to a named parameter. There's also an overload to bind a whole map of parameters -<.> This is the type of the result you want -<.> And finally, the `mappedBy` method, exposing one `Record` for each entry in the result plus the drivers type system if needed. - This is the API in which you hook in for your custom mappings - -The whole query runs in the context of a Spring transaction, in this case, a read-only one. - -==== Low level interactions - -Sometimes you might want to do bulk loadings from a repository or delete whole subgraphs or interact in very specific ways -with the Neo4j Java-Driver. This is possible as well. The following example shows how: - -[source,java,indent=0,tabsize=4] -[[low-level-interactions]] -.Fragments using the plain driver ----- -include::../../../../src/test/java/org/springframework/data/neo4j/documentation/repositories/custom_queries/MovieRepository.java[tags=lowlevel-interactions] ----- -<.> Work with the driver directly. As with all the examples: There is no need for `@Autowired` magic. All the fragments - are actually testable on their own. -<.> The use case is made up. Here we use a driver managed transaction deleting the whole graph and return the number of - deleted nodes and relationships - -This interaction does of course not run in a Spring transaction, as the driver does not know about Spring. - -Putting it all together, this test succeeds: - -[source,java,indent=0,tabsize=4] -[[custom-queries-test]] -.Testing the composed repository ----- -include::../../../../src/test/java/org/springframework/data/neo4j/documentation/repositories/custom_queries/CustomQueriesIT.java[tags=custom-queries-test] ----- - -As a final word: All three interfaces and implementations are picked up by Spring Data Neo4j automatically. -There is no need for further configuration. -Also, the same overall repository could have been created with only one additional fragment (the interface defining all three methods) -and one implementation. The implementation would than have had all three abstractions injected (template, client and driver). - -All of this applies of course to reactive repositories as well. -They would work with the `ReactiveNeo4jTemplate` and `ReactiveNeo4jClient` and the reactive session provided by the driver. - -If you have recurring methods for all repositories, you could swap out the default repository implementation. - -[[faq.custom-base-repositories]] -== How do I use custom Spring Data Neo4j base repositories? - -Basically the same ways as the shared Spring Data Commons documentation shows for Spring Data JPA in <>. -Only that in our case you would extend from - -[source,java,indent=0,tabsize=4] -[[custom-base-repository]] -.Custom base repository ----- -include::../../../../src/test/java/org/springframework/data/neo4j/integration/imperative/CustomBaseRepositoryIT.java[tags=custom-base-repository] ----- -<.> This signature is required by the base class. Take the `Neo4jOperations` (the actual specification of the `Neo4jTemplate`) - and the entity information and store them on an attribute if needed. - -In this example we forbid the use of the `findAll` method. -You could add methods taking in a fetch depth and run custom queries based on that depth. -One way to do this is shown in <>. - -To enable this base repository for all declared repositories enable Neo4j repositories with: `@EnableNeo4jRepositories(repositoryBaseClass = MyRepositoryImpl.class)`. - -[[faq.entities.auditing]] -== How do I audit entities? - -All Spring Data annotations are supported. -Those are - -* `org.springframework.data.annotation.CreatedBy` -* `org.springframework.data.annotation.CreatedDate` -* `org.springframework.data.annotation.LastModifiedBy` -* `org.springframework.data.annotation.LastModifiedDate` - -<> gives you a general view how to use auditing in the bigger context of Spring Data Commons. -The following listing presents every configuration option provided by Spring Data Neo4j: - -[source,java,indent=0,tabsize=4] -.Enabling and configuring Neo4j auditing ----- -include::../../../../src/test/java/org/springframework/data/neo4j/integration/imperative/AuditingIT.java[tags=faq.entities.auditing] ----- -<.> Set to true if you want the modification data to be written during creating as well -<.> Use this attribute to specify the name of the bean that provides the auditor (i.e. a user name) -<.> Use this attribute to specify the name of a bean that provides the current date. In this case - a fixed date is used as the above configuration is part of our tests - -The reactive version is basically the same apart from the fact the auditor aware bean is of type `ReactiveAuditorAware`, -so that the retrieval of an auditor is part of the reactive flow. - -In addition to those auditing mechanism you can add as many beans implementing `BeforeBindCallback` or `ReactiveBeforeBindCallback` -to the context. These beans will be picked up by Spring Data Neo4j and called in order (in case they implement `Ordered` or -are annotated with `@Order`) just before an entity is persisted. - -They can modify the entity or return a completely new one. -The following example adds one callback to the context that changes one attribute before the entity is persisted: - -[source,java,indent=0,tabsize=4] -.Modifying entities before save ----- -include::../../../../src/test/java/org/springframework/data/neo4j/integration/imperative/CallbacksIT.java[tags=faq.entities.auditing.callbacks] ----- - -No additional configuration is required. - -[[faq.find-by-example]] -== How do I use "Find by example"? - -"Find by example" is a new feature in SDN. -You instantiate an entity or use an existing one. -With this instance you create an `org.springframework.data.domain.Example`. -If your repository extends `org.springframework.data.neo4j.repository.Neo4jRepository` or `org.springframework.data.neo4j.repository.ReactiveNeo4jRepository`, you can immediately use the available `findBy` methods taking in an example, like shown in <> - -[source,java] -[[find-by-example-example]] -.findByExample in Action ----- -Example movieExample = Example.of(new MovieEntity("The Matrix", null)); -Flux movies = this.movieRepository.findAll(movieExample); - -movieExample = Example.of( - new MovieEntity("Matrix", null), - ExampleMatcher - .matchingAny() - .withMatcher( - "title", - ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.CONTAINING) - ) -); -movies = this.movieRepository.findAll(movieExample); ----- - -[[faq.spring-boot.sdn]] -== Do I need Spring Boot to use Spring Data Neo4j? - -No, you don't. -While the automatic configuration of many Spring aspects through Spring Boot takes away a lot of manual cruft and is the recommended approach for setting up new Spring projects, you don't need to have to use this. - -The following dependency is required for the solutions described above: - -[source,xml,subs="verbatim,attributes"] ----- - - {neo4jGroupId} - {artifactId} - {spring-data-neo4j-version} - ----- - -The coordinates for a Gradle setup are the same. - -To select a different database - either statically or dynamically - you can add a Bean of type `DatabaseSelectionProvider` as explained in <>. -For a reactive scenario, we provide `ReactiveDatabaseSelectionProvider`. - -[[faq.sdn-without-spring-boot]] -=== Using Spring Data Neo4j inside a Spring context without Spring Boot - -We provide two abstract configuration classes to support you in bringing in the necessary beans: `AbstractNeo4jConfig` for imperative database access and `AbstractReactiveNeo4jConfig` for the reactive version. -They are meant to be used with `@EnableNeo4jRepositories` and `@EnableReactiveNeo4jRepositories` respectively. -See <> and <> for an example usage. -Both classes require you to override `driver()` in which you are supposed to create the driver. - -To get the imperative version of the <>, the template and support for imperative repositories, use something similar as shown here: - -[source,java] -[[bootless-imperative-configuration]] -.Enabling Spring Data Neo4j infrastructure for imperative database access ----- -import org.neo4j.driver.Driver; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import org.springframework.transaction.annotation.EnableTransactionManagement; - -import org.springframework.data.neo4j.config.AbstractNeo4jConfig; -import org.springframework.data.neo4j.core.DatabaseSelectionProvider; -import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; - -@Configuration -@EnableNeo4jRepositories -@EnableTransactionManagement -class MyConfiguration extends AbstractNeo4jConfig { - - @Override @Bean - public Driver driver() { // <.> - return GraphDatabase.driver("bolt://localhost:7687", AuthTokens.basic("neo4j", "secret")); - } - - @Override - protected Collection getMappingBasePackages() { - return Collections.singletonList(Person.class.getPackage().getName()); - } - - @Override @Bean // <.> - protected DatabaseSelectionProvider databaseSelectionProvider() { - - return DatabaseSelectionProvider.createStaticDatabaseSelectionProvider("yourDatabase"); - } -} ----- -<.> The driver bean is required. -<.> This statically selects a database named `yourDatabase` and is *optional*. - -The following listing provides the reactive Neo4j client and template, enables reactive transaction management and discovers Neo4j related repositories: - -[source,java] -[[bootless-reactive-configuration]] -.Enabling Spring Data Neo4j infrastructure for reactive database access ----- -import org.neo4j.driver.Driver; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.neo4j.config.AbstractReactiveNeo4jConfig; -import org.springframework.data.neo4j.repository.config.EnableReactiveNeo4jRepositories; -import org.springframework.transaction.annotation.EnableTransactionManagement; - -@Configuration -@EnableReactiveNeo4jRepositories -@EnableTransactionManagement -class MyConfiguration extends AbstractReactiveNeo4jConfig { - - @Bean - @Override - public Driver driver() { - return GraphDatabase.driver("bolt://localhost:7687", AuthTokens.basic("neo4j", "secret")); - } - - @Override - protected Collection getMappingBasePackages() { - return Collections.singletonList(Person.class.getPackage().getName()); - } -} ----- - -[[faq.sdn-in-cdi-2.0]] -=== Using Spring Data Neo4j in a CDI 2.0 environment - -For your convenience we provide a CDI extension with `Neo4jCdiExtension`. -When run in a compatible CDI 2.0 container, it will be automatically be registered and loaded through https://docs.oracle.com/javase/tutorial/ext/basics/spi.html[Java's service loader SPI]. - -The only thing you have to bring into your application is an annotated type that produces the Neo4j Java Driver: - -[source,java] -[[cdi-driver-producer]] -.A CDI producer for the Neo4j Java Driver ----- -import javax.enterprise.context.ApplicationScoped; -import javax.enterprise.inject.Disposes; -import javax.enterprise.inject.Produces; - -import org.neo4j.driver.AuthTokens; -import org.neo4j.driver.Driver; -import org.neo4j.driver.GraphDatabase; - -public class Neo4jConfig { - - @Produces @ApplicationScoped - public Driver driver() { // <.> - return GraphDatabase - .driver("bolt://localhost:7687", AuthTokens.basic("neo4j", "secret")); - } - - public void close(@Disposes Driver driver) { - driver.close(); - } - - @Produces @Singleton - public DatabaseSelectionProvider getDatabaseSelectionProvider() { // <.> - return DatabaseSelectionProvider.createStaticDatabaseSelectionProvider("yourDatabase"); - } -} ----- -<.> Same as with plain Spring in <>, but annotated with the corresponding CDI infrastructure. -<.> This is *optional*. However, if you run a custom database selection provider, you _must_ not qualify this bean. - -If you are running in a SE Container - like the one https://weld.cdi-spec.org[Weld] provides for example, you can enable the extension like that: - -[source,java] -[[cdi-driver-producer-se]] -.Enabling the Neo4j CDI extension in a SE container ----- -import javax.enterprise.inject.se.SeContainer; -import javax.enterprise.inject.se.SeContainerInitializer; - -import org.springframework.data.neo4j.config.Neo4jCdiExtension; - -public class SomeClass { - void someMethod() { - try (SeContainer container = SeContainerInitializer.newInstance() - .disableDiscovery() - .addExtensions(Neo4jCdiExtension.class) - .addBeanClasses(YourDriverFactory.class) - .addPackages(Package.getPackage("your.domain.package")) - .initialize() - ) { - SomeRepository someRepository = container.select(SomeRepository.class).get(); - } - } -} ----- diff --git a/src/main/asciidoc/faq/index.adoc b/src/main/asciidoc/faq/index.adoc index 9cb44a98d..75754b8e3 100644 --- a/src/main/asciidoc/faq/index.adoc +++ b/src/main/asciidoc/faq/index.adoc @@ -1 +1,1461 @@ -include::faq.adoc[leveloffset=+1] +[[faq.sdn-related-to-ogm]] +== How does SDN relate to Neo4j-OGM? + +https://neo4j.com/docs/ogm-manual/current/[Neo4j-OGM] is an Object Graph Mapping library, which is mainly used by previous versions of Spring Data Neo4j as its backend for the heavy lifting of mapping nodes and relationships into domain object. +The current SDN *does not need* and *does not support* Neo4j-OGM. +SDN uses Spring Data's mapping context exclusively for scanning classes and building the meta model. + +While this pins SDN to the Spring ecosystem, it has several advantages, among them the smaller footprint regarding CPU and memory usage and especially, all the features of Spring's mapping context. + +[[faq.why-should-you-favor-sdn]] +=== Why should I use SDN in favor of SDN+OGM + +SDN has several features not present in SDN+OGM, notably + +* Full support for Springs reactive story, including reactive transaction +* Full support for https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#query-by-example[Query By Example] +* Full support for fully immutable entities +* Support for all modifiers and variations of derived finder methods, including spatial queries + +[[faq.sdn-and-http-support]] +== Does SDN support connections over HTTP to Neo4j? + +No. + +[[faq.sdn-and-embedded-support]] +== Does SDN support embedded Neo4j? + +Embedded Neo4j has multiple facets to it: + +[[faq.sdn-and-embedded-instances-support]] +=== Does SDN provide an embedded instance for your application? + +No. + +[[faq.sdn-interact-with-embedded-instances]] +=== Does SDN interact directly with an embedded instance? + +No. +An embedded database is usually represented by an instance of `org.neo4j.graphdb.GraphDatabaseService` and has no Bolt connector out of the box. + +SDN can however work very much with Neo4j's test harness, the test harness is specially meant to be a drop-in replacement for the real database. +Support for both Neo4j 3.5 and 4.x test harness is implemented via link:{java-driver-starter-href}[the Spring Boot starter for the driver]. +Have a look at the corresponding module `org.neo4j.driver:neo4j-java-driver-test-harness-spring-boot-autoconfigure`. + +[[faq.change-driver-version]] +== Which Neo4j Java Driver can be used and how? + +SDN6 relies on the Neo4j Java Driver version 4.x. +Each SDN6 release uses a Neo4j Java Driver version compatible with the latest Neo4j available at the time of its +release. +While patch versions of the Neo4j Java Driver are usually drop-in replacements, SDN6 makes sure that even minor versions +are interchangeable as it checks for the presence or absence of methods or interface changes if necessary. + +Therefore, you are able to use any 4.x Neo4j Java Driver with any SDN 6.x version. + +=== With Spring Boot + +These days, a Spring boot deployment is the most likely deployment of a Spring Data based applications. Please use +Spring Boots dependency management to change the driver version like this: + +.Change the driver version from Maven (pom.xml) +[source,xml] +---- + + 4.4.2 + +---- + +Or + +.Change the driver version from Gradle (gradle.properties) +[source,properties] +---- +neo4j-java-driver.version = 4.4.2 +---- + +=== Without Spring Boot + +Without Spring Boot, you would just manually declare the dependency. For Maven, we recommend using the `` +section like this: + +.Change the driver version without Spring Boot from Maven (pom.xml) +---- + + + org.neo4j.driver + neo4j-java-driver + 4.4.2 + + +---- + +[[faq.multidatabase]] +== Neo4j 4.0 supports multiple databases - How can I use them? + +You can either statically configure the database name or run your own database name provider. +Bear in mind that SDN will not create the databases for you. +You can do this with the help of a https://github.com/michael-simons/neo4j-migrations[migrations tool] +or of course with a simple script upfront. + +[[faq.multidatabase.statically]] +=== Statically configured + +Configure the database name to use in your Spring Boot configuration like this (the same property applies of course for YML or environment based configuration, with Spring Boot's conventions applied): + +[source,properties] +---- +spring.data.neo4j.database = yourDatabase +---- + +With that configuration in place, all queries generated by all instances of SDN repositories (both reactive and imperative) and by the `ReactiveNeo4jTemplate` respectively `Neo4jTemplate` will be executed against the database `yourDatabase`. + +[[faq.multidatabase.dynamically]] +=== Dynamically configured + +Provide a bean with the type `Neo4jDatabaseNameProvider` or `ReactiveDatabaseSelectionProvider` depending on the type of your Spring application. + +That bean could use for example Spring's security context to retrieve a tenant. +Here is a working example for an imperative application secured with Spring Security: + +[source,java] +[[faq.databaseSelectionProvider]] +.Neo4jConfig.java +---- +import org.neo4j.springframework.data.core.DatabaseSelection; +import org.neo4j.springframework.data.core.DatabaseSelectionProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.User; + +include::../../../../src/test/java/org/springframework/data/neo4j/documentation/Neo4jConfig.java[tags=faq.multidatabase] +---- + +NOTE: Be careful that you don't mix up entities retrieved from one database with another database. +The database name is requested for each new transaction, so you might end up with less or more entities than expected when changing the database name in between calls. +Or worse, you could inevitably store the wrong entities in the wrong database. + +[[faq.multidatabase.health]] +=== The Spring Boot Neo4j health indicator targets the default database, how can I change that? + +Spring Boot comes with both imperative and reactive Neo4j https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-health[health indicators.] +Both variants are able to detect multiple beans of `org.neo4j.driver.Driver` inside the application context and provide +a contribution to the overall health for each instance. +The Neo4j driver however does connect to a server and not to a specific database inside that server. +Spring Boot is able to configure the driver without Spring Data Neo4j and as the information which database is to be used +is tied to Spring Data Neo4j, this information is not available to the built-in health indicator. + +This is most likely not a problem in many deployment scenarios. +However, if configured database user does not have at least access rights to the default database, the health checks will fail. + +This can be mitigated by custom Neo4j health contributors that are aware of the database selection. + +==== Imperative variant + +[[faq.multidatabase.health.imperative]] +[source,java,indent=0,tabsize=4] +---- +import java.util.Optional; + +import org.neo4j.driver.Driver; +import org.neo4j.driver.Result; +import org.neo4j.driver.SessionConfig; +import org.neo4j.driver.summary.DatabaseInfo; +import org.neo4j.driver.summary.ResultSummary; +import org.neo4j.driver.summary.ServerInfo; +import org.springframework.boot.actuate.health.AbstractHealthIndicator; +import org.springframework.boot.actuate.health.Health; +import org.springframework.data.neo4j.core.DatabaseSelection; +import org.springframework.data.neo4j.core.DatabaseSelectionProvider; +import org.springframework.util.StringUtils; + +public class DatabaseSelectionAwareNeo4jHealthIndicator extends AbstractHealthIndicator { + + private final Driver driver; + + private final DatabaseSelectionProvider databaseSelectionProvider; + + public DatabaseSelectionAwareNeo4jHealthIndicator( + Driver driver, DatabaseSelectionProvider databaseSelectionProvider + ) { + this.driver = driver; + this.databaseSelectionProvider = databaseSelectionProvider; + } + + @Override + protected void doHealthCheck(Health.Builder builder) { + try { + SessionConfig sessionConfig = Optional + .ofNullable(databaseSelectionProvider.getDatabaseSelection()) + .filter(databaseSelection -> databaseSelection != DatabaseSelection.undecided()) + .map(DatabaseSelection::getValue) + .map(v -> SessionConfig.builder().withDatabase(v).build()) + .orElseGet(SessionConfig::defaultConfig); + + class Tuple { + String edition; + ResultSummary resultSummary; + + Tuple(String edition, ResultSummary resultSummary) { + this.edition = edition; + this.resultSummary = resultSummary; + } + } + + String query = + "CALL dbms.components() YIELD name, edition WHERE name = 'Neo4j Kernel' RETURN edition"; + Tuple health = driver.session(sessionConfig) + .writeTransaction(tx -> { + Result result = tx.run(query); + String edition = result.single().get("edition").asString(); + return new Tuple(edition, result.consume()); + }); + + addHealthDetails(builder, health.edition, health.resultSummary); + } catch (Exception ex) { + builder.down().withException(ex); + } + } + + static void addHealthDetails(Health.Builder builder, String edition, ResultSummary resultSummary) { + ServerInfo serverInfo = resultSummary.server(); + builder.up() + .withDetail( + "server", serverInfo.version() + "@" + serverInfo.address()) + .withDetail("edition", edition); + DatabaseInfo databaseInfo = resultSummary.database(); + if (StringUtils.hasText(databaseInfo.name())) { + builder.withDetail("database", databaseInfo.name()); + } + } +} +---- + +This uses the available database selection to run the same query that Boot runs to check whether a connection is healthy or not. +Use the following configuration to apply it: + +[[faq.multidatabase.health.imperative.config]] +[source,java,indent=0,tabsize=4] +---- +import java.util.Map; + +import org.neo4j.driver.Driver; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.actuate.health.CompositeHealthContributor; +import org.springframework.boot.actuate.health.HealthContributor; +import org.springframework.boot.actuate.health.HealthContributorRegistry; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.neo4j.core.DatabaseSelectionProvider; + +@Configuration(proxyBeanMethods = false) +public class Neo4jHealthConfig { + + @Bean // <.> + DatabaseSelectionAwareNeo4jHealthIndicator databaseSelectionAwareNeo4jHealthIndicator( + Driver driver, DatabaseSelectionProvider databaseSelectionProvider + ) { + return new DatabaseSelectionAwareNeo4jHealthIndicator(driver, databaseSelectionProvider); + } + + @Bean // <.> + HealthContributor neo4jHealthIndicator( + Map customNeo4jHealthIndicators) { + return CompositeHealthContributor.fromMap(customNeo4jHealthIndicators); + } + + @Bean // <.> + InitializingBean healthContributorRegistryCleaner( + HealthContributorRegistry healthContributorRegistry, + Map customNeo4jHealthIndicators + ) { + return () -> customNeo4jHealthIndicators.keySet() + .stream() + .map(HealthContributorNameFactory.INSTANCE) + .forEach(healthContributorRegistry::unregisterContributor); + } +} +---- +<.> If you have multiple drivers and database selection providers, you would need to create one indicator per combination +<.> This makes sure that all of those indicators are grouped under Neo4j, replacing the default Neo4j health indicator +<.> This prevents the individual contributors showing up in the health endpoint directly + +==== Reactive variant + +The reactive variant is basically the same, using reactive types and the corresponding reactive infrastructure classes: + +[[faq.multidatabase.health.reactive]] +[source,java,indent=0,tabsize=4] +---- +import reactor.core.publisher.Mono; +import reactor.util.function.Tuple2; + +import org.neo4j.driver.Driver; +import org.neo4j.driver.SessionConfig; +import org.neo4j.driver.reactive.RxResult; +import org.neo4j.driver.reactive.RxSession; +import org.neo4j.driver.summary.DatabaseInfo; +import org.neo4j.driver.summary.ResultSummary; +import org.neo4j.driver.summary.ServerInfo; +import org.reactivestreams.Publisher; +import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator; +import org.springframework.boot.actuate.health.Health; +import org.springframework.data.neo4j.core.DatabaseSelection; +import org.springframework.data.neo4j.core.ReactiveDatabaseSelectionProvider; +import org.springframework.util.StringUtils; + +public final class DatabaseSelectionAwareNeo4jReactiveHealthIndicator + extends AbstractReactiveHealthIndicator { + + private final Driver driver; + + private final ReactiveDatabaseSelectionProvider databaseSelectionProvider; + + public DatabaseSelectionAwareNeo4jReactiveHealthIndicator( + Driver driver, + ReactiveDatabaseSelectionProvider databaseSelectionProvider + ) { + this.driver = driver; + this.databaseSelectionProvider = databaseSelectionProvider; + } + + @Override + protected Mono doHealthCheck(Health.Builder builder) { + String query = + "CALL dbms.components() YIELD name, edition WHERE name = 'Neo4j Kernel' RETURN edition"; + return databaseSelectionProvider.getDatabaseSelection() + .map(databaseSelection -> databaseSelection == DatabaseSelection.undecided() ? + SessionConfig.defaultConfig() : + SessionConfig.builder().withDatabase(databaseSelection.getValue()).build() + ) + .flatMap(sessionConfig -> + Mono.usingWhen( + Mono.fromSupplier(() -> driver.rxSession(sessionConfig)), + s -> { + Publisher> f = s.readTransaction(tx -> { + RxResult result = tx.run(query); + return Mono.from(result.records()) + .map((record) -> record.get("edition").asString()) + .zipWhen((edition) -> Mono.from(result.consume())); + }); + return Mono.fromDirect(f); + }, + RxSession::close + ) + ).map((result) -> { + addHealthDetails(builder, result.getT1(), result.getT2()); + return builder.build(); + }); + } + + static void addHealthDetails(Health.Builder builder, String edition, ResultSummary resultSummary) { + ServerInfo serverInfo = resultSummary.server(); + builder.up() + .withDetail( + "server", serverInfo.version() + "@" + serverInfo.address()) + .withDetail("edition", edition); + DatabaseInfo databaseInfo = resultSummary.database(); + if (StringUtils.hasText(databaseInfo.name())) { + builder.withDetail("database", databaseInfo.name()); + } + } +} + +---- + +And of course, the reactive variant of the configuration. It needs two different registry cleaners, as Spring Boot will +wrap existing reactive indicators to be used with the non-reactive actuator endpoint, too. + +[[faq.multidatabase.health.reactive.config]] +[source,java,indent=0,tabsize=4] +---- +import java.util.Map; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.actuate.health.CompositeReactiveHealthContributor; +import org.springframework.boot.actuate.health.HealthContributorNameFactory; +import org.springframework.boot.actuate.health.HealthContributorRegistry; +import org.springframework.boot.actuate.health.ReactiveHealthContributor; +import org.springframework.boot.actuate.health.ReactiveHealthContributorRegistry; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +public class Neo4jHealthConfig { + + @Bean + ReactiveHealthContributor neo4jHealthIndicator( + Map customNeo4jHealthIndicators) { + return CompositeReactiveHealthContributor.fromMap(customNeo4jHealthIndicators); + } + + @Bean + InitializingBean healthContributorRegistryCleaner(HealthContributorRegistry healthContributorRegistry, + Map customNeo4jHealthIndicators) { + return () -> customNeo4jHealthIndicators.keySet() + .stream() + .map(HealthContributorNameFactory.INSTANCE) + .forEach(healthContributorRegistry::unregisterContributor); + } + + @Bean + InitializingBean reactiveHealthContributorRegistryCleaner( + ReactiveHealthContributorRegistry healthContributorRegistry, + Map customNeo4jHealthIndicators) { + return () -> customNeo4jHealthIndicators.keySet() + .stream() + .map(HealthContributorNameFactory.INSTANCE) + .forEach(healthContributorRegistry::unregisterContributor); + } +} +---- + +[[faq.impersonation]] +== Neo4j 4.4 supports impersonation of different users - How can I use them? + +User impersonation is especially interesting in big multi-tenant settings, in which one physically connected (or technical) +user can impersonate many tenants. Depending on your setup this will drastically reduce the number of physical driver instances needed. + +The feature requires Neo4j Enterprise 4.4+ on the server side and a 4.4+ driver on the client side (`org.neo4j.driver:neo4j-java-driver:4.4.0` or higher). + +For both imperative and reactive versions you need to provide a `UserSelectionProvider` respectively a `ReactiveUserSelectionProvider`. +The same instance needs to be passed along to the `Neo4Client` and `Neo4jTransactionManager` respectively their reactive variants. + +In <> and <> configurations you just need to provide a bean of the +type in question: + +[[faq.impersonation.userselectionbean]] +.User selection provider bean +[source,java] +---- +import org.springframework.data.neo4j.core.UserSelection; +import org.springframework.data.neo4j.core.UserSelectionProvider; + +public class CustomConfig { + + @Bean + public UserSelectionProvider getUserSelectionProvider() { + return () -> UserSelection.impersonate("someUser"); + } +} +---- + +In a typical Spring Boot scenario this feature requires a bit more work, as Boot supports also SDN versions without that feature. +So given the bean in <>, you would need fully customize the client and transaction manager: + +[[faq.impersonation.boot]] +.Necessary customization for Spring Boot +[source,java] +---- +import org.neo4j.driver.Driver; + +import org.springframework.data.neo4j.core.DatabaseSelectionProvider; +import org.springframework.data.neo4j.core.Neo4jClient; +import org.springframework.data.neo4j.core.UserSelectionProvider; +import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager; + +import org.springframework.transaction.PlatformTransactionManager; + +public class CustomConfig { + + @Bean + public Neo4jClient neo4jClient( + Driver driver, + DatabaseSelectionProvider databaseSelectionProvider, + UserSelectionProvider userSelectionProvider + ) { + + return Neo4jClient.with(driver) + .withDatabaseSelectionProvider(databaseSelectionProvider) + .withUserSelectionProvider(userSelectionProvider) + .build(); + } + + @Bean + public PlatformTransactionManager transactionManager( + Driver driver, + DatabaseSelectionProvider databaseSelectionProvider, + UserSelectionProvider userSelectionProvider + ) { + + return Neo4jTransactionManager + .with(driver) + .withDatabaseSelectionProvider(databaseSelectionProvider) + .withUserSelectionProvider(userSelectionProvider) + .build(); + } +} +---- + +[[faq.cluster]] +== Using a Neo4j cluster instance from Spring Data Neo4j + +The following questions apply to Neo4j AuraDB as well as to Neo4j on-premise cluster instances. + +[[faq.transactions.cluster]] +=== Do I need specific configuration so that transactions work seamless with a Neo4j Causal Cluster? + +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.transactions.cluster.rw]] +=== Is it important to use read-only transactions for Neo4j cluster? + +Yes, it is. +The Neo4j cluster architecture is a causal clustering architecture, and it distinguishes between primary and secondary servers. +Primary server either are single instances or core instances. Both of them can answer to read and write operations. +Write operations are propagated from the core instances to read replicas or more generally, followers, inside the cluster. +Those followers are secondary servers. +Secondary servers don't answer to write operations. + +In a standard deployment scenario you'll have some core instances and many read replicas inside a cluster. +Therefore, it is important to mark operations or queries as read-only to scale your cluster in such a way that leaders are +never overwhelmed and queries are propagated as much as possible to read replicas. + +Neither Spring Data Neo4j nor the underlying Java driver do Cypher parsing and both building blocks assume +write operations by default. This decision has been made to support all operations out of the box. If something in the +stack would assume read-only by default, the stack might end up sending write queries to read replicas and fail +on executing them. + +NOTE: All `findById`, `findAllById`, `findAll` and predefined existential methods are marked as read-only by default. + +Some options are described below: + +.Making a whole repository read-only +[source,java] +---- +import org.springframework.data.neo4j.repository.Neo4jRepository; +import org.springframework.transaction.annotation.Transactional; + +@Transactional(readOnly = true) +interface PersonRepository extends Neo4jRepository { +} +---- + +.Making selected repository methods read-only +[source,java] +---- +import org.springframework.data.neo4j.repository.Neo4jRepository; +import org.springframework.data.neo4j.repository.query.Query; +import org.springframework.transaction.annotation.Transactional; + +interface PersonRepository extends Neo4jRepository { + + @Transactional(readOnly = true) + Person findOneByName(String name); // <.> + + @Transactional(readOnly = true) + @Query(""" + CALL apoc.search.nodeAll('{Person: "name",Movie: ["title","tagline"]}','contains','her') + YIELD node AS n RETURN n""") + Person findByCustomQuery(); // <.> +} +---- +<.> Why isn't this read-only be default? While it would work for the derived finder above (which we actually know to be read-only), + we often have seen cases in which user add a custom `@Query` and implement it via a `MERGE` construct, + which of course is a write operation. +<.> Custom procedures can do all kinds of things, there's no way at the moment to check for read-only vs write here for us. + +.Orchestrate calls to a repository from a service +[source,java] +---- +import java.util.Optional; + +import org.springframework.data.neo4j.repository.Neo4jRepository; +import org.springframework.transaction.annotation.Transactional; + +interface PersonRepository extends Neo4jRepository { +} + +interface MovieRepository extends Neo4jRepository { + List findByLikedByPersonName(String name); +} + +public class PersonService { + + private final PersonRepository personRepository; + private final MovieRepository movieRepository; + + public PersonService(PersonRepository personRepository, + MovieRepository movieRepository) { + this.personRepository = personRepository; + this.movieRepository = movieRepository; + } + + @Transactional(readOnly = true) + public Optional getPerson(Long id) { // <.> + return this.repository.findById(id) + .map(person -> { + var movies = this.movieRepository + .findByLikedByPersonName(person.getName()); + return new PersonDetails(person, movies); + }); + } +} +---- +<.> Here, several calls to multiple repositories are wrapped in one single, read-only transaction. + + +.Using Springs `TransactionTemplate` inside private service methods and / or with the Neo4j client +[source,java] +---- +import java.util.Collection; + +import org.neo4j.driver.types.Node; +import org.springframework.data.neo4j.core.Neo4jClient; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.support.TransactionTemplate; + +public class PersonService { + + private final TransactionTemplate readOnlyTx; + + private final Neo4jClient neo4jClient; + + public PersonService(PlatformTransactionManager transactionManager, Neo4jClient neo4jClient) { + + this.readOnlyTx = new TransactionTemplate(transactionManager, // <.> + new TransactionDefinition() { + @Override public boolean isReadOnly() { + return true; + } + } + ); + this.neo4jClient = neo4jClient; + } + + void internalOperation() { // <.> + + Collection nodes = this.readOnlyTx.execute(state -> { + return neo4jClient.query("MATCH (n) RETURN n").fetchAs(Node.class) // <.> + .mappedBy((types, record) -> record.get(0).asNode()) + .all(); + }); + } +} +---- +<.> Create an instance of the `TransactionTemplate` with the characteristics you need. + Of course, this can be a global bean, too. +<.> Reason number one for using the transaction template: Declarative transactions don't work + in package private or private methods and also not in inner method calls (imagine another method + in this service calling `internalOperation`) due to their nature being implemented with Aspects + and proxies. +<.> The `Neo4jClient` is a fixed utility provided by SDN. It cannot be annotated, but it integrates with Spring. + So it gives you everything you would do with the pure driver and without automatic mapping and with + transactions. It also obeys declarative transactions. + +[[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. +It may however be useful to retrieve the latest bookmark the SDN transaction system received from a database. +You can add a `@Bean` like `BookmarkCapture` to do this: + +[source,java,indent=0,tabsize=4] +.BookmarkCapture.java +---- +import java.util.Set; + +import org.neo4j.driver.Bookmark; +import org.springframework.context.ApplicationListener; + +public final class BookmarkCapture + implements ApplicationListener { + + @Override + public void onApplicationEvent(Neo4jBookmarksUpdatedEvent event) { + // We make sure that this event is called only once, + // the thread safe application of those bookmarks is up to your system. + Set latestBookmarks = event.getBookmarks(); + } +} +---- + +For seeding the transaction system, a customized transaction manager like the following is required: + +[source,java,indent=0,tabsize=4] +.BookmarkSeedingConfig.java +---- +import java.util.Set; +import java.util.function.Supplier; + +import org.neo4j.driver.Bookmark; +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 BookmarkSeedingConfig { + + @Bean + public PlatformTransactionManager transactionManager( + Driver driver, DatabaseSelectionProvider databaseNameProvider) { // <.> + + Supplier> bookmarkSupplier = () -> { // <.> + Bookmark a = null; + Bookmark b = null; + return Set.of(a, b); + }; + + Neo4jBookmarkManager bookmarkManager = + Neo4jBookmarkManager.create(bookmarkSupplier); // <.> + return new Neo4jTransactionManager( + driver, databaseNameProvider, bookmarkManager); // <.> + } +} +---- +<.> Let Spring inject those +<.> This supplier can be anything that holds the latest bookmarks you want to bring into the system +<.> Create the bookmark manager with it +<.> Pass it on to the customized transaction manager + +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? + +No. +You are free to use the following, equivalent Spring Data annotations: + +[cols="4*",options="header"] +|=== + +|SDN specific annotation +|Spring Data common annotation +|Purpose +|Difference + +|`org.springframework.data.neo4j.core.schema.Id` +|`org.springframework.data.annotation.Id` +|Marks the annotated attribute as the unique id. +|Specific annotation has no additional features. + +|`org.springframework.data.neo4j.core.schema.Node` +|`org.springframework.data.annotation.Persistent` +|Marks the class as persistent entity. +|`@Node` allows customizing the labels + +|=== + +[[faq.ids.assignment]] +== How do I use assigned ids? + +Just use `@Id` without `@GeneratedValue` and fill your id attribute via a constructor parameter or a setter or _wither_. +See this https://medium.com/neo4j/neo4j-ogm-and-spring-data-neo4j-a55a866df68c[blog post] for some general remarks about finding good ids. + +[[faq.ids.externally]] +== How do I use externally generated ids? + +We provide the interface `org.springframework.data.neo4j.core.schema.IdGenerator`. +Implement it in any way you want and configure your implementation like this: + +[source,java] +.ThingWithGeneratedId.java +---- +@Node +public class ThingWithGeneratedId { + + @Id @GeneratedValue(TestSequenceGenerator.class) + private String theId; +} +---- + +If you pass in the name of a class to `@GeneratedValue`, this class must have a no-args default constructor. +You can however use a string as well: + +[source,java] +.ThingWithIdGeneratedByBean.java +---- +@Node +public class ThingWithIdGeneratedByBean { + + @Id @GeneratedValue(generatorRef = "idGeneratingBean") + private String theId; +} +---- + +With that, `idGeneratingBean` refers to a bean in the Spring context. +This might be useful for sequence generating. + +NOTE: Setters are not required on non-final fields for the id. + +[[template-support]] +== Do I have to create repositories for each domain class? + +No. +Have a look at the <> and find the `Neo4jTemplate` or the `ReactiveNeo4jTemplate`. + +Those templates know your domain and provide all necessary basic CRUD methods for retrieving, writing and counting entities. + +This is our canonical movie example with the imperative template: + +[source,java] +[[imperative-template-example]] +.TemplateExampleTest.java +---- +include::../../../../src/test/java/org/springframework/data/neo4j/documentation/spring_boot/TemplateExampleTest.java[tags=faq.template-imperative-pt1] +@DataNeo4jTest +include::../../../../src/test/java/org/springframework/data/neo4j/documentation/spring_boot/TemplateExampleTest.java[tags=faq.template-imperative-pt2] +---- + +And here is the reactive version, omitting the setup for brevity: + +[source,java] +[[reactive-template-example]] +.ReactiveTemplateExampleTest.java +---- +include::../../../../src/test/java/org/springframework/data/neo4j/documentation/spring_boot/ReactiveTemplateExampleTest.java[tags=faq.template-reactive-pt1] +@DataNeo4jTest +include::../../../../src/test/java/org/springframework/data/neo4j/documentation/spring_boot/ReactiveTemplateExampleTest.java[tags=faq.template-reactive-pt2] +---- + +Please note that both examples use `@DataNeo4jTest` from Spring Boot. + +[[faq.custom-queries-with-page-and-slice]] +== How do I use custom queries with repository methods returning `Page` or `Slice`? + +While you don't have to provide anything else apart a `Pageable` as a parameter on derived finder methods +that return a `Page` or a `Slice`, you must prepare your custom query to handle the pageable. +<> gives you an overview about what's needed. + +[[custom-queries-with-page-and-slice-examples]] +[source,java] +.Pages and Slices +---- +import org.springframework.data.domain.Pageable; +import org.springframework.data.neo4j.repository.Neo4jRepository; +import org.springframework.data.neo4j.repository.query.Query; + +public interface MyPersonRepository extends Neo4jRepository { + + Page findByName(String name, Pageable pageable); // <.> + + @Query("" + + "MATCH (n:Person) WHERE n.name = $name RETURN n " + + "ORDER BY n.name ASC SKIP $skip LIMIT $limit" + ) + Slice findSliceByName(String name, Pageable pageable); // <.> + + @Query( + value = "" + + "MATCH (n:Person) WHERE n.name = $name RETURN n " + + "ORDER BY n.name ASC SKIP $skip LIMIT $limit", + countQuery = "" + + "MATCH (n:Person) WHERE n.name = $name RETURN count(n)" + ) + Page findPageByName(String name, Pageable pageable); // <.> +} +---- +<.> A derived finder method that creates a query for you. + It handles the `Pageable` for you. + You should use a sorted pageable. +<.> This method uses `@Query` to define a custom query. It returns a `Slice`. + A slice does not know about the total number of pages, so the custom query + doesn't need a dedicated count query. SDN will notify you that it estimates the next slice. + The Cypher template must spot both `$skip` and `$limit` Cypher parameter. + If you omit them, SDN will issue a warning. The will probably not match your expectations. + Also, the `Pageable` should be unsorted and you should provide a stable order. + We won't use the sorting information from the pageable. +<.> This method returns a page. A page knows about the exact number of total pages. + Therefore, you must specify an additional count query. + All other restrictions from the second method apply. + +[[faq.path-mapping]] +== Can I map named paths? + +A series of connected nodes and relationships is called a "path" in Neo4j. +Cypher allows paths to be named using an identifier, as exemplified by: + +[source,cypher] +---- +p = (a)-[*3..5]->(b) +---- + +or as in the infamous Movie graph, that includes the following path (in that case, one of the shortest path between two actors): + +[[bacon-distance]] +[source,cypher] +.The "Bacon" distance +---- +MATCH p=shortestPath((bacon:Person {name:"Kevin Bacon"})-[*]-(meg:Person {name:"Meg Ryan"})) +RETURN p +---- + +Which looks like this: + +image::bacon-distance.png[] + +We find 3 nodes labeled `Vertex` and 2 nodes labeled `Movie`. Both can be mapped with a custom query. +Assume there's a node entity for both `Vertex` and `Movie` as well as `Actor` taking care of the relationship: + + +[source,java] +."Standard" movie graph domain model +---- +@Node +public final class Person { + + @Id @GeneratedValue + private final Long id; + + private final String name; + + private Integer born; + + @Relationship("REVIEWED") + private List reviewed = new ArrayList<>(); +} + +@RelationshipProperties +public final class Actor { + + @RelationshipId + private final Long id; + + @TargetNode + private final Person person; + + private final List roles; +} + +@Node +public final class Movie { + + @Id + private final String title; + + @Property("tagline") + private final String description; + + @Relationship(value = "ACTED_IN", direction = Direction.INCOMING) + private final List actors; +} +---- + +When using a query as shown in <> for a domain class of type `Vertex` like this + +[source,java] +---- +interface PeopleRepository extends Neo4jRepository { + @Query("" + + "MATCH p=shortestPath((bacon:Person {name: $person1})-[*]-(meg:Person {name: $person2}))\n" + + "RETURN p" + ) + List findAllOnShortestPathBetween(@Param("person1") String person1, @Param("person2") String person2); +} +---- + +it will retrieve all people from the path and map them. +If there are relationship types on the path like `REVIEWED` that are also present on the domain, these +will be filled accordingly from the path. + +WARNING: Take special care when you use nodes hydrated from a path based query to save data. +If not all relationships are hydrated, data will be lost. + +The other way round works as well. The same query can be used with the `Movie` entity. +It then will only populate movies. +The following listing shows how todo this as well as how the query can be enriched with additional data +not found on the path. That data is used to correctly populate the missing relationships (in that case, all the actors) + +[source,java] +---- +interface MovieRepository extends Neo4jRepository { + + @Query("" + + "MATCH p=shortestPath(\n" + + "(bacon:Person {name: $person1})-[*]-(meg:Person {name: $person2}))\n" + + "WITH p, [n IN nodes(p) WHERE n:Movie] AS x\n" + + "UNWIND x AS m\n" + + "MATCH (m) <-[r:DIRECTED]-(d:Person)\n" + + "RETURN p, collect(r), collect(d)" + ) + List findAllOnShortestPathBetween(@Param("person1") String person1, @Param("person2") String person2); +} +---- + +The query returns the path plus all relationships and related nodes collected so that the movie entities are fully hydrated. + +The path mapping works for single paths as well for multiple records of paths (which are returned by the `allShortestPath` function.) + +TIP: Named paths can be used efficiently to populate and return more than just a root node, see <>. + + +[[faq.custom-queries-and-custom-mappings]] +== Is `@Query` the only way to use custom queries? + +No, `@Query` is *not* the only way to run custom queries. +The annotation is comfortable in situations in which your custom query fills your domain completely. +Please remember that SDN 6 assumes your mapped domain model to be the truth. +That means if you use a custom query via `@Query` that only fills a model partially, you are in danger of using the same +object to write the data back which will eventually erase or overwrite data you didn't consider in your query. + +So, please use repositories and declarative methods with `@Query` in all cases where the result is shaped like your domain +model or you are sure you don't use a partially mapped model for write commands. + +What are the alternatives? + +* <> might be already enough to shape your *view* on the graph: They can be used to define + the depth of fetching properties and related entities in an explicit way: By modelling them. +* If your goal is to make only the conditions of your queries *dynamic*, then have a look at the <> + but especially our own variant of it, the `CypherdslConditionExecutor`. Both <> allow adding conditions to + the full queries we create for you. Thus, you will have the domain fully populated together with custom conditions. + Of course, your conditions must work with what we generate. Find the names of the root node, the related nodes and more + <>. +* Use the http://neo4j-contrib.github.io/cypher-dsl/current/[Cypher-DSL] via the `CypherdslStatementExecutor` or the `ReactiveCypherdslStatementExecutor`. + The Cypher-DSL is predestined to create dynamic queries. In the end, it's what SDN uses under the hood anyway. The corresponding + mixins work both with the domain type of a repository itself as well as with projections (something that the mixins for adding + conditions don't). + +If you think that you can solve your problem with a partially dynamic query or a full dynamic query together with a projection, +please jump back now to the chapter <>. + +Otherwise, please read up on two things: <> +the <> we offer in SDN 6. + +Why speaking about custom repository fragments now? + +* You might have more complex situation in which more than one dynamic query is required, but the queries still belong + conceptually in a repository and not in the service layer +* Your custom queries return a graph shaped result that fits not quite to your domain model + and therefore the custom query should be accompanied by a custom mapping as well +* You have the need for interacting with the driver, i.e. for bulk loads that should not go through object mapping. + +Assume the following repository _declaration_ that basically aggregates one base repository plus 3 fragments: + +[source,java,indent=0,tabsize=4] +[[aggregating-repository]] +.A repository composed from several fragments +---- +include::../../../../src/test/java/org/springframework/data/neo4j/documentation/repositories/custom_queries/MovieRepository.java[tags=aggregating-interface] +---- + +The repository contains <> as shown in <>. + +The additional interface from which the repository extends (`DomainResults`, `NonDomainResults` and `LowlevelInteractions`) +are the fragments that addresses all the concerns above. + +=== Using complex, dynamic custom queries but still returning domain types + +The fragment `DomainResults` declares one additional method `findMoviesAlongShortestPath`: + +[source,java,indent=0,tabsize=4] +[[domain-results]] +.DomainResults fragment +---- +include::../../../../src/test/java/org/springframework/data/neo4j/documentation/repositories/custom_queries/MovieRepository.java[tags=domain-results] +---- + +This method is annotated with `@Transactional(readOnly = true)` to indicate that readers can answer it. +It cannot be derived by SDN but would need a custom query. +This custom query is provided by the one implementation of that interface. +The implementation has the same name with the suffix `Impl`: + +[source,java,indent=0,tabsize=4] +[[domain-results-impl]] +.A fragment implementation using the Neo4jTemplate +---- +include::../../../../src/test/java/org/springframework/data/neo4j/documentation/repositories/custom_queries/MovieRepository.java[tags=domain-results-impl] +---- +<.> The `Neo4jTemplate` is injected by the runtime through the constructor of `DomainResultsImpl`. No need for `@Autowired`. +<.> The Cypher-DSL is used to build a complex statement (pretty much the same as shown in <>.) + The statement can be passed directly to the template. + +The template has overloads for String-based queries as well, so you could write down the query as String as well. +The important takeaway here is: + +* The template "knows" your domain objects and maps them accordingly +* `@Query` is not the only option to define custom queries +* They can be generated in various ways +* The `@Transactional` annotation is respected + +=== Using custom queries and custom mappings + +Often times a custom query indicates custom results. +Should all of those results be mapped as `@Node`? Of course not! Many times those objects represents read commands +and are not meant to be used as write commands. +It is also not unlikely that SDN 6 cannot or want not map everything that is possible with Cypher. +It does however offer several hooks to run your own mapping: On the `Neo4jClient`. +The benefit of using the SDN 6 `Neo4jClient` over the driver: + +* The `Neo4jClient` is integrated with Springs transaction management +* It has a fluent API for binding parameters +* It has a fluent API exposing both the records and the Neo4j type system so that you can access + everything in your result to execute the mapping + +Declaring the fragment is exactly the same as before: + +[source,java,indent=0,tabsize=4] +[[non-domain-results]] +.A fragment declaring non-domain-type results +---- +include::../../../../src/test/java/org/springframework/data/neo4j/documentation/repositories/custom_queries/MovieRepository.java[tags=non-domain-results] +---- +<.> This is a made up non-domain result. A real world query result would probably look more complex. +<.> The method this fragment adds. Again, the method is annotated with Spring's `@Transactional` + +Without an implementation for that fragment, startup would fail, so here it is: + +[source,java,indent=0,tabsize=4] +[[non-domain-results-impl]] +.A fragment implementation using the Neo4jClient +---- +include::../../../../src/test/java/org/springframework/data/neo4j/documentation/repositories/custom_queries/MovieRepository.java[tags=non-domain-results-impl] +---- +<.> Here we use the `Neo4jClient`, as provided by the infrastructure. +<.> The client takes only in Strings, but the Cypher-DSL can still be used when rendering into a String +<.> Bind one single value to a named parameter. There's also an overload to bind a whole map of parameters +<.> This is the type of the result you want +<.> And finally, the `mappedBy` method, exposing one `Record` for each entry in the result plus the drivers type system if needed. + This is the API in which you hook in for your custom mappings + +The whole query runs in the context of a Spring transaction, in this case, a read-only one. + +==== Low level interactions + +Sometimes you might want to do bulk loadings from a repository or delete whole subgraphs or interact in very specific ways +with the Neo4j Java-Driver. This is possible as well. The following example shows how: + +[source,java,indent=0,tabsize=4] +[[low-level-interactions]] +.Fragments using the plain driver +---- +include::../../../../src/test/java/org/springframework/data/neo4j/documentation/repositories/custom_queries/MovieRepository.java[tags=lowlevel-interactions] +---- +<.> Work with the driver directly. As with all the examples: There is no need for `@Autowired` magic. All the fragments + are actually testable on their own. +<.> The use case is made up. Here we use a driver managed transaction deleting the whole graph and return the number of + deleted nodes and relationships + +This interaction does of course not run in a Spring transaction, as the driver does not know about Spring. + +Putting it all together, this test succeeds: + +[source,java,indent=0,tabsize=4] +[[custom-queries-test]] +.Testing the composed repository +---- +include::../../../../src/test/java/org/springframework/data/neo4j/documentation/repositories/custom_queries/CustomQueriesIT.java[tags=custom-queries-test] +---- + +As a final word: All three interfaces and implementations are picked up by Spring Data Neo4j automatically. +There is no need for further configuration. +Also, the same overall repository could have been created with only one additional fragment (the interface defining all three methods) +and one implementation. The implementation would than have had all three abstractions injected (template, client and driver). + +All of this applies of course to reactive repositories as well. +They would work with the `ReactiveNeo4jTemplate` and `ReactiveNeo4jClient` and the reactive session provided by the driver. + +If you have recurring methods for all repositories, you could swap out the default repository implementation. + +[[faq.custom-base-repositories]] +== How do I use custom Spring Data Neo4j base repositories? + +Basically the same ways as the shared Spring Data Commons documentation shows for Spring Data JPA in <>. +Only that in our case you would extend from + +[source,java,indent=0,tabsize=4] +[[custom-base-repository]] +.Custom base repository +---- +include::../../../../src/test/java/org/springframework/data/neo4j/integration/imperative/CustomBaseRepositoryIT.java[tags=custom-base-repository] +---- +<.> This signature is required by the base class. Take the `Neo4jOperations` (the actual specification of the `Neo4jTemplate`) + and the entity information and store them on an attribute if needed. + +In this example we forbid the use of the `findAll` method. +You could add methods taking in a fetch depth and run custom queries based on that depth. +One way to do this is shown in <>. + +To enable this base repository for all declared repositories enable Neo4j repositories with: `@EnableNeo4jRepositories(repositoryBaseClass = MyRepositoryImpl.class)`. + +[[faq.entities.auditing]] +== How do I audit entities? + +All Spring Data annotations are supported. +Those are + +* `org.springframework.data.annotation.CreatedBy` +* `org.springframework.data.annotation.CreatedDate` +* `org.springframework.data.annotation.LastModifiedBy` +* `org.springframework.data.annotation.LastModifiedDate` + +<> gives you a general view how to use auditing in the bigger context of Spring Data Commons. +The following listing presents every configuration option provided by Spring Data Neo4j: + +[source,java,indent=0,tabsize=4] +.Enabling and configuring Neo4j auditing +---- +include::../../../../src/test/java/org/springframework/data/neo4j/integration/imperative/AuditingIT.java[tags=faq.entities.auditing] +---- +<.> Set to true if you want the modification data to be written during creating as well +<.> Use this attribute to specify the name of the bean that provides the auditor (i.e. a user name) +<.> Use this attribute to specify the name of a bean that provides the current date. In this case + a fixed date is used as the above configuration is part of our tests + +The reactive version is basically the same apart from the fact the auditor aware bean is of type `ReactiveAuditorAware`, +so that the retrieval of an auditor is part of the reactive flow. + +In addition to those auditing mechanism you can add as many beans implementing `BeforeBindCallback` or `ReactiveBeforeBindCallback` +to the context. These beans will be picked up by Spring Data Neo4j and called in order (in case they implement `Ordered` or +are annotated with `@Order`) just before an entity is persisted. + +They can modify the entity or return a completely new one. +The following example adds one callback to the context that changes one attribute before the entity is persisted: + +[source,java,indent=0,tabsize=4] +.Modifying entities before save +---- +include::../../../../src/test/java/org/springframework/data/neo4j/integration/imperative/CallbacksIT.java[tags=faq.entities.auditing.callbacks] +---- + +No additional configuration is required. + +[[faq.find-by-example]] +== How do I use "Find by example"? + +"Find by example" is a new feature in SDN. +You instantiate an entity or use an existing one. +With this instance you create an `org.springframework.data.domain.Example`. +If your repository extends `org.springframework.data.neo4j.repository.Neo4jRepository` or `org.springframework.data.neo4j.repository.ReactiveNeo4jRepository`, you can immediately use the available `findBy` methods taking in an example, like shown in <> + +[source,java] +[[find-by-example-example]] +.findByExample in Action +---- +Example movieExample = Example.of(new MovieEntity("The Matrix", null)); +Flux movies = this.movieRepository.findAll(movieExample); + +movieExample = Example.of( + new MovieEntity("Matrix", null), + ExampleMatcher + .matchingAny() + .withMatcher( + "title", + ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.CONTAINING) + ) +); +movies = this.movieRepository.findAll(movieExample); +---- + +[[faq.spring-boot.sdn]] +== Do I need Spring Boot to use Spring Data Neo4j? + +No, you don't. +While the automatic configuration of many Spring aspects through Spring Boot takes away a lot of manual cruft and is the recommended approach for setting up new Spring projects, you don't need to have to use this. + +The following dependency is required for the solutions described above: + +[source,xml,subs="verbatim,attributes"] +---- + + {neo4jGroupId} + {artifactId} + {spring-data-neo4j-version} + +---- + +The coordinates for a Gradle setup are the same. + +To select a different database - either statically or dynamically - you can add a Bean of type `DatabaseSelectionProvider` as explained in <>. +For a reactive scenario, we provide `ReactiveDatabaseSelectionProvider`. + +[[faq.sdn-without-spring-boot]] +=== Using Spring Data Neo4j inside a Spring context without Spring Boot + +We provide two abstract configuration classes to support you in bringing in the necessary beans: +`org.springframework.data.neo4j.config.AbstractNeo4jConfig` for imperative database access and +`org.springframework.data.neo4j.config.AbstractReactiveNeo4jConfig` for the reactive version. +They are meant to be used with `@EnableNeo4jRepositories` and `@EnableReactiveNeo4jRepositories` respectively. +See <> and <> for an example usage. +Both classes require you to override `driver()` in which you are supposed to create the driver. + +To get the imperative version of the <>, the template and support for imperative repositories, use something similar as shown here: + +[source,java] +[[bootless-imperative-configuration]] +.Enabling Spring Data Neo4j infrastructure for imperative database access +---- +import org.neo4j.driver.Driver; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import org.springframework.data.neo4j.config.AbstractNeo4jConfig; +import org.springframework.data.neo4j.core.DatabaseSelectionProvider; +import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; + +@Configuration +@EnableNeo4jRepositories +@EnableTransactionManagement +class MyConfiguration extends AbstractNeo4jConfig { + + @Override @Bean + public Driver driver() { // <.> + return GraphDatabase.driver("bolt://localhost:7687", AuthTokens.basic("neo4j", "secret")); + } + + @Override + protected Collection getMappingBasePackages() { + return Collections.singletonList(Person.class.getPackage().getName()); + } + + @Override @Bean // <.> + protected DatabaseSelectionProvider databaseSelectionProvider() { + + return DatabaseSelectionProvider.createStaticDatabaseSelectionProvider("yourDatabase"); + } +} +---- +<.> The driver bean is required. +<.> This statically selects a database named `yourDatabase` and is *optional*. + +The following listing provides the reactive Neo4j client and template, enables reactive transaction management and discovers Neo4j related repositories: + +[source,java] +[[bootless-reactive-configuration]] +.Enabling Spring Data Neo4j infrastructure for reactive database access +---- +import org.neo4j.driver.Driver; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.neo4j.config.AbstractReactiveNeo4jConfig; +import org.springframework.data.neo4j.repository.config.EnableReactiveNeo4jRepositories; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@Configuration +@EnableReactiveNeo4jRepositories +@EnableTransactionManagement +class MyConfiguration extends AbstractReactiveNeo4jConfig { + + @Bean + @Override + public Driver driver() { + return GraphDatabase.driver("bolt://localhost:7687", AuthTokens.basic("neo4j", "secret")); + } + + @Override + protected Collection getMappingBasePackages() { + return Collections.singletonList(Person.class.getPackage().getName()); + } +} +---- + +[[faq.sdn-in-cdi-2.0]] +=== Using Spring Data Neo4j in a CDI 2.0 environment + +For your convenience we provide a CDI extension with `Neo4jCdiExtension`. +When run in a compatible CDI 2.0 container, it will be automatically be registered and loaded through https://docs.oracle.com/javase/tutorial/ext/basics/spi.html[Java's service loader SPI]. + +The only thing you have to bring into your application is an annotated type that produces the Neo4j Java Driver: + +[source,java] +[[cdi-driver-producer]] +.A CDI producer for the Neo4j Java Driver +---- +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Disposes; +import javax.enterprise.inject.Produces; + +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.Driver; +import org.neo4j.driver.GraphDatabase; + +public class Neo4jConfig { + + @Produces @ApplicationScoped + public Driver driver() { // <.> + return GraphDatabase + .driver("bolt://localhost:7687", AuthTokens.basic("neo4j", "secret")); + } + + public void close(@Disposes Driver driver) { + driver.close(); + } + + @Produces @Singleton + public DatabaseSelectionProvider getDatabaseSelectionProvider() { // <.> + return DatabaseSelectionProvider.createStaticDatabaseSelectionProvider("yourDatabase"); + } +} +---- +<.> Same as with plain Spring in <>, but annotated with the corresponding CDI infrastructure. +<.> This is *optional*. However, if you run a custom database selection provider, you _must_ not qualify this bean. + +If you are running in a SE Container - like the one https://weld.cdi-spec.org[Weld] provides for example, you can enable the extension like that: + +[source,java] +[[cdi-driver-producer-se]] +.Enabling the Neo4j CDI extension in a SE container +---- +import javax.enterprise.inject.se.SeContainer; +import javax.enterprise.inject.se.SeContainerInitializer; + +import org.springframework.data.neo4j.config.Neo4jCdiExtension; + +public class SomeClass { + void someMethod() { + try (SeContainer container = SeContainerInitializer.newInstance() + .disableDiscovery() + .addExtensions(Neo4jCdiExtension.class) + .addBeanClasses(YourDriverFactory.class) + .addPackages(Package.getPackage("your.domain.package")) + .initialize() + ) { + SomeRepository someRepository = container.select(SomeRepository.class).get(); + } + } +} +---- diff --git a/src/main/asciidoc/img/bacon-distance.png b/src/main/asciidoc/images/bacon-distance.png similarity index 100% rename from src/main/asciidoc/img/bacon-distance.png rename to src/main/asciidoc/images/bacon-distance.png diff --git a/src/main/asciidoc/img/custom-query.paths.png b/src/main/asciidoc/images/custom-query.paths.png similarity index 100% rename from src/main/asciidoc/img/custom-query.paths.png rename to src/main/asciidoc/images/custom-query.paths.png diff --git a/src/main/asciidoc/img/movie-graph-deep.png b/src/main/asciidoc/images/movie-graph-deep.png similarity index 100% rename from src/main/asciidoc/img/movie-graph-deep.png rename to src/main/asciidoc/images/movie-graph-deep.png diff --git a/src/main/asciidoc/img/springdatagraph.png b/src/main/asciidoc/images/springdatagraph.png similarity index 100% rename from src/main/asciidoc/img/springdatagraph.png rename to src/main/asciidoc/images/springdatagraph.png diff --git a/src/main/asciidoc/index.adoc b/src/main/asciidoc/index.adoc index 44e24e1b4..053619c59 100644 --- a/src/main/asciidoc/index.adoc +++ b/src/main/asciidoc/index.adoc @@ -62,12 +62,16 @@ include::testing/index.adoc[] include::{spring-data-commons-docs}/auditing.adoc[leveloffset=+1] -include::faq/index.adoc[] +[[faq]] += Frequently Asked Questions -include::appendix/index.adoc[] +:numbered!: +include::faq/index.adoc[] [[appendix]] = Appendix + :numbered!: +include::appendix/index.adoc[leveloffset=+1] include::{spring-data-commons-docs}/repository-query-keywords-reference.adoc[leveloffset=+1] include::{spring-data-commons-docs}/repository-query-return-types-reference.adoc[leveloffset=+1] diff --git a/src/main/asciidoc/introduction-and-preface/building-blocks.adoc b/src/main/asciidoc/introduction-and-preface/building-blocks.adoc index 7a1389fa8..54b55069c 100644 --- a/src/main/asciidoc/introduction-and-preface/building-blocks.adoc +++ b/src/main/asciidoc/introduction-and-preface/building-blocks.adoc @@ -1,5 +1,5 @@ [[building-blocks]] -= Building blocks += Building blocks of Spring Data Neo4j == Overview diff --git a/src/main/asciidoc/introduction-and-preface/index.adoc b/src/main/asciidoc/introduction-and-preface/index.adoc index fe4ea36f3..dee6e0e14 100644 --- a/src/main/asciidoc/introduction-and-preface/index.adoc +++ b/src/main/asciidoc/introduction-and-preface/index.adoc @@ -1,5 +1,9 @@ include::introduction.adoc[leveloffset=+1] -include::preface.adoc[leveloffset=+1] +include::preface-neo4j.adoc[leveloffset=+1] + +include::preface-sd.adoc[leveloffset=+1] + +include::preface-sdn.adoc[leveloffset=+1] include::building-blocks.adoc[leveloffset=+1] diff --git a/src/main/asciidoc/introduction-and-preface/introduction.adoc b/src/main/asciidoc/introduction-and-preface/introduction.adoc index a2032b8db..884ef67d8 100644 --- a/src/main/asciidoc/introduction-and-preface/introduction.adoc +++ b/src/main/asciidoc/introduction-and-preface/introduction.adoc @@ -1,6 +1,22 @@ [[introduction]] = Your way through this document +This documentation tries to bridge between a broad spectrum of possible users: + +* People new to all the Spring ecosystem, including Spring Framework, Spring Data, the concrete module (in this case Spring Data Neo4j) +and Neo4j. +* Experienced Neo4j developers that are new to Spring Data and want to make best use of their Neo4j knowledge but are unfamiliar +with declarative transactions for example and how to incorporate the latter with Neo4j cluster requirements. +* Experienced Spring Data developers who are new to this specific module and Neo4j and need to learn how the building blocks +interact together. While the programming paradigm of this module is very much in line with Spring Data JDBC, Mongo and others, +the query language (Cypher), transactional and clustering behaviour is different and can't be abstracted away. + +Here's how we address those different needs: + +A lot of Neo4j specific questions can be found in the <>. These questions are +particular relevant for people who well aware of Neo4j specific requirements and want to know how to address them +with Spring Data Neo4j. + If you are already familiar with the core concepts of Spring Data, head straight to <>. This chapter will walk you through different options of configuring an application to connect to a Neo4j instance and how to model your domain. @@ -12,7 +28,10 @@ Choices are Neo4j repositories, the Neo4j Template or on a lower level, the Neo4 All of them are available in a reactive fashion as well. Apart from the paging mechanism, all the features of standard repositories are available in the reactive variant. -You will find the building blocks in the next <>. +If you come from older versions of Spring Data Neo4j - which are usually abbreviated SDN+OGM or SDN5 - +you will most likely be interested in the <> and especially in the relationship +<>. In the same chapter, you will find out about the +<> of SDN6. To learn more about the general concepts of repositories, head over to <>. diff --git a/src/main/asciidoc/introduction-and-preface/preface-neo4j.adoc b/src/main/asciidoc/introduction-and-preface/preface-neo4j.adoc new file mode 100644 index 000000000..304fd1c60 --- /dev/null +++ b/src/main/asciidoc/introduction-and-preface/preface-neo4j.adoc @@ -0,0 +1,25 @@ +[[preface.neo4j]] += Introducing Neo4j + +A graph database is a storage engine that specializes in storing and retrieving vast networks of information. +It efficiently stores data as nodes with relationships to other or even the same nodes, thus allowing high-performance retrieval and querying of those structures. +Properties can be added to both nodes and relationships. +Nodes can be labelled by zero or more labels, relationships are always directed and named. + +Graph databases are well suited for storing most kinds of domain models. +In almost all domains, there are certain things connected to other things. +In most other modeling approaches, the relationships between things are reduced to a single link without identity and attributes. +Graph databases allow to keep the rich relationships that originate from the domain equally well-represented in the database without resorting to also modeling the relationships as "things". +There is very little "impedance mismatch" when putting real-life domains into a graph database. + +https://neo4j.com/[Neo4j] is an open source NoSQL graph database. +It is a fully transactional database (ACID) that stores data structured as graphs consisting of nodes, connected by relationships. +Inspired by the structure of the real world, it allows for high query performance on complex data, while remaining intuitive and simple for the developer. + +The starting point for learning about Neo4j is https://neo4j.com/[neo4j.com]. +Here is a list of useful resources: + +* The https://neo4j.com/docs/[Neo4j documentation] introduces Neo4j and contains links to getting started guides, reference documentation and tutorials. +* The https://neo4j.com/sandbox/[online sandbox] provides a convenient way to interact with a Neo4j instance in combination with the online https://neo4j.com/developer/get-started/[tutorial]. +* Neo4j https://neo4j.com/developer/java/[Java Bolt Driver] +* Several https://neo4j.com/books/[books] available for purchase and https://www.youtube.com/neo4j[videos] to watch. diff --git a/src/main/asciidoc/introduction-and-preface/preface-sd.adoc b/src/main/asciidoc/introduction-and-preface/preface-sd.adoc new file mode 100644 index 000000000..fb10b81bc --- /dev/null +++ b/src/main/asciidoc/introduction-and-preface/preface-sd.adoc @@ -0,0 +1,17 @@ +[[preface.spring-data]] += Introducing Spring Data + +Spring Data uses Spring Framework's {spring-framework-ref}/core.html[core] functionality, such as the {spring-framework-ref}/core.html#beans[IoC] container, +{spring-framework-ref}/core.html#core-convert[type conversion system], +{spring-framework-ref}/core.html#expressions[expression language], +{spring-framework-ref}/integration.html#jmx[JMX integration], and portable {spring-framework-ref}/data-access.html#dao-exceptions[DAO exception hierarchy]. +While it is not necessary to know all the Spring APIs, understanding the concepts behind them is. +At a minimum, the idea behind IoC should be familiar. + +To learn more about Spring, you can refer to the comprehensive documentation that explains in detail the Spring Framework. +There are a lot of articles, blog entries and books on the matter - take a look at the Spring Framework https://spring.io/docs[home page ] for more information. + +The beauty of Spring Data is that it applies the same programming model to a variety of different stores, such as JPA, JDBC +Mongo and others. For that reason, parts of the general Spring Data documentations are included in this document, especially the +general chapter about <>. Make sure to have a look at that if you haven't +worked with a Spring Data module in the past. diff --git a/src/main/asciidoc/introduction-and-preface/preface-sdn.adoc b/src/main/asciidoc/introduction-and-preface/preface-sdn.adoc new file mode 100644 index 000000000..9de05c4c1 --- /dev/null +++ b/src/main/asciidoc/introduction-and-preface/preface-sdn.adoc @@ -0,0 +1,46 @@ +[[preface.sdn]] += Introducing Spring Data Neo4j + +Spring Data Neo4j or in short SDN is the next-generation https://spring.io/projects/spring-data[Spring Data] module, created and maintained by https://neo4j.com[Neo4j, Inc.] in close collaboration with https://www.vmware.com/[VMware's] Spring Data Team. +It supports all officially supported releases of Neo4j, including Neo4j AuraDB. +The Spring Data Neo4j project applies aforementioned Spring Data concepts to the development of solutions using the Neo4j graph data store. + +SDN relies completely on the https://github.com/neo4j/neo4j-java-driver[Neo4j Java Driver], without introducing another "driver" or "transport" layer between the mapping framework and the driver. The Neo4j Java Driver - sometimes dubbed Bolt or the Bolt driver - is used as a protocol much like JDBC is with relational databases. + +SDN is an Object-Graph-Mapping (OGM) library. +An OGM maps nodes and relationships in the graph to objects and references in a domain model. +Object instances are mapped to nodes while object references are mapped using relationships, or serialized to properties (e.g. references to a Date). +JVM primitives are mapped to node or relationship properties. +An OGM abstracts the database and provides a convenient way to persist your domain model in the graph and query it without having to use low level drivers directly. +It also provides the flexibility to the developer to supply custom queries where the queries generated by SDN are insufficient. + +SDN is the *official successor* to prior SDN version 5, to which this documentation refers as SDN+OGM. +SDN version 5 used a separate object mapping framework, much in the way Spring Data JPA relates to JPA. +That separate layer aka Neo4j-OGM (Neo4j Object Graph Mapper) is now contained in this module itself. +Spring Data Neo4j itself is an object mapper, dedicated to be used in Spring and Spring Boot applications and in some supported Jakarta EE environments. +It does not require or support a separate implementation of an object mapper. + +Noteworthy features that differentiate the current SDN version from prior SDN+OGM are + +* SDN is a complete OGM on its own +* Full support for immutable entities and thus full support for Kotlin's data classes +* Full support for the reactive programming model in the Spring Framework itself and Spring Data +* Neo4j client and reactive client feature, resurrecting the idea of a template over the plain driver, easing database access + +We provide _repositories_ as a high-level abstraction for storing and querying documents as well as templates and clients for generic domain access or generic query execution. +All of them are integrated with Spring's application transactions. + +The core functionality of the Neo4j support can be used directly, through either the `Neo4jClient` or the `Neo4jTemplate` or the reactive variants thereof. +All of them provide integration with Spring's application level transactions. +On a lower level, you can grab the Bolt driver instance, but than you have to manage your own transactions in these cases. + +NOTE: You still can use Neo4j-OGM, even in modern Spring Boot applications. + But you *cannot* use it with SDN 6. + If you tried you would have two different sets of entities in two different - and unrelated - persistence context. + Hence, if you want to stick to Neo4j-OGM 3.2.x, you would use the Java driver instantiated by Spring Boot and pass it onto a Neo4j-OGM session. + Neo4j-OGM 3.2.x is still supported, and we recommend its use in frameworks such as Quarkus. + In a Spring Boot application however your primary choice should be SDN 6. + +Please make sure you read the <> where we address many reoccurring questions about our mapping decisions but also how interaction with Neo4j cluster instances such as https://neo4j.com/cloud/platform/aura-graph-database/[Neo4j AuraDB] and on-premise cluster deployments can be significantly improved. + +Concepts that are important to understand are Neo4j Bookmarks, https://medium.com/neo4j/try-and-then-retry-there-can-be-failure-30bf336383da[the potential need] for incorporating a proper retry mechanism such as https://github.com/spring-projects/spring-retry[Spring Retry] or https://github.com/resilience4j/resilience4j[Resilience4j] (we recommend the latter, as this knowledge is applicable outside Spring, too) and the importance of read-only vs write queries in the context of Neo4j cluster. diff --git a/src/main/asciidoc/introduction-and-preface/preface.adoc b/src/main/asciidoc/introduction-and-preface/preface.adoc deleted file mode 100644 index 35c4ae56a..000000000 --- a/src/main/asciidoc/introduction-and-preface/preface.adoc +++ /dev/null @@ -1,148 +0,0 @@ -[[preface.nosql]] -= NoSQL and Graph databases - -A graph database is a storage engine that specializes in storing and retrieving vast networks of information. -It efficiently stores data as nodes with relationships to other or even the same nodes, thus allowing high-performance retrieval and querying of those structures. -Properties can be added to both nodes and relationships. -Nodes can be labelled by zero or more labels, relationships are always directed and named. - -Graph databases are well suited for storing most kinds of domain models. -In almost all domains, there are certain things connected to other things. -In most other modeling approaches, the relationships between things are reduced to a single link without identity and attributes. -Graph databases allow to keep the rich relationships that originate from the domain equally well-represented in the database without resorting to also modeling the relationships as "things". -There is very little "impedance mismatch" when putting real-life domains into a graph database. - -[[preface.nosql.neo4j]] -== Introducing Neo4j - -https://neo4j.com/[Neo4j] is an open source NoSQL graph database. -It is a fully transactional database (ACID) that stores data structured as graphs consisting of nodes, connected by relationships. -Inspired by the structure of the real world, it allows for high query performance on complex data, while remaining intuitive and simple for the developer. - -The starting point for learning about Neo4j is https://neo4j.com/[neo4j.com]. -Here is a list of useful resources: - -* The https://neo4j.com/docs/[Neo4j documentation] introduces Neo4j and contains links to getting started guides, reference documentation and tutorials. -* The https://neo4j.com/sandbox/[online sandbox] provides a convenient way to interact with a Neo4j instance in combination with the online https://neo4j.com/developer/get-started/[tutorial]. -* Neo4j https://neo4j.com/developer/java/[Java Bolt Driver] -* Several https://neo4j.com/books/[books] available for purchase and https://www.youtube.com/neo4j[videos] to watch. - -[[preface.spring-data]] -== Spring and Spring Data - -Spring Data uses Spring Framework's {spring-framework-ref}/core.html[core] functionality, such as the {spring-framework-ref}/core.html#beans[IoC] container, -{spring-framework-ref}/core.html#core-convert[type conversion system], -{spring-framework-ref}/core.html#expressions[expression language], -{spring-framework-ref}/integration.html#jmx[JMX integration], and portable {spring-framework-ref}/data-access.html#dao-exceptions[DAO exception hierarchy]. -While it is not necessary to know all the Spring APIs, understanding the concepts behind them is. -At a minimum, the idea behind IoC should be familiar. - -The Spring Data Neo4j project applies Spring Data concepts to the development of solutions using the Neo4j graph data store. -We provide _repositories_ as a high-level abstraction for storing and querying documents as well as templates and clients for generic domain access or generic query execution. -All of them are integrated with Spring's application transactions. - -The core functionality of the Neo4j support can be used directly, through either the `Neo4jClient` or the `Neo4jTemplate` or the reactive variants thereof. -All of them provide integration with Spring's application level transactions. -On a lower level, you can grab the Bolt driver instance, but than you have to manage your own transactions. - -To learn more about Spring, you can refer to the comprehensive documentation that explains in detail the Spring Framework. -There are a lot of articles, blog entries and books on the matter - take a look at the Spring Framework https://spring.io/docs[home page ] for more information. - -This documentation tries to bridge between a broad spectrum of possible users: - -* People new to all the Spring ecosystem, including Spring Framework, Spring Data, the concrete module (in this case Spring Data Neo4j) - and Neo4j. -* Experienced Neo4j developers that are new to Spring Data and want to make best use of their Neo4j knowledge but are unfamiliar - with declarative transactions for example and how to incorporate the latter with Neo4j cluster requirements. -* Experienced Spring Data developers who are new to this specific module and Neo4j and need to learn how the building blocks - interact together. While the programming paradigm of this module is very much in line with Spring Data JDBC, Mongo and others, - the query language (Cypher), transactional and clustering behaviour is different and can't be abstracted away. - -We decided to move a lot of Neo4j specific questions into the <>. - - -[[what-is-sdn]] -== What is Spring Data Neo4j - -The current Spring Data Neo4j is the successor to Spring Data Neo4j + Neo4j-OGM. -The separate layer of Neo4j-OGM (Neo4j Object Graph Mapper) has been replaced by Spring infrastructure, but the basic concepts of an Object Graph Mapper (OGM) still apply. - -An OGM maps nodes and relationships in the graph to objects and references in a domain model. -Object instances are mapped to nodes while object references are mapped using relationships, or serialized to properties (e.g. references to a Date). -JVM primitives are mapped to node or relationship properties. -An OGM abstracts the database and provides a convenient way to persist your domain model in the graph and query it without having to use low level drivers directly. -It also provides the flexibility to the developer to supply custom queries where the queries generated by SDN are insufficient. - -TIP: Please make sure you read the <> where we address many reoccurring questions about our - mapping decisions but also how interaction with Neo4j cluster instances such as https://neo4j.com/cloud/platform/aura-graph-database/[Neo4j AuraDB] - and on-premise cluster deployments can be significantly improved. - + - Concepts that are important to understand are Neo4j Bookmarks, https://medium.com/neo4j/try-and-then-retry-there-can-be-failure-30bf336383da[the potential need] - for incorporating a proper retry mechanism such as https://github.com/spring-projects/spring-retry[Spring Retry] or - https://github.com/resilience4j/resilience4j[Resilience4j] (we recommend the latter, as this knowledge is applicable outside - Spring, too) and the importance of read-only vs write queries in the context of Neo4j cluster. - - -[[what-is-in-the-box-sdn]] -=== What's in the box? - -Spring Data Neo4j or in short SDN is a next-generation https://spring.io/projects/spring-data[Spring Data] module, created and maintained by https://neo4j.com[Neo4j, Inc.] in close collaboration with https://www.vmware.com/[VMware's] Spring Data Team. - -SDN relies completely on the https://github.com/neo4j/neo4j-java-driver[Neo4j Java Driver], without introducing another "driver" or "transport" layer between the mapping framework and the driver. -The Neo4j Java Driver - sometimes dubbed Bolt or the Bolt driver - is used as a protocol much like JDBC is with relational databases. - -Noteworthy features that differentiate the new SDN from Spring Data Neo4j + OGM are - -* Full support for immutable entities and thus full support for Kotlin's data classes -* Full support for the reactive programming model in the Spring Framework itself and Spring Data -* Brand new Neo4j client and reactive client feature, resurrecting the idea of a template over the plain driver, easing database access - -[[why-should-you-favor-sdn]] -=== Why should I use SDN in favor of SDN+OGM - -SDN has several features not present in SDN+OGM, notably - -* Full support for Springs reactive story, including reactive transaction -* Full support for https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#query-by-example[Query By Example] -* Full support for fully immutable entities -* Support for all modifiers and variations of derived finder methods, including spatial queries - -[[sdn-related-to-ogm]] -=== How does SDN relate to Neo4j-OGM? - -https://neo4j.com/docs/ogm-manual/current/[Neo4j-OGM] is an Object Graph Mapping library, which is mainly used by previous versions of Spring Data Neo4j as its backend for the heavy lifting of mapping nodes and relationships into domain object. -The current SDN *does not need* and *does not support* Neo4j-OGM. -SDN uses Spring Data's mapping context exclusively for scanning classes and building the meta model. - -While this pins SDN to the Spring ecosystem, it has several advantages, among them the smaller footprint regarding CPU and memory usage and especially, all the features of Spring's mapping context. - -[[sdn-and-http-support]] -=== Does SDN support connections over HTTP to Neo4j? - -No. - -[[sdn-and-embedded-support]] -=== Does SDN support embedded Neo4j? - -Embedded Neo4j has multiple facets to it: - -[[sdn-and-embedded-instances-support]] -==== Does SDN provide an embedded instance for your application? - -No. - -[[sdn-interact-with-embedded-instances]] -==== Does SDN interact directly with an embedded instance? - -No. -An embedded database is usually represented by an instance of `org.neo4j.graphdb.GraphDatabaseService` and has no Bolt connector out of the box. - -SDN can however work very much with Neo4j's test harness, the test harness is specially meant to be a drop-in replacement for the real database. -Support for both Neo4j 3.5 and 4.x test harness is implemented via link:{java-driver-starter-href}[the Spring Boot starter for the driver]. -Have a look at the corresponding module `org.neo4j.driver:neo4j-java-driver-test-harness-spring-boot-autoconfigure`. - -[[sdn-without-spring-boot]] -==== Can I use SDN without Spring Boot? - -Yes, see our `README`. -We provide `org.springframework.data.neo4j.config.AbstractNeo4jConfig` and `org.springframework.data.neo4j.config.AbstractReactiveNeo4jConfig` for that purpose. diff --git a/src/main/asciidoc/object-mapping/mapping.adoc b/src/main/asciidoc/object-mapping/mapping.adoc index cfe9a9eac..d4d21809d 100644 --- a/src/main/asciidoc/object-mapping/mapping.adoc +++ b/src/main/asciidoc/object-mapping/mapping.adoc @@ -288,7 +288,7 @@ On the other hand, we want to be able to pull all people from the database witho Please consider your application's use case before you try to map every relationship in your database in every direction. While you can do this, you may end up rebuilding a graph database inside your object graph and this is not the intention of a mapping framework. If you have to model your circular or bidirectional domain and don't want to fetch the whole graph, -you can define a fine-grained description of the data that you want to fetch by using <>. +you can define a fine-grained description of the data that you want to fetch by using <>. [[mapping.id-handling]] == Handling and provisioning of unique IDs diff --git a/src/main/asciidoc/object-mapping/projections.adoc b/src/main/asciidoc/object-mapping/projections.adoc index 41d01d858..83882591d 100644 --- a/src/main/asciidoc/object-mapping/projections.adoc +++ b/src/main/asciidoc/object-mapping/projections.adoc @@ -1,4 +1,4 @@ -[[projections.sdn]] +[[projections]] = Projections [[projections.sdn.general-remarks]]