@@ -480,9 +480,129 @@ public interface MyPersonRepository extends Neo4jRepository<Person, Long> {
480480 Also, the `Pageable` should be unsorted and you should provide a stable order.
481481 We won't use the sorting information from the pageable.
482482<.> This method returns a page. A page knows about the exact number of total pages.
483- Therefore you must specify an additional count query.
483+ Therefore, you must specify an additional count query.
484484 All other restrictions from the second method apply.
485485
486+ [[faq.path-mapping]]
487+ == Can I map named paths?
488+
489+ A series of connected nodes and relationships is called a "path" in Neo4j.
490+ Cypher allows paths to be named using an identifer, as exemplified by:
491+
492+ [source,cypher]
493+ ----
494+ p = (a)-[*3..5]->(b)
495+ ----
496+
497+ or as in the infamous Movie graph, that includes the following path (in that case, one of the shortest path between two actors):
498+
499+ [[bacon-distance]]
500+ [source,cypher]
501+ .The "Bacon" distance
502+ ----
503+ MATCH p=shortestPath((bacon:Person {name:"Kevin Bacon"})-[*]-(meg:Person {name:"Meg Ryan"}))
504+ RETURN p
505+ ----
506+
507+ Which looks like this:
508+
509+ image::bacon-distance.png[]
510+
511+ We find 3 nodes labeled `Person` and 2 nodes labeled `Movie`. Both can be mapped with a custom queury.
512+ Assume there's a node entity for both `Person` and `Movie` as well as `Actor` taking care of the relationship:
513+
514+
515+ [source,java]
516+ ."Standard" movie graph domain model
517+ ----
518+ @Node
519+ public final class Person {
520+
521+ @Id @GeneratedValue
522+ private final Long id;
523+
524+ private final String name;
525+
526+ private Integer born;
527+
528+ @Relationship("REVIEWED")
529+ private List<Movie> reviewed = new ArrayList<>();
530+ }
531+
532+ @RelationshipProperties
533+ public final class Actor {
534+
535+ @Id @GeneratedValue
536+ private final Long id;
537+
538+ @TargetNode
539+ private final Person person;
540+
541+ private final List<String> roles;
542+ }
543+
544+ @Node
545+ public final class Movie {
546+
547+ @Id
548+ private final String title;
549+
550+ @Property("tagline")
551+ private final String description;
552+
553+ @Relationship(value = "ACTED_IN", direction = Direction.INCOMING)
554+ private final List<Actor> actors;
555+ }
556+ ----
557+
558+ When using a query as shown in <<bacon-distance>> for a domain class of type `Person` like this
559+
560+ [source,java]
561+ ----
562+ interface PeopleRepository extends Neo4jRepository<Person, Long> {
563+ @Query(""
564+ + "MATCH p=shortestPath((bacon:Person {name: $person1})-[*]-(meg:Person {name: $person2}))\n"
565+ + "RETURN p"
566+ )
567+ List<Person> findAllOnShortestPathBetween(@Param("person1") String person1, @Param("person2") String person2);
568+ }
569+ ----
570+
571+ it will retrieve all people from the path and map them.
572+ If there are relationship types on the path like `REVIEWED` that are also present on the domain, these
573+ will be filled accordingly from the path.
574+
575+ WARNING: Take special care when you use nodes hydrated from a path based query to save data.
576+ If not all relationships are hydrated, data will be lost.
577+
578+ The other way round works as well. The same query can be used with the `Movie` entity.
579+ It then will only populate movies.
580+ The following listing shows how todo this as well as how the query can be enriched with additional data
581+ not found on the path. That data is used to correctly populate the missing relationships (in that case, all the actors)
582+
583+ [source,java]
584+ ----
585+ interface MovieRepository extends Neo4jRepository<Movie, String> {
586+
587+ @Query(""
588+ + "MATCH p=shortestPath(\n"
589+ + "(bacon:Person {name: $person1})-[*]-(meg:Person {name: $person2}))\n"
590+ + "WITH p, [n IN nodes(p) WHERE n:Movie] AS x\n"
591+ + "UNWIND x AS m\n"
592+ + "MATCH (m) <-[r:DIRECTED]-(d:Person)\n"
593+ + "RETURN p, collect(r), collect(d)"
594+ )
595+ List<Movie> findAllOnShortestPathBetween(@Param("person1") String person1, @Param("person2") String person2);
596+ }
597+ ----
598+
599+ The query returns the path plus all relationships and related nodes collected so that the movie entities are fully hydrated.
600+
601+ The path mapping works for single paths as well for multiple records of paths (which are returned by the `allShortestPath` function.)
602+
603+ TIP: Named paths can be used efficiently to populate and return more than just a root node, see <<custom-query.paths>>.
604+
605+
486606[[faq.custom-queries-and-custom-mappings]]
487607== Is `@Query` the only way to use custom queries?
488608
@@ -632,7 +752,7 @@ and one implementation. The implementation would than have had all three abstrac
632752All of this applies of course to reactive repositories as well.
633753They would work with the `ReactiveNeo4jTemplate` and `ReactiveNeo4jClient` and the reactive session provided by the driver.
634754
635- If you have recuring methods for all repositories, you could swap out the default repository implementation.
755+ If you have recurring methods for all repositories, you could swap out the default repository implementation.
636756
637757[[faq.custom-base-repositories]]
638758== How do I use custom Spring Data Neo4j base repositories?
@@ -666,6 +786,37 @@ Those are
666786* `org.springframework.data.annotation.LastModifiedBy`
667787* `org.springframework.data.annotation.LastModifiedDate`
668788
789+ <<auditing>> gives you a general view how to use auditing in the bigger context of Spring Data Commons.
790+ The following listing presents every configuration option provided by Spring Data Neo4j:
791+
792+ [source,java,indent=0,tabsize=4]
793+ .Enabling and configuring Neo4j auditing
794+ ----
795+ include::../../../../src/test/java/org/springframework/data/neo4j/integration/imperative/AuditingIT.java[tags=faq.entities.auditing]
796+ ----
797+ <.> Set to true if you want the modification data to be written during creating as well
798+ <.> Use this attribute to specify the name of the bean that provides the auditor (i.e. a user name)
799+ <.> Use this attribute to specify the name of a bean that provides the current date. In this case
800+ a fixed date is used as the above configuration is part of our tests
801+
802+ The reactive version is basically the same apart from the fact the auditor aware bean is of type `ReactiveAuditorAware`,
803+ so that the retrieval of an auditor is part of the reactive flow.
804+
805+ In addition to those auditing mechanism you can add as many beans implementing `BeforeBindCallback<T>` or `ReactiveBeforeBindCallback<T>`
806+ to the context. These beans will be picked up by Spring Data Neo4j and called in order (in case they implement `Ordered` or
807+ are annotated with `@Order`) just before an entity is persisted.
808+
809+ They can modify the entity or return a completely new one.
810+ The following example adds one callback to the context that changes one attribute before the entity is persisted:
811+
812+ [source,java,indent=0,tabsize=4]
813+ .Modifying entities before save
814+ ----
815+ include::../../../../src/test/java/org/springframework/data/neo4j/integration/imperative/CallbacksIT.java[tags=faq.entities.auditing.callbacks]
816+ ----
817+
818+ No additional configuration is required.
819+
669820[[faq.find-by-example]]
670821== How do I use "Find by example"?
671822
@@ -693,125 +844,6 @@ movieExample = Example.of(
693844movies = this.movieRepository.findAll(movieExample);
694845----
695846
696- [[faq.path-mapping]]
697- == Can I map named paths?
698-
699- A series of connected nodes and relationships is called a "path" in Neo4j.
700- Cypher allows paths to be named using an identifer, as exemplified by:
701-
702- [source,cypher]
703- ----
704- p = (a)-[*3..5]->(b)
705- ----
706-
707- or as in the infamous Movie graph, that includes the following path (in that case, one of the shortest path between two actors):
708-
709- [[bacon-distance]]
710- [source,cypher]
711- .The "Bacon" distance
712- ----
713- MATCH p=shortestPath((bacon:Person {name:"Kevin Bacon"})-[*]-(meg:Person {name:"Meg Ryan"}))
714- RETURN p
715- ----
716-
717- Which looks like this:
718-
719- image::bacon-distance.png[]
720-
721- We find 3 nodes labeled `Person` and 2 nodes labeled `Movie`. Both can be mapped with a custom queury.
722- Assume there's a node entity for both `Person` and `Movie` as well as `Actor` taking care of the relationship:
723-
724-
725- [source,java]
726- ."Standard" movie graph domain model
727- ----
728- @Node
729- public final class Person {
730-
731- @Id @GeneratedValue
732- private final Long id;
733-
734- private final String name;
735-
736- private Integer born;
737-
738- @Relationship("REVIEWED")
739- private List<Movie> reviewed = new ArrayList<>();
740- }
741-
742- @RelationshipProperties
743- public final class Actor {
744-
745- @Id @GeneratedValue
746- private final Long id;
747-
748- @TargetNode
749- private final Person person;
750-
751- private final List<String> roles;
752- }
753-
754- @Node
755- public final class Movie {
756-
757- @Id
758- private final String title;
759-
760- @Property("tagline")
761- private final String description;
762-
763- @Relationship(value = "ACTED_IN", direction = Direction.INCOMING)
764- private final List<Actor> actors;
765- }
766- ----
767-
768- When using a query as shown in <<bacon-distance>> for a domain class of type `Person` like this
769-
770- [source,java]
771- ----
772- interface PeopleRepository extends Neo4jRepository<Person, Long> {
773- @Query(""
774- + "MATCH p=shortestPath((bacon:Person {name: $person1})-[*]-(meg:Person {name: $person2}))\n"
775- + "RETURN p"
776- )
777- List<Person> findAllOnShortestPathBetween(@Param("person1") String person1, @Param("person2") String person2);
778- }
779- ----
780-
781- it will retrieve all people from the path and map them.
782- If there are relationship types on the path like `REVIEWED` that are also present on the domain, these
783- will be filled accordingly from the path.
784-
785- WARNING: Take special care when you use nodes hydrated from a path based query to save data.
786- If not all relationships are hydrated, data will be lost.
787-
788- The other way round works as well. The same query can be used with the `Movie` entity.
789- It then will only populate movies.
790- The following listing shows how todo this as well as how the query can be enriched with additional data
791- not found on the path. That data is used to correctly populate the missing relationships (in that case, all the actors)
792-
793- [source,java]
794- ----
795- interface MovieRepository extends Neo4jRepository<Movie, String> {
796-
797- @Query(""
798- + "MATCH p=shortestPath(\n"
799- + "(bacon:Person {name: $person1})-[*]-(meg:Person {name: $person2}))\n"
800- + "WITH p, [n IN nodes(p) WHERE n:Movie] AS x\n"
801- + "UNWIND x AS m\n"
802- + "MATCH (m) <-[r:DIRECTED]-(d:Person)\n"
803- + "RETURN p, collect(r), collect(d)"
804- )
805- List<Movie> findAllOnShortestPathBetween(@Param("person1") String person1, @Param("person2") String person2);
806- }
807- ----
808-
809- The query returns the path plus all relationships and related nodes collected so that the movie entities are fully hydrated.
810-
811- The path mapping works for single paths as well for multiple records of paths (which are returned by the `allShortestPath` function.)
812-
813- TIP: Named paths can be used efficiently to populate and return more than just a root node, see <<custom-query.paths>>.
814-
815847[[faq.spring-boot.sdn]]
816848== Do I need Spring Boot to use Spring Data Neo4j?
817849
0 commit comments