Spring Data Neo4j is divided into two main components.
1. OGM SUpport
2. Spring Data Repository Support
3. maven
3.1 SDN connects neo4j via BOLT driver
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j</artifactId>
</dependency>
<!-- add this dependency if you want to use the embedded driver -->
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-ogm-embedded-driver</artifactId>
</dependency>
<!-- add this dependency if you want to use the HTTP driver -->
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-ogm-http-driver</artifactId>
</dependency>
</dependency>
3.2 SDN supports JavaConfig only
@Configuration
@EnableNeo4jRepositories(basePackages = "org.neo4j.example.repository")
@EnableTransactionManagement
public class MyConfiguration {
@Bean
public SessionFactory sessionFactory() {
// with domain entity base package(s)
return new SessionFactory(configuration(), "org.neo4j.example.domain");
}
@Bean
public org.neo4j.ogm.config.Configuration configuration() {
ConfigurationSource properties = new ClasspathConfigurationSource("ogm.properties");
org.neo4j.ogm.config.Configuration configuration = new org.neo4j.ogm.config.Configuration.Builder(properties).build();
return configuration;
}
@Bean
public Neo4jTransactionManager transactionManager() {
return new Neo4jTransactionManager(sessionFactory());
}
}
Change these default values by providing a file at the root of the class path by ogm.properties or passing in an org.neo4j.ogm.config.Configuration object. The last infrastructure component declared here is Neo4j Transaction Manager. We finally activated the Spring Data Neo4j repository with the @EnableNeo4j Repositories annotation. If the underlying package is not configured, it will use the package where the configuration class is located.
Note that you must explicitly activate @EnableTransaction Management to get annotation-based configuration in normal work, Neo4jTransaction Manager and use the bean name to define this instance transaction Manager. The above example assumes that you are using component scanning.
To make your query method transactional, simply use @Transactional on your defined repository interface
- Transactions : @Transactional
- Neo4J Repositories
@Repository
public interface PersonRepository extends Neo4jRepository<Person, Long> {}
public class MySpringBean {
@Autowired
private PersonRepository repo;
...
}
// then you can use the repository as you would any other object
Person michael = repo.save(new Person("Michael", 36));
Optional<Person> dave = repo.findById(123);
long numberOfPeople = repo.count();
- Annotated queries
@Query("MATCH (:Actor {name:{name}})-[:ACTED_IN]->(m:Movie) return m")
public interface MovieRepository extends Neo4jRepository<Movie, Long> {
// returns the node with id equal to idOfMovie parameter
@Query("MATCH (n) WHERE id(n)={0} RETURN n")
Movie getMovieFromId(Integer idOfMovie);
// returns the nodes which have a title according to the movieTitle parameter
@Query("MATCH (movie:Movie {title={0}}) RETURN movie")
Movie getMovieFromTitle(String movieTitle);
// same with optional result
@Query("MATCH (movie:Movie {title={0}}) RETURN movie")
Optional<Movie> getMovieFromTitle(String movieTitle);
// returns a Page of Actors that have a ACTS_IN relationship to the movie node with the title equal to movieTitle parameter.
@Query(value = "MATCH (movie:Movie {title={0}})<-[:ACTS_IN]-(actor) RETURN actor", countQuery= "MATCH (movie:Movie {title={0}})<-[:ACTS_IN]-(actor) RETURN count(actor)")
Page<Actor> getActorsThatActInMovieFromTitle(String movieTitle, PageRequest page);
// returns a Page of Actors that have a ACTS_IN relationship to the movie node with the title equal to movieTitle parameter with an accurate total count
@Query(value = "MATCH (movie:Movie {title={0}})<-[:ACTS_IN]-(actor) RETURN actor", countQuery = "MATCH (movie:Movie {title={0}})<-[:ACTS_IN]-(actor) RETURN count(*)")
Page<Actor> getActorsThatActInMovieFromTitle(String movieTitle, Pageable page);
// returns a Slice of Actors that have a ACTS_IN relationship to the movie node with the title equal to movieTitle parameter.
@Query("MATCH (movie:Movie {title={0}})<-[:ACTS_IN]-(actor) RETURN actor")
Slice<Actor> getActorsThatActInMovieFromTitle(String movieTitle, Pageable page);
// returns users who rated a movie (movie parameter) higher than rating (rating parameter)
@Query("MATCH (movie:Movie)<-[r:RATED]-(user) " +
"WHERE id(movie)={movieId} AND r.stars > {rating} " +
"RETURN user")
Iterable<User> getUsersWhoRatedMovieFromTitle(@Param("movieId") Movie movie, @Param("rating") Integer rating);
// returns users who rated a movie based on movie title (movieTitle parameter) higher than rating (rating parameter)
@Query("MATCH (movie:Movie {title:{0}})<-[r:RATED]-(user) " +
"WHERE r.stars > {1} " +
"RETURN user")
Iterable<User> getUsersWhoRatedMovieFromTitle(String movieTitle, Integer rating);
@Query(value = "MATCH (movie:Movie) RETURN movie;")
Stream<Movie> getAllMovies();
}
- Queries derived from finder-method names
Some examples of methods and corresponding Cypher queries of a PersonRepository
public interface PersonRepository extends Neo4jRepository<Person, Long> {
// MATCH (person:Person {name={0}}) RETURN person
Person findByName(String name);
// MATCH (person:Person)
// WHERE person.age = {0} AND person.married = {1}
// RETURN person
Iterable<Person> findByAgeAndMarried(int age, boolean married);
// MATCH (person:Person)
// WHERE person.age = {0}
// RETURN person ORDER BY person.name SKIP {skip} LIMIT {limit}
Page<Person> findByAge(int age, Pageable pageable);
// MATCH (person:Person)
// WHERE person.age = {0}
// RETURN person ORDER BY person.name
List<Person> findByAge(int age, Sort sort);
//Allow a custom depth as a parameter
Person findByName(String name, @Depth int depth);
//Fix the depth for the query
@Depth(value = 0)
Person findBySurname(String surname);
}
- Mapping Query Results (Mapping the results of a query to pojo via annotation @QueryResult)
Example of query result mapping
public interface MovieRepository extends Neo4jRepository<Movie, Long> {
@Query("MATCH (movie:Movie)-[r:RATING]\->(), (movie)<-[:ACTS_IN]-(actor:Actor) " +
"WHERE movie.id={0} " +
"RETURN movie as movie, COLLECT(actor) AS 'cast', AVG(r.stars) AS 'averageRating'")
MovieData getMovieData(String movieId);
@QueryResult
public class MovieData {
Movie movie;
Double averageRating;
Set<Actor> cast;
}
}
Paging and sorting
Projections
@NodeEntity
public class Cinema {
private Long id;
@Value("#{target.name}")
private String name,
private Stringlocation;
@Relationship(type = "VISITED", direction = Relationship.INCOMING)
private Set<User> visited = new HashSet<>();
@Relationship(type = "BLOCKBUSTER", direction = Relationship.OUTGOING)
private Movie blockbusterOfTheWeek;
…
}
This Cinema has several attributes:
1. id is the graph id 2. name and location are data attributes 3. visited and blockbusterOfTheWeek are links to other domain objects 4. @Value("#{target.name}": rename
- Transactions
Using a facade to define transactions for multiple repository calls
@Service
class UserManagementImpl implements UserManagement {
private final UserRepository userRepository;
private final RoleRepository roleRepository;
@Autowired
public UserManagementImpl(UserRepository userRepository,
RoleRepository roleRepository) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
}
@Transactional
public void addRoleToAllUsers(String roleName) {
Role role = roleRepository.findByName(roleName);
for (User user : userRepository.findAll()) {
user.addRole(role);
userRepository.save(user);
}
}
- Transaction binding events
The current structure of the Spring framework makes the context unaware of transaction support and has an open infrastructure to allow additional components to be registered and affect the way event listeners are created. The transaction module implements an EventListenerFactory tool to find new @Transactional EventListener annotations. When this exists, notice that the extended event listener for transactions is registered rather than default. @Component public class MyComponent { @TransactionalEventListener(condition = "#creationEvent.awesome") public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) { ... } }