Chapter 9. A convincing act - Relationships

9.1. Value in Relationships - Creating them

Next were relationships. Direct relationships didn't require any annotation. Unfortunately we had none of those, because ours had more semantics. So we went for the Role relationship between Movie and Actor. It had to be annotated with @RelationshipEntity and the @StartNode and @EndNode had to be marked. So our Role looked like this:

@RelationshipEntity
class Role {
    @StartNode Actor actor;
    @EndNode Movie movie;
    String role;
}
    

When writing a test for that we tried to create the relationship entity with new, but got an exception saying that this is not allowed. This must be a strange restriction about having only correctly constructed RelationshipEntities. To fix it, we had to recall the relateTo method from the introduced methods on the NodeEntities. After checking it turned out to be exactly what we needed. We then added the method for connecting movies and actors to the actor - which seems a more natural fit.

class Actor {
...
public Role playedIn(Movie movie, String roleName) {
    Role role = relateTo(movie, Role.class, "ACTS_IN");
    role.setRole(roleName);
    return role;
}}
            

9.2. Who's there ? - Accessing related entities

What was left? Accessing those relationships. We already had the appropriate fields in both classes. Time to annotate them correctly. For the fields providing access to the entities on the each side of the relationship this was straightforward. Providing the target type again (thanks to Java's type erasure) and the relationship type (learned from the Neo4j lesson before) there was only the direction left. Which defaults to OUTGOING so only for the movie we had to specify it.

@NodeEntity
class Movie {
    @Indexed
    int id;
    String title;
    int year;
    @RelatedTo(elementClass = Actor.class, type = "ACTS_IN", direction = Direction.INCOMING)
    Set<Actor> cast;
}

@NodeEntity
class Actor {
    @Indexed
    int id;
    String name;
    @RelatedTo(elementClass = Movie.class, type = "ACTS_IN")
    Set<Movie> cast;

    public Role playedIn(Movie movie, String roleName) {
        Role role = relateTo(movie, Role.class, "ACTS_IN");
        role.setRole(roleName);
        return role;
    }
}
            

While reading about those relationship-sets we learned that they are handled by managed collections of Spring Data Graph. So whenever we add something to the set or remove it, it automatically reflects that in the underlying relationships. Neat. But this also meant we mustn't initialize the fields. Something we will certainly forget not to do in the future, so watch out for it.

We made sure to add a test for those, so are assured that the collections worked as advertised (and also ran into the intialization problem above).

9.3. May I introduce ? - Accessing Relationships themselves

But we still couldn't access the Role relationships. There was more to read about this. For accessing the relationship in between the nodes there was a separate annotation @RelatedToVia. And we had to declare the field as readonly Iterable<Role>. That should make sure that we never tried to add Roles (which I couldn't create on my own anyway) to this field. Otherwise the annotation attributes were similar to those used for @RelatedTo. So off we went, creating our first real relationship (just kidding).

@NodeEntity
class Movie {
    @Indexed
    int id;
    String title;
    int year;
    @RelatedTo(elementClass = Actor.class, type = "ACTS_IN", direction = Direction.INCOMING)
     Set<Actor> cast;

    @RelatedToVia(elementClass = Role.class, type = "ACTS_IN", direction = Direction.INCOMING)
    Iterable<Roles> roles;
}
        

After the tests proved that those relationship fields really mirrored the underlying relationships in the graph and instantly reflected additions and removals we were pretty satisfied with our domain.