Spring Data Neo4j - Application Development with Neo4j - Neo4j in Action (2015)

Neo4j in Action (2015)

Part 2. Application Development with Neo4j

Chapter 9. Spring Data Neo4j

This chapter covers

· Creating a domain model using Spring Data Neo4j (SDN)

· Loading and saving your SDN domain entities

· Object-graph mapping modes work within SDN

· Performing queries with SDN

Until now we’ve been working directly with the core Neo4j graph primitives—nodes and relationships—to represent and interact with (that is, read and persist) various domain model concepts.

Though that approach is extremely powerful and flexible, operating with the low-level Neo4j APIs can sometimes be quite verbose and result in a lot of boilerplate code, especially when it comes to working with domain model entities. In this chapter we’ll introduce you to Spring Data Neo4j (SDN), a subproject within the broader Spring Data project that aims to bring the convenience of working with the simpler, more familiar Spring-based development model to the NoSQL world, and in this case, specifically to Neo4j.

To demonstrate SDN, we’ll be returning to the social network example first described in chapter 1 and used in other chapters throughout the book. This is the social network example that allows users to rate movies. We’ll use this social network to demonstrate how SDN allows for the domain to be modeled using plain old Java objects (POJOs), and we’ll explain how the mapping to the underlying graph structure occurs. We’ll also demonstrate how to read, persist, and query these managed entities.

9.1. Where does SDN fit in?

In a nutshell, SDN is an object-graph mapping (OGM) framework that was created to make life easier for (currently only Java) developers who need, or would prefer, to work with a POJO-based domain model, where some or all of the data is stored in Neo4j. It aims to increase productivity by dealing with all the low-level plumbing and mapping logic required to read domain entities from and write them back into Neo4j. This should free you to focus on the important job of writing the code that makes you (or your company) money—namely, the business logic. Figure 9.1shows where SDN fits within your broader application.

Figure 9.1. Overview of where SDN fits within your broader application

In computer software, the requirement to read data from disk, and transform it into application-specific data structures, then write it back out again is very common. In the very early days of Java/RDBMS (relational database management system) projects, many developers would handcraft their own persistence-mapping logic, using, for example, low-level frameworks such as JDBC to directly interact with databases to create and populate their domain objects. This approach proved to be quite error-prone and took up a significant amount of time in the overall development of the application, with developers doing the same thing over and over again for each new project. Various object-relational mapping (ORM) frameworks, such as Hibernate (http://hibernate.org/orm), evolved to take some of the pain out of this process and bridge the gap between translating between the physical storage structure and the model in memory.

Neo4j’s native graph-based storage structure naturally provides a better fit for storing and retrieving complex object graphs than a relational database does. Relational databases often need to introduce additional structures, such as joining tables to deal with basic many-to-many relationships; Neo4j handles this natively. Nevertheless, there still exists a need to transform data from the persistence store into domain objects, and SDN seeks to play this role for scenarios where the underlying persistence technology is the NoSQL graph database Neo4j. In SDN’s case, this involves mapping the native graph concepts of nodes and relationships into your chosen POJO domain model classes.

What about OGMs for non-Java projects?

There are other non-Java OGM frameworks out there. A current listing of some of these (interspersed among general Neo4j clients) can be found at http://neo4j.org/drivers. The following list will give you a taste of what’s available, but please consult the website for the most up-to-date options:

· neo4j.rb —A Ruby binding that includes an OGM-type implementation of the Rails Active Model and a subset of the Active Record API. See https://github.com/andreasronge/neo4j.

· Neo4j Grails plugin —A plugin that integrates the Neo4j graph database into Grails, providing a GORM API for it. See http://grails.org/plugin/neo4j.

· neo4django —An OGM for Django. See https://github.com/scholrly/neo4django.

· neomodel —An OGM for Python. See https://github.com/robinedwards/neomodel.

9.1.1. What is Spring and how is SDN related to it?

Spring (http://projects.spring.io/spring-framework) is a very popular Java-based application development framework that provides productivity tools and utilities for Java developers.

Spring itself has many subprojects dealing with different areas, one of which is Spring Data. Within Spring Data, one of the projects is SDN. The engineers at Pivotal (formerly SpringSource) recognized that there were many data-access frameworks available for use with traditional RDBMS databases, but not so many in the brave new world of NoSQL.

The open source Spring Data project was developed as an umbrella project, with multiple subprojects being created to provide logic for specific databases (the first of which was SDN, where Emil Eifrem and Rod Johnson worked on the initial draft). Other NoSQL newcomers in this space include MongoDB and Redis.

In all cases, developers from both Pivotal and the underlying database helped get the frameworks to where they are today. All Spring Data projects leverage the existing good practices and proven functionality available within the core Spring framework to provide their flavors of data-accessing logic.

9.1.2. What is SDN good for (and not good for)?

By its very nature, SDN provides a convenient way to work with code or libraries that need or expect to operate on POJO-based domain entities. If you’re already using Spring, or you’re already using a rich domain model that you want to map to a graph, SDN could be for you.

Besides simply having, or wanting to work with, a rich domain model, another key consideration to take into account with SDN is the nature of, and number of, results or entities you’re typically going to be dealing with. If it’s in the region of a few hundred to a few thousand at a time, this scenario could lend itself well to an SDN setup. SDN isn’t suited to scenarios where you’ll need to perform any kind of mass data handling in a single go. Any logic where you have to load or store more than about 10,000 elements in a single swoop isn’t a good candidate for SDN. Additionally, by providing a layer of indirection, SDN will be slower than simply using the core API, so if speed and performance are of the utmost importance to you, you might be better served using the native API.

Note

The SDN framework provides a number of hooks (code providing access to the underlying GraphDatabaseService instance), which, if required, allow you to drop down and use the low-level core APIs to achieve maximum performance and flexibility. You can still view, query, and (with certain restrictions applied) update the graph outside of SDN using standard Neo4j tools if you so desire. Later in this chapter, the sidebar “Can SDN and Native Neo4j play nicely together?” offers more information on this subject.

9.1.3. Where to get SDN

SDN isn’t part of the main Neo4j offering, but can be downloaded as a separate library and used in conjunction with an appropriate Neo4j version. At the time of writing, the latest version available for use is 3.2.1.RELEASE, which is compatible with Neo4j version 2.1.5 and can be found athttp://projects.spring.io/spring-data-neo4j. This chapter (and the accompanying source code) will be using this release. Appendix C provides more detailed instructions on how to set up your project to use SDN. For more information about major changes introduced in SDN 3 in general, please also see the Neo4j blog post, “Spring Data Neo4j Progress Update: SDN 3 & Neo4j 2,” at http://blog.neo4j.org/2014/03/spring-data-neo4j-progress-update-sdn-3.html.

9.1.4. Where to get more information

SDN is a vast project in and of itself, and we’ll only be able to go so far in covering it here. In this chapter we aim to provide an introduction to some of the important aspects in the framework rather than a comprehensive reference guide. If you’re looking for more in-depth coverage on any of the points covered here, as well as those we’ll not be able to cover, there are good books on this subject, namely Good Relationships by Michael Hunger, which has also been incorporated into the official SDN reference docs, available at http://docs.spring.io/spring-data/neo4j/docs/current/reference/html/ (a free download at www.infoq.com/minibooks/good-relationships-spring-data) and Spring Data by Mark Pollak and others (O’Reilly, 2012). In general, the SDN documentation (http://projects.spring.io/spring-data-neo4j) is also a very useful reference resource, with more general information about the core Spring framework itself available at http://projects.spring.io/spring-framework.

9.2. Modeling with SDN

In this section, we’ll show you how SDN can be used to transform POJOs to represent the entities within the movie-lovers’ social network, where the data is stored in your Neo4j database. We’ll assume that you’re starting from a blank canvas and will be using SDN as the primary mechanism for driving the creation of, and setting up, your graph database. If you already have a graph database and want to know if you can apply SDN retrospectively, please refer to the sidebar “Can SDN and native Neo4j play nicely together?” later in the chapter.

In this section, you’ll

1. Define a standard POJO object model to represent your domain.

2. See what’s required by SDN to transform these POJOs into entities backed by Neo4j.

3. Dig a little deeper into various elements of SDN modeling, including

· Modeling node entities

· Modeling relationship entities

· Modeling relationships between node entities

To recap, in this social network users can be friends with each other. Users can also mark the movies they’ve seen and rate them with one to five stars, based on how much they liked them. You’re going to add a userId property so users can log in. This will also allow you to uniquely identify and refer to each user. Finally, you’re going to add the ability for new users to indicate whether they were referred by anyone at joining time. This could be used to assign points to the referrer for each movie rated by new members within their first month, potentially leading to a free movie ticket or some other benefit. Figure 9.2 illustrates that John originally joined the network because he was referred by David.

Figure 9.2. Conceptual overview of the movie-lovers’ social network, with referrals

9.2.1. Initial POJO domain modeling

If you ignore the fact for a moment that your data is actually being stored in Neo4j and take a simple stab at modeling your conceptual domain as POJOs, your first attempt might look something like the following listing. Note that getters and setters have been omitted for brevity.

Listing 9.1. Initial POJO modeling attempt

There is nothing too complicated here—this is basic object modeling. Both the User and Movie concepts have been modeled as first-class entities—that seems reasonable enough. You’ve also modeled a user’s viewing of a particular movie as an entity as well, namely the Viewing class. This is primarily because the relationship itself holds important information that you want to retain and use—namely the stars rating provided by the user. If you had modeled this as a simple collection type relationship between User and Movie, you’d lose this information.

At this point there is still no reference to any SDN- or Neo4j-specific concepts, just POJO stuff. Next, you’ll need to map this entity into the underlying Neo4j graph model.

How close have you gotten to creating a POJO model that’s easily translatable into the Neo4j world? Can you use it as is, or does it need any modifications? In this particular case you appear to have a good fit, with the User and Movie classes mapping neatly into the Neo4j nodes primitive concept with an associated name and title property, respectively.

The new referredBy relationship is represented as a reference to the user who did the referring, while the IS_FRIEND_OF relationship between users maps nicely to the set of friends. The only tricky part seems to be the modeling surrounding the Viewing class, which is trying to represent the scenario where a user has seen a movie and optionally rated it. Further inspection, however, reveals that this also fits perfectly into the Neo4j relationship concept. The Viewing class represents the HAS_SEEN relationship with its optional stars property, as well as the Userwho viewed the movie and the Movie reference itself.

So far so good; now it’s time to do the mapping with SDN.

Note

It won’t always be possible to find a logical POJO model that’s so closely tied to the physical Neo4j structure. In this case, you were quite fortunate, but in other cases you may have to adapt your model to fit. In any case, what this does highlight is how general POJO modeling concepts translate relatively well into Neo4j structures.

SDN modeling challenges

The logical model we’ve used for this chapter happens to translate very easily, and without much adjusting, to the physical Neo4j storage structure. It’s worth highlighting two scenarios where you may have an object model that requires a bit of adjusting to fit some of the SDN mapping requirements. These are some common scenarios:

· Using non-Set-based collections —When modeling node entity relationships (via @RelatedTo, which is covered in section 9.2.5), at present only Set-backed collections may be used. For example, you can’t have the following:

· @RelatedTo

private Map<RelationshipType,Set<User>> users;

· Entity equality —SDN requires the database ID (the node/relationship ID) form part of an object’s identity. Quoting from the reference guide, “Entity equality can be a grey area, and it’s debatable whether natural keys or database ids best describe equality, there is the issue of versioning over time, etc. For Spring Data Neo4j we have adopted the convention that database-issued ids are the basis for equality, and that has some consequences.” There are techniques for dealing with this scenario, but we won’t be covering this issue in this book. Once again, we direct you to the official website for more information. See the “Entity Equality” section in http://docs.spring.io/spring-data/neo4j/docs/current/reference/html.

9.2.2. Annotating the domain model

SDN is an annotation-based object-graph mapping library. This means it’s a library that relies on being able to recognize certain SDN-specific annotations attached to parts of your code. These annotations provide instructions about how to transform the associated code to the underlying structures in the graph.

Sometimes you may even find that you don’t need to annotate certain pieces of code. This is because SDN tries to infer some sensible defaults, applying the principle of convention over configuration. OGM is to graphs what ORM is to an RDBMS.

The following listing shows SDN annotations added to the POJOs to identify them as entities backed by Neo4j.

Listing 9.2. SDN annotated domain model

These annotations, along with sensible defaults assumed by SDN based on field names, directly tie elements of your Java class to physical entities in Neo4j. This means it’s imperative you have a very good understanding of your Neo4j data model when modeling with SDN. Although SDN shields you from having to do the low-level mappings yourself, it expects you to be able to describe how it should be done.

The sections that follow break down listing 9.2 a bit more and explain core modeling concepts, repeated here, in more detail:

· Modeling node entities

· Modeling relationship entities

· Modeling relationships between node entities

We’ll start with node entities.

9.2.3. Modeling node entities

Within SDN, a node entity refers to a Java class that’s being used to represent a particular domain entity represented and backed by a Neo4j node primitive in the underlying graph database. Figure 9.3 highlights candidate nodes within the social network domain model that could be modeled as SDN node entities.

Figure 9.3. Social network model with nodes highlighted

The Movie and User classes are perfect examples here. The @NodeEntity annotation is used to mark a class as a node entity, generally being placed just before the Java class definition, as shown in listing 9.2 and in the following snippet:

Properties

Within a class annotated with @NodeEntity, SDN will by default treat all simple fields as being representative of Neo4j properties on the backing node. In this context, simple means

· Any primitive or its associated wrapper class

· A string

· An object capable of being converted to a string through a Spring conversion service

Collections of any of these types are also included, where these collections are ultimately stored as an array on the Neo4j node. In the preceding domain example, this means that the name and title fields defined on the User and Movie classes, respectively, will get mapped as Neo4j properties with those same names, without you needing to lift a finger!

What about custom property types?

The core Spring framework comes with a general type-conversion system that allows you to write and register custom type-conversion logic that can transform a specific (nonprimitive) class to a string representation and back again. SDN has already registered some conversion services for you that handle enums and dates. So if, for example, you define a java.util.Date field on a node entity when the entity is due to get persisted, Spring recognizes that there is a custom converter defined for date fields and uses it to convert the Date property to a string that’s then stored in the graph; the reverse occurs when reading the data out of the graph and back into the node entity field.

This means that you can take advantage of this same mechanism to handle any custom class types that you may want to use, such as a phone number object. You’ll need to write a custom converter and register it with Spring. The book’s sample code provides an example of how to do this by adding a phoneNumber to the User entity. For more details on this conversion mechanism, please refer to chapter 6 of the Spring Framework Reference Documentation, http://static.springsource.org/spring/docs/current/spring-framework-reference/html/validation.html#core-convert.

If you don’t register a converter, SDN will still save a string version of your object into the database. However, it will simply be based on whatever is returned from the toString() method.

You’ve probably noticed that an additional field appears to have snuck into both the User and Movie node entities, namely nodeId, annotated with @GraphId. This is a mandatory SDN requirement when using simple object mapping. Without going into too much detail just yet, simple object mapping is one of the strategies employed by SDN to perform the physical mapping between your domain entity and the Neo4j-backed graph entity. Using this strategy, SDN needs to have a field where it can store the underlying ID of the node backing the entity. The @GraphIdannotation serves to mark the field that you’ve set aside for SDN to store this value in. Read more about simple object mapping in section 9.4.1.

Indexed properties

As it’s generally considered bad practice for an application to rely on the Neo4j node ID as an external unique identifier of an object (see the “Notes on the lookup benchmark” sidebar in section 5.6.1 for details), it’s important to have some other way of looking up a node. The addition of the@Indexed annotation on the userId field in the previous bit of code ensures that lookups and queries can be performed against this user based on this field. By default, @Indexed uses schema-based indexes, which rely on Neo4j labels. The code to look up the entity will be covered insection 9.3, but internally Neo4j uses exactly the same schema-indexing code and logic as described in section 5.5.1 to accomplish this.

Note

If you’d like to still use the legacy indexes, you can specify this using the indexType attribute; for example, @Indexed(indexType = IndexType.SIMPLE).

Under what labels and names are these annotated properties indexed?

By default, SDN employs a label-based type representation strategy that associates the underlying node for a node entity with labels whose names are the simple version of the class names in the entity hierarchy (User in this case). The schema index is then created on the property name defined against this label.

If you wanted to write some Cypher to perform a lookup on your userId property directly against the graph, you’d need to issue the following query:

MATCH (n:User) WHERE n.userId = {userId} return n

Be careful how you name and structure your domain entities. It’s generally not advisable to have two domain entities with the same name, even if they’re in separate packages, due to this default indexing behavior. If you had one User domain entity under a “core” package and another under “admin”, they’d end up sharing the same label, which is probably not what you’d want. It’s possible to override this default behavior by setting an @Alias annotation against the class, which will then create the index against the specified alias name instead. Nevertheless, it could be confusing to those who may not be aware of this behavior, and it could result in unforeseen results if you’re not careful.

Relationships to other node entities

There are also relationships between node entities. You’ll be pleased to learn that these are simply mapped as reference fields on the entity; this will be covered in section 9.2.5.

Before we get into the details of how this is done, you need to learn about relationship entities.

9.2.4. Modeling relationship entities

A relationship entity is the relationship version of a node entity. It refers to a Java class that’s ultimately represented and backed by a Neo4j relationship in the underlying graph database. Neo4j treats relationships as first-class citizens, which, just like nodes, can have their own identifiers and sets of properties if required. Thus, SDN also allows them to be represented as entities in their own right. Figure 9.4 highlights these relationships within the domain model.

Figure 9.4. A social network model with highlighted relationships that could potentially be modeled as relationship entities

In this section we’ll cover what’s required to model the physical Neo4j relationship entity as a POJO itself; in section 9.2.5, we’ll show what’s required from the node entity’s perspective to refer to other node entities through these modeled relationships, as well as through simpler mechanisms.

Note

You will probably define many relationships within your physical Neo4j model, but this doesn’t automatically mean that all of them need to be modeled as relationship entities in SDN.

SDN relationship entities are generally only required for relationships that have their own set of properties, and, together with these properties, provide context to the relationship. We’ll refer to these relationships as rich relationships because of the additional data they contain over and above the relationship type.

The HAS_SEEN relationship is a perfect example of this, with its additional stars property providing more context to the relationship. It indicates not just that a user has seen a movie, but also how the user rated it. In the social network model, this relationship with all its associated information has been defined as the Viewing class, as shown in listing 9.3. Contrast this to the IS_FRIEND_OF relationship, which alone is all that’s required to understand the relationship between two users—that is, that they’re friends. These simple relationships (relationships where theTYPE alone is enough to completely describe the relationship, such as the IS_FRIEND_OF relationship) can still be referenced, and you’ll see how this is done in section 9.2.5, but there’s no additional benefit in defining a whole new class to represent them.

Listing 9.3. The Viewing class as a relationship entity

The @RelationshipEntity annotation is applied to classes to indicate that it represents a Neo4j relationship. The annotation takes a type property that indicates the name of the Neo4j relationship type used within the graph itself. If the type property is not explicitly specified within the annotation, SDN will default the value assigned to it as the simple name of the class (which would be Viewing in this example). As with the node entity, the relationship entity has the same requirement for a @GraphId annotated field , this time for storing the underlying relationship ID.

If you’d like to access the node entities on either side of this relationship, you’ll need to provide a field for each of these and annotate them with @StartNode and @EndNode . For the Viewing class example, the User node entity starts (has the outgoing) relationship to the endingMovie entity.

In terms of what’s required to model a Neo4j relationship, that’s it. There is, however, generally not much point in defining relationship entity classes in isolation. They’re almost always referred to through one or more fields on associated node entities. In section 9.2.5 we’ll go into detail about how node entities can refer to other node entities through simple references, but also through POJO-modeled relationships. To provide the full context for this example, the following listing provides a sneak preview of how the User node entity, as well as the Movie node entity, refer to the Viewing relationship entity class through their views field.

Listing 9.4. User and Movie node entity snippets

Within the User node entity, the RelatedToVia annotation on the views field essentially reads as “all the HAS_SEEN relationships (with any associated properties) between this user and any movies the user has watched.” (The HAS_SEEN relationship type is inferred because that is what’s defined in the type property on the annotation of the Viewing class itself.) The Viewing class represents the full context of the relationship between these two entities including the rating field. Note that in this case (unlike the Movie class detailed next) you don’t explicitly specify direction = Direction .OUTGOING within the annotation because this is the default.

Within the Movie node entity, the RelatedToVia annotation marks the views field as representative of “all the HAS_SEEN relationships (with any associated properties) between this movie and any users who have actually watched it.”

In both cases the Viewing class provides a way to access the extra contextual information about the relationship, over and above the fact that these two entities are merely related—in this case, the extra contextual information is the specific rating provided for each viewing. You may at this stage be wondering why in one case the relationship was modeled with a Set (on the User) and with an Iterable in the other (on the Movie). All will be revealed in the next section, so keep reading!

In the next section we’ll continue to detail some of the finer points defining different types of relationships between node entities, including where the rich relationship details are required to fully understand the context.

9.2.5. Modeling relationships between node entities

Being able to model node and relationship entities with their simple associated properties in isolation will take you only so far. Models start getting interesting when you’re able to actually connect them to explore the relationships between them, and in this section, we’ll cover how to do that.

The end of the previous section provided a preview of how such a connection could be established between the User and Movie entities through the Viewing relationship entity. You saw how the HAS_SEEN relationship between Users and Movies was modeled as a physical POJO (theViewing class) and was then referred to from the entities. In that particular case, a whole separate class (Viewing) was used to represent the relationship in context. But what about other, simpler relationships, such as “John is a friend of Jack”? Do you also need a dedicated relationship entity class for such cases? You’ll be pleased to know the answer is no—they can be dealt with in a much simpler manner.

Figure 9.5 recaps the relationships between node entities that you’re potentially interested in referencing from the node entities.

Figure 9.5. A social network model with the relationship references between nodes highlighted

From the node entity’s perspective, relationships are also modeled as normal Java object references, and they come in a variety of flavors depending on what you’re trying to convey. We’ve already previewed how you can use the Viewing class to reference the HAS_SEEN relationship, but let’s tackle the other simpler relationships as well, such as referredBy and IS_FRIEND_OF.

The relationships involved in the User and Movie entities are shown in the next listing.

Listing 9.5. User and Movie node entity snippets

Basic relationships, represented by an underlying Neo4j relationship with no associated properties to exactly zero or one other node entity, can be modeled as a standard object reference within the node entity. By default, the property name will be used as the name that’s mapped to the relationship type in Neo4j, in the absence of any meta-information to the contrary. The newly introduced concept of one user referring another to the social network (modeled by the referredBy property ) is an excellent example of this. Note that this property must be a reference to another node entity.

Basic relationships to zero or more other node entities are modeled with a Set, List, Collection, or Iterable class, with the referenced node entity as the collection type. The use of a Set, List, or Collection class signifies that the field is modifiable from the containing node’s perspective; an Iterable class indicates this should be treated as read-only. Based on the contained node entity class type and its annotations, SDN will be able to work out that your intention is for this field to represent a basic relationship. If, however, you’d like to overwrite any of the defaults inferred, you can add an @RelatedTo annotation. The friends relationships between users is an example. Note how in this case we added the @RelatedTo annotation with the type property to specify the underlying Neo4j relationship type as IS_FRIEND_OF rather than the default value that SDN would have inferred if it were not there. In the absence of the annotation, SDN would have used the name of the field, friends, as the name to be used for the underlying relationship type property.

Rich relationships, those represented by an underlying Neo4j relationship with associated properties, are also modeled with the same Collection class as basic relationships. In this case, however, the type of the contained entity in the collection is a relationship entity rather than a node entity. To recap, a relationship entity represents the underlying Neo4j relationship along with any associated properties that were also modeled in the entity (see section 9.2.4). This provides a neat way to access the rich information on the relationship itself, while still being able to get to the entity or entities on the other end. As with basic relationships, without any annotations, SDN can work out that you’re creating such references based solely on the fact that the type of class contained in the collection has been defined as a relationship entity. Again, if you wish to override any of these relationship defaults assumed by SDN, you can apply the @RelatedToVia annotation. As you’ve already seen, the views field reference representing the relationship between a User and a Movie is a good example; the additional stars rating serves to enhance the information about the relationship between these two entities. Notice the different Collection class used for the views property in the case of the User and Movie nodes, namely Set<Viewing> and Iterable<Viewing>. This means that the views property can be modified from the User node perspective but not from the Movie node. Conceptually, users rate movies; movies don’t apply ratings to users.

Both the @RelatedTo and @RelatedToVia and annotations can take a type and optional direction element, specified to clarify whether relationships of a particular direction (valid values are INCOMING, BOTH, and the default OUTGOING) should be included in the collection of entities returned.

Note that for the friends property you need to specify the direction as BOTH, as shown in the following snippet:

@RelatedTo(type = "IS_FRIEND_OF", direction = Direction.BOTH)

Set<User> friends;

Logically, a friends relationship is bidirectional; physically within Neo4j, however, relationships are only stored in one direction. By specifying BOTH, you’re telling Neo4j to consider any IS_FRIENDS_OF relationships associated with the user, regardless of the direction in which the physical relationship is defined.

9.3. Accessing and persisting entities

You’ve seen how you can use annotations to provide SDN with the basic information it needs to map your POJO classes to the underlying graph-backed database without so much as a low-level node or relationship class in sight. This is a great step forward in having to only deal with business-level concepts in your domain model code. The next logical questions are how you can interact with these entities and how you can load and save your POJO entities to and from the graph database.

SDN offers a few options in this area, and we’ll be focusing on two of them in this section, namely the Neo4jTemplate class and the more general concept of Spring data repositories. Before that, we’ll look at the supporting Spring configuration, which you need to supply in order to make this all work.

9.3.1. Supporting Spring configuration

The following listing shows the minimum XML Spring configuration required to initialize SDN so it can find your domain entities and make various beans (such as the Neo4jTemplate) available to you.

Listing 9.6. XML-based Spring configuration

The neo4j:config entry is used by SDN to perform many initialization activities; for example, you can supply the storeDirectory attribute as a convenient way to refer to a graph database (creating a new embedded one if it doesn’t exist). From SDN 3.0 onward, it’s mandatory that you use the base-package attribute to specify the directory or list of directories where your domain entities are defined.

Note

You can achieve the same outcome by using Spring Java configuration if you don’t fancy XML.

Let’s move on to look at how you can use the Neo4jTemplate class to interact with your entities.

9.3.2. Neo4jTemplate class

Neo4jTemplate is an SDN class that can be instantiated and used directly or made available to your application when Spring is initialized. Following in the spirit of the other successful Spring template classes (such as JDBCTemplate and JMSTemplate), the Neo4jTemplate class aims to provide a convenient and simplified API for interacting with the low-level classes and behavior requirements of the underlying Neo4j graph-based classes.

It’s interesting to note that the SDN framework itself makes use of Neo4jTemplate to perform many of its own internal tasks, delegating to it as appropriate. But the template itself isn’t restricted to internal use only and can also be used as one of the options for invoking basic functionality surrounding SDN entities.

Neo4jTemplate: Power to the people!

The Neo4jTemplate class also provides many other low-level utility methods for operating on, querying, and gaining access to nodes and relationships. This is one of the ways in which SDN provides a hook for you to drop down to the core low-level API if you really need to get into the “belly of the beast” and have more fine-grained control. Be sure to explore all of the other methods available to you at some point.

The following listing is an example that illustrates how to save and load the User entity using the Neo4jTemplate class in its most basic form.

Listing 9.7. A basic Neo4jTemplate example

First up, the Neo4jTemplate class needs a reference to the underlying graph database it’s ultimately operating on to perform its tasks. This is the same GraphDatabaseService you’ve used in previous chapters to work with the native Neo4j constructs.

Although you appear to be holding true to the aspiration of only having to deal with real domain objects, there’s still a lot of “noise” (all that boilerplate transaction code) in listing 9.7. You could do better in this regard by making full use of the Spring framework with its dependency injection (DI) functionality and all that comes with it. Moving in this direction, your code can be transformed to what you see in the following listing.

Listing 9.8. A Neo4jTemplate example with full Spring integration

This new code comes a lot closer to achieving your Spring nirvana of only needing to write the core business logic, deferring all low-level plumbing to Spring. The boilerplate transaction code has now been replaced with the @Transactional annotation that instructs Spring to ensure that all code executed within the method is wrapped in a transaction for you.

Focusing on the core persistence logic, you can see that creating and saving a new entity is as simple as calling the save method on the Neo4jTemplate class, passing in the newly created POJO-based entity as the argument. Internally, the Neo4jTemplate creates a new node in the database for you, assigns a new (or reused) graph node ID, stores all associated properties (userId and name), and indexes the userId property.

Listing 9.7 illustrates two ways to load an entity. The first requires knowledge of and access to the graph node ID, which you have as a result of a previous call to save the entity . Recall that the @GraphId-annotated nodeId property on the User entity was required by SDN to store the underlying node ID. You’re simply making use of this information now for your own lookup purposes.

The second approach, for the cases where the node ID isn’t available or known, is to look up the entity via the indexed userId property .

Although this code isn’t quite as clean as it could be, it does the job. In the next section you’ll see how entities can be looked up via repositories, which does an even better job of hiding all internal plumbing required to perform index lookups as well as other functions.

9.3.3. Repositories

The second main option for loading and saving entities is via an implementation of the repository pattern, which you can find discussed in various books, including Domain Driven Design by Eric Evans (Addison-Wesley Professional, 2003) and Patterns of Enterprise Application Architecture by Martin Fowler (Addison-Wesley Professional, 2002).

In simple terms, a repository provides an abstraction layer between your business code, which should generally be dealing with domain-level entities, and all the logic required to convert these entities to and from their formats in any underlying data stores. Your business code should deal in the language of the domain; how these domain entities are actually loaded and saved is a job for a particular repository implementation. Using this approach, if you decided to swap out your data store, you wouldn’t need to throw away all of your business code; rather, you’d replace the appropriate repository implementation with one that knows how to translate the domain entities to and from the new data store format.

SDN repositories have specifically been designed to focus on one domain class at a time, and they aim to provide a whole range of default operations specific to that domain class. The really nice feature of SDN repositories is that they don’t require you to write a single line of code to implement the default operations. All you need do is define an interface and specify which domain class you’d like these operations created for.

Using the User domain class as an example, let’s look at how this works and what’s involved. Figure 9.6 shows our starting point.

Figure 9.6. Overview of SDN repository classes involved in accessing User node entity

The following one-line snippet shows the definition of the UserRepository interface, specifically for the User domain (node entity) class:

public interface UserRepository extends GraphRepository<User> { }

In this case, the UserRepository interface is extending the GraphRepository interface, which defines the broadest set of default operations available for a domain class. The use of Java generics ties this repository (at compile time, anyway) to the User domain entity.

The GraphRepository interface essentially consolidates all the other interfaces and thus defines operations covering CRUD, indexing, and traversals for a user. The following list shows some of the default operations that the GraphRepository interface covers:

· public <U extends User> U save(U entity);

· public <U extends T> Iterable<U> save(Iterable<U> entities);

· public User findOne(Long id);

· public boolean exists(Long id);

· public Result<User> findAll();

· public void delete(User entity);

SDN builds on the abstraction concept defined in the Spring Data Commons module that provides shared infrastructure across Spring Data projects. SDN takes on the responsibility of implementing the base methods in the interface that your repository extends (for example, theGraphRepository interface in the UserRepository case), providing a uniform set of functionality to support common loading, saving, querying, indexing, and traversing operations specific to an entity.

The following listing depicts how the UserRepository can be used to save and load users.

Listing 9.9. Loading and saving data via the UserRepository

@Autowired

UserRepository userRepository;

@Transactional

public void saveAndLoad() {

User user = new User("john001","John");

User savedUser = userRepository.save(user);

User loadedUser = userRepository.findOne(savedUser.getNodeId());

User loadedUserViaIndex =

userRepository.findBySchemaPropertyValue ("userId","john001");

}

You’ll notice that this code is very similar to that used by the Neo4jTemplate approach in listing 9.7 (with the exception that you don’t have to provide a target type). The Repository.save method persists the entity into the graph, with the loading being accomplished with findOne(based on a node ID) or findBySchemaPropertyValue (based on an index lookup, against the userId property in this case). For Spring to know about any repository interfaces you define, it needs to be told. Adding the following code into your Spring XML configuration file will do the trick:

<neo4j:repositories base-

package="com.manning.neo4jia.chapter09simple.repository"/>

Who implemented my interface?

The astute among you may have realized that we never actually defined any concrete implementation for the UserRepository interface. We simply injected it through Spring’s @Autowired annotation and then used it, somehow managing to read and save the User domain entities along the way. So, if we never provided any implementation, who did, and how does this work?

There’s no magic involved here. What’s happening is that SDN used the initial packages you provided in your XML configuration to search for your repository definitions, and then dynamically created a proxy for you that implements your interface and makes it available as a Spring bean. This proxy does all the hard work of implementing the functionality defined in the core interfaces. You get a whole bunch of functionality simply by extending one interface!

Repositories are a step up from the Neo4jTemplate class in that they also provide implementations for a lot of boilerplate code, but in a far more domain-targeted manner.

9.3.4. Other options

Besides using the Neo4jTemplate class or repositories, there’s a third approach available when saving entities. This involves calling a persistence method on the entity itself. We’ll cover this in section 9.4.2 as it’s only available when making use of the advanced mapping mode, but it’s listed here for completeness.

There may also be cases when your domain object needs to load data from multiple data stores, the so-called cross-store persistence scenario. Perhaps some of your data is stored in Neo4j and the rest in MySQL. You’ll be happy to know that SDN also caters to cross-store persistence, although we won’t be covering how to do this in this book. More information about cross-store persistence can be found in the core Neo4j SDN documentation.

Can SDN and native Neo4j play nicely together?

From a reading, querying, and traversing perspective, any data written into the underlying graph from SDN can safely be accessed in a read-only manner using any of the native Neo4j tools you’ve come across thus far, such as Cypher via the Web Admin Console, the Neo4j Shell, and so on.

SDN tries to be as unobtrusive as possible when it comes to storing data in the graph, but it needs to store some meta information in order to do some of the things it does. For example, there are a few different ways in which a domain model’s Java hierarchy can be represented in the graph, and these options are handled by something called type representation strategies. These strategies rely on storing some information in the graph. The LabelBasedNodeTypeRepresentationStrategy (which is the default strategy from SDN 3.0 onward) creates labels against the underlying nodes based on the simple class names of the node entities. The IndexingNodeTypeRepresentationStrategy creates legacy indexes as well as adding a property called __type__ on each entity, whereas the SubReferenceNodeTypeRepresentationStrategy may create INSTANCE_OF and SUBCLASS_OF relationships between entities.

You’ll need to understand and take this additional meta information into account if you want to update the underlying entities outside of SDN. Creating a new Person entity, for example, may involve more than simply adding one node in the graph with its basic attributes.

We can’t cover all the scenarios where meta information is used and stored by SDN, but we can highlight this point here to make you aware of it and the possible limitations it may impose on you should you consider using SDN.

For more information about the different type representation strategies available, see the “Entity type representation” detailed in http://docs.spring.io/spring-data/neo4j/docs/current/reference/html.

Before we move on to the final functional section regarding how to perform queries and traversals using entities, let’s detour to cover OGM. We’ve occasionally referred to simple object mapping throughout this chapter, and now’s the time to define exactly what we mean by this.

9.4. Object-graph mapping options

SDN offers two modes to perform the mapping between your domain POJOs and the underlying graph entities: simple mapping and advanced mapping. Understanding the differences in how these modes operates will help you assess the trade-offs in each approach and decide which one is most appropriate for your project, should you choose to use SDN. Choosing is a little bit like deciding between DOM and SAX XML parsers—both do the same thing, parse XML, but in very different ways with implications for the choice taken.

Regardless of whether you choose the simple or advanced mapping mode, the process for determining what gets mapped to what is the same for both and was conceptually described in section 9.2.2 for annotating the domain model. Very briefly, this mapping process involves SDN parsing and analyzing all known node and relationship entities. With a little help from the Java reflection API, SDN builds up an in-memory metamodel of the mapping rules. These rules are then used by each implementation to perform the grunt work, so to speak—the work of running backwards and forwards between the domain model and graph primitives, translating when each sees fit.

And then there were two ...

Prior to the release of Spring Data Neo4j version 2.0.0, only the advanced approach (AspectJ-based mapping) was supported. AspectJ is a Java implementation of the aspect-oriented programming (AOP) paradigm—see http://eclipse.org/aspectj. Within SDN, it looks to dynamically intercept method calls on your POJOs as the mechanism for seamlessly integrating the Neo4j database and Java domain model.

Reacting to feedback from the community, the simple mapping mode was added to address many of the issues and concerns that were raised in relation to the AspectJ tooling, as well as some other more general implications present with this more complicated setup. It should be noted that more effort is now being focused on upgrading and making the simple model the de facto approach to using SDN.

9.4.1. Simple mapping

The simple mapping mode, which is the default mode and requires no special setup, involves copying data into a node entity on any load operations, with the writing back to the graph only occurring when an explicit save operation is invoked. Figure 9.7 shows a simple mapping mode.

Figure 9.7. Overview of the simple mapping logic

Although the approach is simple, you need to be aware of the memory implications inherent with simple mapping. Because this approach involves copying data between your domain entities and their underlying representations, you’ll need to keep an eye on your memory footprint, as everything is essentially loaded twice—once via the node and then again via the domain class when everything is copied over. Additionally, this approach will also incur a performance penalty because you have to constantly keep copying the graph to perform certain operations.

That said, let’s take a look at the next listing and dig a little deeper into what’s happening with the loading and saving methods, as well as everything in between.

Listing 9.10. Loading and saving

The findOne method available via the CRUDRepository interface is classified as one of the load methods. When this line of code executes, SDN will do three things:

· Create a new User object.

· Load the underlying Node class into memory.

· For each property and certain relationships (those without @Fetch annotations, which we’ll come to shortly), copy the data out of the graph and into the POJO classes properties.

The next time SDN interacts with the underlying node is when an explicit save is called . Nothing between these points ever results in a call to the underlying database; everything is done on the domain objects. Technically, entities that have just been created or modified but not yet saved are said to be in a detached state.

Transitive persistence

The other noteworthy point to make about this code is that when John is saved , Susan gets saved too. Lucky Susan! So what’s special about John? Whenever a save operation is invoked, SDN will look at the object to be persisted (John, in this case) and detect what’s new or changed from the perspective of the node that’s being saved, including relationships. John has a new age and name and a new friend. Thus, SDN saves the full object graph of modified data, bringing Susan along for the ride as well. Remember relationship properties defined with read-only semantics (that is, those of the Iterable collection type) won’t be persisted.

Eager versus lazy loading

Susan gets saved by default when John is saved, but is she also loaded when John is loaded? The official answer is “It depends.” If the SDN default configuration is in play, as in this case, the answer is initially “No.” This is because, by default, SDN doesn’t engage in eager relationship instantiation when loading a domain entity’s relationship-based properties.

Eager loading would involve SDN actively following any relationships that may exist on an entity and then also loading these entities, including all of their properties as well as their relationships. To conserve memory and prevent potentially loading vast swathes of the graph into memory when it’s not explicitly needed, SDN defaults to lazy loading.

So what happens if you ask for entities that have been lazily loaded? Take a look at the following listing.

Listing 9.11. Implications for lazy loading

For each node entity to be lazily loaded, SDN still instantiates a node entity POJO for the underlying entity, but it will only populate the property annotated with @GraphId. The unit test code in listing 9.11 shows you’re able to access the nodeId, but the name is still null at this point.

When you want to load the full entity, you can make use of the Neo4JTemplate fetch method, shown in the following extension snippet, which would follow directly after the code in listing 9.11:

template.fetch(loadedSally.getFriends());

assertEquals( "John", firstFriendOfSusan.getName());

Alternatively, if you want SDN to eagerly instantiate the entities at loading time, you can annotate the appropriate property with @Fetch, as in the following snippet:

...

@Fetch

@RelatedTo(type = "IS_FRIEND_OF", direction = Direction.BOTH)

Set<User> friends;

...

You’ve now had a brief overview of the simple mapping mode, which can probably be summed up with the phrase “Simple, yet powerful, but potentially memory-hungry too!” Next up is advanced mapping!

Mandatory @GraphId property should be defined as a Long

Simple mapping mandates that domain modelers provide a property annotated with @GraphId to store the associated node or relationship ID, providing the library with a link to the underlying node or relationship backing it. The value will be null when the entity has yet to be persisted to Neo4j (typically when you use the new keyword to create your entity), but once it has been saved at least once, it will be populated to reflect the underlying node or relationship it’s connected to in the graph.

Words of caution here. Make sure you define this property as the Long data type and not as the long primitive. The wrapper class allows for a null to be specified, indicating that the entity has yet to be persisted into the graph. If you define this field as a primitive long, this will mean that its default value will always be 0. Zero, however could represent a real node ID (prior to Neo4j 2.0, the reference node present in all graph databases was always node ‘zero’). This could result in all kinds of erroneous data being stored and retrieved. What you really want is a null node ID if the entity has never been persisted.

9.4.2. Advanced mapping based on AspectJ

The advanced mapping mode relies on the AspectJ library as its mechanism for implementing the mapping and conversion logic between your POJO-annotated domain class and the underlying entity in the graph (see figure 9.8).

Figure 9.8. Advanced mapping overview

AspectJ (see http://eclipse.org/aspectj) is a Java implementation of the AOP paradigm (http://en.wikipedia.org/wiki/Aspect-oriented_programming). AOP, as a paradigm, aims to increase modularity within a code base, specifically for the purpose of allowing common cross-cutting concerns to be untangled and extracted out of the main code and ultimately stored separately (as aspects). A cross-cutting concern is some common logic that needs to be applied at multiple points within the code base but that isn’t necessarily related to the core domain itself (typical examples cited are logging and transaction handling). This concern or aspect can then somehow (implementations differ) be configured to be applied to your code base at appropriate points.

So how does SDN make use of AOP (specifically AspectJ) to perform its common logic of converting and translating between the POJO class and underlying graph entity?

First, unlike the simple mapping that all happens at runtime, the advanced mode relies on some compile-time settings and configurations. This involves including a specific AspectJ-aware version of SDN in your project, as well as using the AspectJ (ajc) compiler. The configuration details are discussed in appendix C.

SDN will scan your code for all @NodeEntity and @RelationshipEntity classes. It will then take these POJO domain classes and, by using the special AspectJ compiler, will replace them with its own custom-generated versions.

Second, this custom-crafted class has two features worth noting. First, a direct reference to the actual node or relationship is attached to the class as an internal hidden field. The additional code generated by the AspectJ compiler, in conjunction with this hidden field, ensures that any code that’s reading or writing to properties backed by Neo4j entities is modified so that it ends up delegating all calls directly to the underlying entity.

The second noteworthy feature is that additional persistence-related methods are added or mixed into the generated class, consistent with an active record type of approach—that is, a technique whereby persistence code lives side by side with the domain code. This is done by ensuring that the generated class implements either a NodeBacked or RelationshipBacked interface, providing appropriate implementations for the raft of persistence and lookup functionality. The following listing shows how you can use some of these persistence-related methods to operate on the entity directly.

Listing 9.12. Active record persistence with implicit transaction

If you cast your eyes over the code in listing 9.12, it more than likely will conjure up a few questions, such as “Does this code even work?” The absence of any transaction code does look a bit suspect, given that all operations need to occur within a transaction.

Remember, node entities can exist in two states: attached or detached. Creating a new node entity occurs in a detached state, which simply means that the entity has not yet had its state written into the underlying graph database.

Calling persist() on the entity (or indeed Neo4jTemplate's save() method) attaches the entity to the graph. Attachment ensures that the state in the entity is synchronized with that in the graph database. For new entities, new nodes and properties are created, and for entities that were at some point previously attached, they’re reattached, ensuring any changes since the last attachment are written to the graph.

SDN can detect whether or not your code is operating within the context of an existing transaction. If not (as is the case in the sample code), SDN will ensure that calls to the attach-related methods (NodeBacked's persist in this case, as well as Neo4jTemplate's save) will create an implicit transaction for you. An implicit transaction is simply a new transaction created for you internally that SDN then uses to perform the mutating calls that save the state into the graph.

Although we’ve provided details here about how implicit transaction works, you’re generally advised to provide explicit transactions yourself. Otherwise the library has to reattach detached entities all the time. Having tiny implicit transactions can also slow down your application tremendously.

It’s worth being aware that use of the advanced mapping mode will generally involve a read-through down to the database whenever a property or relationship is accessed on the domain entity. To avoid multiple DB reads it may sometimes make sense to store data, or interim results, or both, in local variables rather than repeatedly querying for the data via the domain entity itself. Depending on how you’re interacting with the entity, this local variable could be scoped to within a method, within the entity class itself, or even within another class. This will aid performance, but additional variables will also add to your overall memory footprint, so you need to keep this in mind.

What if I’m operating within an existing transaction?

If you have previously created or started an explicit transaction before your code executes (as opposed to relying on implicit transactions), SDN will not try to create its own new transaction. SDN will simply operate within the existing transaction boundary established, providing you with the ability to make use of all the standard transaction controls (such as rollback) that you’re typically accustomed to.

This concludes our brief overview of the advanced mapping mode. In exchange for some configuration headaches as you set up AspectJ for your development environment (this was one of the primary complaints that led to the introduction of the simple mapping mode), you can better utilize your available memory compared to the simple approach, though there may be performance penalties for property read-throughs if you don’t deal with them appropriately.

9.4.3. Object mapping summary

Table 9.1 compares the simple and advanced mapping modes.

Table 9.1. Comparison of simple and advanced mapping modes

Advantages

Disadvantages

Simple mapping mode

Simple and intuitive to understand, works out of the box.

Can be memory-hungry, with data copied backward and forward between the entity and node representation.

No special IDE requirements.

Slower.

Advanced AspectJ mapping mode

Read-through approach may be more memory-efficient.

Read-through approach may have performance implications if too many fine-grained calls are occurring.

Provides ability to use active record persistence pattern if required.

Bugs involving implicit transactions can be harder to track down and debug.

Faster.

Tooling, specifically around support for AspectJ in IDEs such as Eclipse and IntelliJ, has been known to cause developers many headaches and sleepless nights and was one of the major gripes by the Neo4j community.

9.5. Performing queries and traversals

To wrap up this chapter, let’s discuss how SDN extends its easy programming model to be able to specify and make use of queries and traversals in a very simple manner. Suppose you want to find information about a user’s friends of friends. We’ll use a submodel of the main domain to explore this scenario further, as shown in figure 9.9. Focusing on the user John, the friends of friends are ultimately identified as those users who fall into the last segment on the right.

Figure 9.9. Friends-of-friends submodel

9.5.1. Annotated queries

It shouldn’t surprise you that SDN provides an annotation (@Query in this case) for the purposes of identifying a query to be executed. This can be very helpful in cases where you want to return a filtered list of entities, or perhaps a subset of information determined at runtime. The annotation can be applied to a field on a node entity, or to methods in a repository interface. When the field is accessed on a node entity, or a method is invoked on a repository interface, the query is executed and results are returned.

Annotation on node entities

Let’s start with how the @Query annotation can be applied to the User node entity to define some queries involving the friends-of-friends scenario:

@NodeEntity

public class User {

...

@Query( value =

"match (n)-[r:IS_FRIEND_OF]-(friend)-[r2:IS_FRIEND_OF]-(fof) " +

"where id(n) = {self} " +

"return distinct fof")

Iterable<User> friendsOfFriends;

Whenever a User entity is loaded (either through Neo4jTemplate or UserRepository methods), the Cypher query specified in the annotation will be executed and the result stored in the variable (friendsOfFriends, in this case).

This is a standard Cypher query with the only new syntax being the reference to {self}, which refers to the current ID of the node backing this entity. This makes sense, as queries defined on an entity should generally be related to that entity. If your query isn’t specifically related to the node entity in question, this is an indication that it probably needs to be defined in a repository as a more generic method.

You can dynamically specify parameters to your query by supplying a set of key-value pairs via the params attribute on the annotation. Following is another snippet illustrating how this can be done to answer the query, “for a given a user, for each of his or her direct friends, count the number of friends and return these counts.”

@Query(value =

"match (n)-[r]-(friend)-[r2:IS_FRIEND_OF]-(fof) " +

"where id(n) = {self} " +

"and type(r) = {friendRelName}" +

"return friend.name as friendName , count(fof) as numFriends",

params = {"friendRelName","IS_FRIEND_OF"})

Iterable<Map<String,Object>> friendsOfFriendsCount;

As you’re not returning the whole entity, but rather a subset of the data, the result of this query will be a read-only collection (Iterable) of entries, with the keys being the names of the variables defined in the return section of the query and their values being the result. Using the example graph in figure 9.9, running this query against John would result in the following answer:

{friendName=Kate, numFriends=2}

{friendName=Jack, numFriends=2}

The next snippet shows how you can return basic aggregation-type values in Cypher, such as counts. In this case, you’re looking for a count of the total number of friends of friends, which according to the submodel depicted in figure 9.9, should be four:

@Query(value =

"match (n)-[r:IS_FRIEND_OF]-(friend)-[r2:IS_FRIEND_OF]-(fof) " +

"where id(n) = {self} " +

"return count(distinct fof)")

Long totalNumFriendsOfFriends;

Annotation on repository interfaces

The @Query annotation can also be applied to methods that you may want to define in your entity-specific repository interfaces. Recall the UserRepository from section 9.3.3. The next listing shows what the same friends-of-friend query looks like when defined against a repository method.

Listing 9.13. @Query annotation on repository

Note that you’re no longer using the {self} reference. Now, even more powerfully, you have access to the parameters passed into the method. The parameters are available as incrementing values starting at zero {0}, which corresponds to the first argument in the method signature, {1} to the second, and so on.

Once again there’s no need for you to implement any of the logic to execute the query or do the mapping to convert the result into POJOs, primitives, and the like. SDN does this all for you.

9.5.2. Dynamically derived queries

Another really neat way of being able to define a query without needing to write even a line of Cypher code is to take advantage of dynamically named finder or get methods. Whenever SDN comes across a method defined in a repository interface that doesn’t have an explicit @Queryannotation, it will try to dynamically construct a Cypher query for you, based on the name of your method. SDN will break down the method name into parts that are matched against the metamodel. All of these parts map to properties defined on the associated domain entity class.

There are quite a few options and permutations available, including specifying sort order, specifying range queries, requesting distinct results, and much more. We can’t cover them all, but the core set of rules used to recognize dynamic methods can be summarized as follows:

· Any method starting with zero or one of find, read, get

· Optionally followed by any alphanumeric combination of characters

· Subsequently followed by the term By (uppercase B)

· Subsequently followed by one or more names of any properties on the associated domain entity, with the first character of the property as uppercase, and multiple properties separated by the term And or other comparison separators like GreaterThan or Like

To demonstrate some of these rules in action, we’ve defined a few methods that all aim to find any users with a particular name (for example, find all “Susan”s). Have a look at listing 9.14 and notice how all of the method definitions on the UserRepository interface will ultimately result in exactly the same underlying Cypher query being generated. Note also that as with annotated queries, the parameter arguments relate to aspects of the method, in this case, the parts representing the property names.

Listing 9.14. Dynamically generated query methods

Handy Hint

If you’d like to see what query SDN generates for your method, change the logging level in your application to DEBUG. SDN uses SLF4J, so any framework that supports SLF4J is acceptable; the tests in the sample code make use of Log4j.

As before, return types of Iterable indicate to SDN that you’re expecting multiple possible entities, whereas a single domain entity indicates you’re expecting just one. If you do ever specify that you only expect one entity and the underlying query results in more than one, SDN will throw an exception of the following sort:

NoSuchElementException: More than one element in IteratorWrapper(non-empty

iterator)

Multiple and nested properties

SDN is capable of understanding related property definitions. If you’d like to find all users who were referred by any other user of a particular name (say Susan), you could define a method as follows:

Iterable<User> findByReferredByName(String name);

Here the first part of the method references the referredBy property on the user. Then, as this property is itself of the User type, you proceed to search based on the name of that user.

You could string a whole lot of properties together and separate them with an And or Or term. (Currently, only And is supported at runtime for Cypher.) Let’s pretend that your user also had an age attribute. In this case, if you wanted to find all users with a particular name and age, you could define a method such as this:

Iterable<User> findByNameAndAge(String name, int age);

And much, much more

This brief section is nowhere near long enough to introduce the wealth of querying options available, including sorting, range queries, and making use of the generic query methods available through the Neo4jTemplate class. We hope we have provided a taste of the kind of queries that you can access and shown you the right direction should you choose to continue down this path.

9.5.3. Traversals

Once again we find ourselves unable to cover everything, and unfortunately, graph traversals within SDN is one area we can’t cover in depth here. Not to be left out completely, however, we will point out that the traversal framework you encountered in chapter 4 is also accessible from within SDN. If you hadn’t already guessed it, there’s an annotation for it—namely, @GraphTraversal—as well a whole repository interface (TraversalRepository) dedicated to helping with traversals involving domain entities.

9.6. Summary

In this chapter we introduced the Spring Data Neo4j (SDN) framework that provides a variety of tools that allow you to use a standard POJO-based domain model backed by the powerful Neo4j database. You saw how easy it was to annotate your POJOs in a manner that allowed SDN to seamlessly take care of all the mapping logic between your domain model and the underlying graph primitives.

You learned how to make use of both the Neo4j template- and repository-backed implementations to perform CRUD and indexing operations on your entities, as well as how to define and execute queries involving node entities.

You also learned a little bit more about the two main object-graph mapping modes available to you: simple and advanced. You discovered that although they both ultimately do the same thing, they take quite different approaches to accomplishing this, which can have memory and performance implications that need to be considered.

As we stated in the section 9.1.4, we’ve only been able to scratch the surface of what SDN can do. We hope that this chapter has served to whet your appetite and that you’ll continue to look further into what SDN has to offer. Next up, you’ll delve into understanding the differences between using Neo4j in embedded versus server mode.