In this article, I will show you the advantages and disadvantages of the neo4j graph database, the technology that is being used by big companies like Google, Facebook or PayPal. I will also show you how to create and populate it with the help of Spring Boot.
Why a graph database…?
The main application of graph databases is to store relationship information as a first-class entity, because, despite the name, relational databases do not describe relationships other than the standard many-to-many, one-to-one and one-to-many.
A huge advantage of graph databases is that the performance is not reduced with the growth of the amount of data.
…and why neo4j?
Apart from the above points, the neo4j itself has a number of advantages, such as:
- scalability
- good documentation
- easy to use
- built-in Spring Boot support
- wide user community
and also it has a useful and pleasant user interface.
Let’s learn!
To start everything off let’s create a local server.
To do that, navigate to https://neo4j.com/download-center/#releases and download the latest release of the community server, it will be sufficient for this tutorial.
Our next step will be to run the neo4j server. Run command “neo4j console” inside the bin directory in the downloaded package.”. If everything went well, you should see something like this:
Now let’s open the localhost link in the browser and we should see the remote UI that I was talking about in the advantages of neo4j. Log in with the default credentials (username: “neo4j” and password: “neo4j”) and then set your new password (do not forget it as we will use it later!).
Creating an application
Now we will build the easy demo application that is going to show Formula 1 results, drivers and also will allow us to retrieve drivers that achieved specific race results.
As I mentioned at the beginning, we will use Spring Boot, so we can create our application, for example, by using the https://start.spring.io/ website. For now, we will need only the neo4j dependency. If you, however, do not want to use the Spring Initializr, here is the dependency you will need:
Maven:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-neo4j</artifactId> </dependency>
Gradle:
compile("org.springframework.boot:spring-boot-starter-data-neo4j")
So now for the actual code…
Let’s create a Driver and Race classes
@NodeEntity public class Driver { @Id @GeneratedValue private Long id; private String name; private Integer number; public Driver() { } public Driver(final String name, final Integer number, final Team team) { this.name = name; this.number = number; this.team = team; } /* Getters */ }
@NodeEntity public class Race { @Id @GeneratedValue private Long id; private String country; @Relationship(type = "STARTED_IN", direction = Relationship.INCOMING) private Result results; public Race() { } public Race(final String country) { this.country = country; } /* Getters */ }
We can notice some of the neo4j properties being used in our classes. First of all, they are annotated as @NodeEntity, which obviously means that those classes will represent nodes in our graph. We also need an id that neo4j will use to map our entities (be aware that @Id annotation has to be a neo4j annotation, do not confuse it with JPA @Id!). Next thing we have to be aware of is that the fields are non-final because neo4j needs an empty constructor, which is one of its downsides. Last thing present in the Race class is the @Relationship annotation; as I have mentioned, the main advantage of graph databases is that relationships can have properties. Let’s now create the relationship class:
@RelationshipEntity(type = "STARTED_IN") public class Result { @Id @GeneratedValue private Long id; private Integer place; @StartNode private Driver driver; @EndNode private Race race; public Result() { } public Result(final Integer place, final Driver driver, final Race race) { this.place = place; this.driver = driver; this.race = race; } }
When we are creating a relationship class we must annotate it with @RelationshipEntity and pass the type as an argument; it should be the same type as in the node entities. Relationship entities also need an id and constructors. The last crucial thing is the need to specify @StartNode and @EndNode so that neo4j knows how to connect the nodes.
Our next step will be creating a result repository, if you are familiar with Spring repositories you will feel at home as Neo4jRepository extends PagingAndSortingRepository so all the Spring repository methods (eg. findById, deleteById, save,..) are available:
public interface ResultRepository extends Neo4jRepository<Result, Long> { List<Result> findAll(); }
Alright, so let’s see neo4j in action. For clarity, we will use hard-coded entities in the CommandLineRunner init bean:
@SpringBootApplication @EnableNeo4jRepositories public class F1Application { public static void main(String[] args) { SpringApplication.run(F1Application.class, args); } @Bean CommandLineRunner init(ResultRepository resultRepository) { Race italy = new Race("Italy"); Race singapore = new Race("Singapore"); Driver hamilton = new Driver("Lewis Hamilton",44); Driver vettel = new Driver("Sebastian Vettel", 5); return args -> { resultRepository.save(new Result(1,hamilton,italy)); resultRepository.save(new Result(2,vettel,italy)); resultRepository.save(new Result(4,hamilton,singapore)); resultRepository.save(new Result(3,vettel,singapore)); }; }
For everything to work, you need to enable neo4j repositories by annotating the application class with @EnableNeo4jRepositories. The last thing we will need is to set the username and password in application.properties to the values we have chosen when we were setting up our server:
spring.data.neo4j.username=neo4j spring.data.neo4j.password=pass
If everything went as expected on the remote neo4j interface we should be able to see our populated database:
When we hover over the STARTED_IN relation we will see the places that we have set in the init method.
Custom query and integration testing
Neo4j uses the CYPHER query language and with its use, we can create our own custom query methods, so let’s say we want to create a list of drivers that achieved a specific result that is interesting for us. For that in our ResultRepository we will create the getByResult(Integer result) method:
@Query("MATCH (r:Race)<-[s:STARTED_IN]-(driver: Driver) WHERE (s.place)={result} RETURN driver") List<Driver> getByResult(@Param("result") Integer result);
We have created something by ourselves, so it would be nice to test it. Let’s add JUnit, neo4j, neo4j ogm test and graphaware dependencies:
Maven:
<dependency> <groupId>org.neo4j</groupId> <artifactId>neo4j</artifactId> <version>3.4.8</version> </dependency> <dependency> <groupId>org.neo4j</groupId> <artifactId>neo4j-ogm-test</artifactId> <version>3.1.3</version> <scope>test</scope> </dependency> <dependency> <groupId>com.graphaware.neo4j</groupId> <artifactId>resttest</artifactId> <version>3.4.8.52.18</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency>
Gradle:
compile group: 'org.neo4j', name: 'neo4j', version: '3.4.8' testCompile group: 'org.neo4j', name: 'neo4j-ogm-test', version: '3.1.3' testCompile group: 'com.graphaware.neo4j', name: 'tests', version: '3.4.8.52' testCompile group: 'junit', name: 'junit', version: '4.12'
Our next step will be to create the context. For that let’s create the PersistenceContext class in our test directory:
@Configuration @EnableNeo4jRepositories("com.neo4j.f1.repositories") @EnableTransactionManagement @ComponentScan("com.neo4j.f1") public class PersistenceContext { @Bean public org.neo4j.ogm.config.Configuration configuration() { org.neo4j.ogm.config.Configuration.Builder builder = new org.neo4j.ogm.config.Configuration.Builder(); return builder.build(); } @Bean public SessionFactory sessionFactory() { return new SessionFactory(configuration(), "com.neo4j.f1.domain"); } @Bean public PlatformTransactionManager transactionManager() { return new Neo4jTransactionManager(sessionFactory()); } }
Make sure to provide the valid packages directories where needed!!!
Let’s now create a test class and the actual tests:
@ContextConfiguration(classes = {PersistenceContext.class}) @RunWith(SpringRunner.class) public class ResultTests { @Autowired ResultRepository resultRepository; @Before public void setup() { Race italy = new Race("Italy"); Race singapore = new Race("Singapore"); Driver hamilton = new Driver("Lewis Hamilton", 44); Driver vettel = new Driver("Sebastian Vettel", 5); Driver alonso = new Driver("Fernando Alonso", 14); resultRepository.save(new Result(1, hamilton, italy)); resultRepository.save(new Result(2, vettel, italy)); resultRepository.save(new Result(13,alonso,italy)); resultRepository.save(new Result(4, hamilton, singapore)); resultRepository.save(new Result(1, vettel, singapore)); resultRepository.save(new Result(12,alonso,singapore)); } @Test public void checkIfAllAreSaved() { List<Result> results = resultRepository.findAll(); Assert.assertEquals(6,results.size()); } @Test public void checkIfCustomQueryHasRightSize() { List<Driver> winners = resultRepository.getByResult(1); Assert.assertEquals(2,winners.size()); } @After public void cleanUp() { resultRepository.deleteAll(); } }
Conclusions
I hope I have shown how nice and easy is to use neo4j. If you would like to broaden your knowledge, more tutorials you can find at https://neo4j.com/docs/developer-manual/3.4/