gjrwebber / spring-data-gremlin

Spring data gremlin makes it easier to implement Graph based repositories. This module extends Spring Data to allow support for potentially any Graph database that implements the Tinkerpop Blueprints 2.x API.
69 stars 54 forks source link

Spring Data Gremlin

Spring data gremlin makes it easier to implement Graph based repositories. This module extends Spring Data to allow support for potentially any Graph database that implements the Tinkerpop Blueprints 2.x API.

Features

Default Schema Generation

Below is a list of default annotations used by the DefaultSchemaGenerator.

Neo4j Schema Generation

Below is a list of supported annotations used by the Neo4jSchemaGenerator. These annotations are part of the spring-data-neo4j platform.

JPA Schema Generation

Below is a list of supported annotations used by the JpaSchemaGenerator:

Getting Started

Download dependencies

Currenlty only SNAPSHOT builds are being uploaded to sonatype so you will need to add https://oss.sonatype.org/content/repositories/snapshots/ repository URL to your build configuration.

Maven

<repositories>
    <repository>
        <id>spring.data.gremlin.snapshot</id>
        <name>Spring Data Gremlin SNAPHSHOT</name>
        <url>https://oss.sonatype.org/content/repositories/snapshots</url>
    </repository>
</repositories>

Gradle

repositories {
    //...
    maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
}

Once you have your build configuration setup you need to add the correct dependencies. To do that you need to decide which database and schema generator you want to use. If you are starting from scratch, then the default schema generator is for you.

Database dependency

OrientDB - com.github.gjrwebber:spring-data-gremlin-orientdb:0.1.0-SNAPSHOT
TitanDB - com.github.gjrwebber:spring-data-gremlin-titan:0.1.0-SNAPSHOT

Schema generator dependency

Default - No further dependency
JPA - com.github.gjrwebber:spring-data-gremlin-schemagen-jpa:0.1.0-SNAPSHOT
Neo4j - com.github.gjrwebber:spring-data-gremlin-schemagen-neo4j:0.1.0-SNAPSHOT

Maven example

Using OrientDB database with Neo4j schema generator:

<dependency>
    <groupId>com.github.gjrwebber</groupId>
    <artifactId>spring-data-gremlin-orientdb</artifactId>
    <version>0.1.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>com.github.gjrwebber</groupId>
    <artifactId>spring-data-gremlin-schemagen-neo4j</artifactId>
    <version>0.1.0-SNAPSHOT</version>
</dependency>

Gradle example

Using TitanDB with default schema generator:

compile("com.github.gjrwebber:spring-data-gremlin-titan:0.1.0-SNAPSHOT")

Create you domain model

Create your domain objects. I have used the default generator for mapping the schema, but you can also use spring-data-neo4j or JPA annotations if you wish.

Note: Have a look at the test subproject for more examples.

Person

@Vertex
public class Person {

    @Id
    private String id;

    @Property("customer_name")
    private String name;

    // No need to annotate simple types
    private boolean active;

    @Link("lives_at")
    private Address address;

    @LinkVia
    private Set<Located> locations;

    @LinkVia
    private Located currentLocation;

    // Annotation optional
    private Set<House> owned;

    @Property(type = JSON) // Will serialize as JSON even though House is a java.io.Serializable
    private House owns;

    @Property(type = JSON)
    private Set<Pet> pets;

    // Annotation optional
    private Pet favouritePet;

}

Address

@Vertex
public class Address {

    @Id
    private String id;

    @Embed(propertyOverrides = { @PropertyOverride(name = "name", property = @Property("countryName")) })
    private Country country;

    private String city;

    private String street;
}

Country (embedded)

@Embeddable
public class Country {
    private String name;
}

Location

@Vertex
public class Location {

    @Id
    private String id;

    private Date date;

    @Index(type = SPATIAL_LATITUDE)
    private double latitude;

    @Index(type = SPATIAL_LONGITUDE)
    private double longitude;

}

Located

@Edge("was_located")
public class Located {

    @Id
    private String id;

    @Property("location_date")
    private Date date;

    @FromVertex
    private Person person;

    @ToVertex
    private Location location;

}

House (Serializable)

public class House implements Serializable {

    private int rooms;

}

Pet (will be saved as a JSON String)

public class Pet {

    public enum TYPE {
        CAT,DOG,HORSE;
    }

    private String name;

    private TYPE type;
}

Now create a repository for the Vertex Person:

public interface PersonRepository extends GremlinRepository<Person> {

    List<Person> findByLastName(String lastName);

    List<Person> findByLastNameLike(String lastName);

    List<Person> findByFirstNameAndLastName(String firstName, String lastName);

    List<Person> findByFirstNameOrLastName(String firstName, String lastName);

    List<Person> findByFirstNameLike(String string);

    @Query(value = "graph.V().has('firstName', ?)")
    List<Person> findByFirstName(String firstName);

    @Query(value = "graph.V().has('firstName', ?)")
    Page<Person> findByFirstName(String firstName, Pageable pageable);

    @Query(value = "graph.V().has('firstName', ?)")
    List<Map<String, Object>> findMapByFirstName(String firstName);

    @Query(value = "graph.V().has('firstName', ?)")
    Map<String, Object> findSingleMapByFirstName(String firstName);

    List<Person> findByAddress_City(String city);

    @Query(value = "delete vertex from (select from Person where firstName <> ?)", nativeQuery = true, modify = true)
    Integer deleteAllExceptPerson(String firstName);

    @Query(value = "select expand(in('was_located_at')) from (select from Location where [latitude,longitude,$spatial] near [?,?,{\"maxDistance\":?}])", nativeQuery = true)
    Page<Person> findNear(double latitude, double longitude, double radius, Pageable pageable);

}

And one for the Edge Located:

public interface LocatedRepository extends GremlinRepository<Located> {

    @Query(value = "graph.V().has('firstName', ?).outE('Location')")
    List<Located> findAllLocatedForUser(String name);
}

Wire it up:


@Configuration
@EnableTransactionManagement
@EnableGremlinRepositories(basePackages = "test.repos", repositoryFactoryBeanClass = GremlinRepositoryFactoryBean.class)
public class Configuration {

    @Bean
    public OrientDBGremlinGraphFactory orientDBGraphFactory() {
        OrientDBGremlinGraphFactory factory = new OrientDBGremlinGraphFactory();
        factory.setUrl("memory:spring-data-orientdb-db");
        factory.setUsername("admin");
        factory.setPassword("admin");
        return factory;
    }

    @Bean
    public GremlinTransactionManager transactionManager() {
        return new GremlinTransactionManager(orientDBGraphFactory());
    }

    @Bean
    public GremlinSchemaFactory schemaFactory() {
        return new GremlinSchemaFactory();
    }

    @Bean
    public SchemaGenerator schemaGenerator() {
        return new DefaultSchemaGenerator(new OrientDbIdEncoder());
    }

    @Bean
    public SchemaWriter schemaWriter() {
        return new OrientDbSchemaWriter();
    }

    @Bean
    public static GremlinBeanPostProcessor gremlinSchemaManager(SchemaGenerator schemaGenerator) {
        return new GremlinBeanPostProcessor(schemaGenerator, "test.domain");
    }

    @Bean
    public GremlinGraphAdapter graphAdapter() {
        return new OrientDBGraphAdapter();
    }

    @Bean
    public GremlinRepositoryContext databaseContext(GremlinGraphFactory graphFactory, GremlinGraphAdapter graphAdapter, GremlinSchemaFactory schemaFactory, SchemaWriter schemaWriter) {
        return new GremlinRepositoryContext(graphFactory, graphAdapter, schemaFactory, schemaWriter, OrientDBGremlinRepository.class, NativeOrientdbGremlinQuery.class);
    }
}

TODO

Acknowledgement

This project would not have been possible without the hard work done by the spring-data-orientdb team. A lot of code and concepts were reused and reshaped. Thanks.