Graphs in the Cloud: Spring + Neo4j on Heroku

Last week I hosted a webinar about running Java apps on Heroku that use the Spring Framework and the [Neo4j graph database][1]. Here is the recording of that webinar:

In the webinar I began by deploying a copy of the Spring MVC + Hibernate template app from [heroku.com/java][2] on Heroku. Then I made a few modifications to the app to switch the persistence from Hibernate / JPA to Neo4j. You can get the [full source code on GitHub][3].

Here is a quick recap of what I did to switch the template app to use Neo4j:

  1. Added the [Neo4j Heroku Add-on][4]: ```bash heroku addons:add neo4j

  2. Added the Spring Data Neo4j dependencies (optionally you can remove the unused JPA dependencies) to the "[pom.xml][5]" Maven build descriptor: ```xml
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-neo4j-rest</artifactId>
    <version>2.0.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>4.2.0.Final</version>
</dependency>
  1. Modified the “[src/main/java/com/example/service/PersonService.java][6]” interface to use the Neo4j GraphRepository: ```java package com.example.service;

import com.example.model.Person; import org.springframework.data.neo4j.repository.GraphRepository;

public interface PersonService extends GraphRepository {

}


  4. Removed the unneeded "src/main/java/com/example/service/PersonServiceImpl.java" DAO.
  5. Modified the "[src/main/java/com/example/model/Person.java][7]" POJO to be a **@NodeEntity** (instead of JPA Entity) and switched the "id" primary key property to be a **Long** annotated as a **@GraphId**: ```java
package com.example.model;

import org.springframework.data.neo4j.annotation.GraphId;
import org.springframework.data.neo4j.annotation.NodeEntity;

@NodeEntity
public class Person {

    @GraphId
    private Long id;

    // the rest is omitted
  1. Modified the “[src/main/java/com/example/controller/PersonController.java][8]” Spring MVC controller to use the new “PersonService”, take a Long parameter in the “deletePerson” method, and make the “deletePerson” and “addPerson” methods transactional: ```java package com.example.controller;

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod;

import com.example.model.Person; import com.example.service.PersonService;

import java.util.Map;

@Controller public class PersonController {

@Autowired
private PersonService personService;

@RequestMapping("/")
public String listPeople(Map<String, Object> map) {
    map.put("person", new Person());
    map.put("peopleList", personService.findAll().iterator());
    return "people";
}

@RequestMapping(value = "/add", method = RequestMethod.POST)
@Transactional
public String addPerson(@ModelAttribute("person") Person person) {
    personService.save(person);
    return "redirect:/people/";
}

@RequestMapping("/delete/{personId}")
@Transactional
public String deletePerson(@PathVariable("personId") Long personId) {
    personService.delete(personId);
    return "redirect:/people/";
}

}


  7. Then I modified the "[src/main/resources/applicationContext.xml][9]" Spring config file to use a file for local Neo4j storage in the "default" profile and then in the "prod" profile the "NEO4J\_REST\_URL", "NEO4J\_LOGIN", and "NEO4J\_PASSWORD" environment variables are used to connect to the Neo4j Heroku add-on service: ```xml
<?xml  version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:neo4j="http://www.springframework.org/schema/data/neo4j"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
                           http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
                           http://www.springframework.org/schema/data/neo4j http://www.springframework.org/schema/data/neo4j/spring-neo4j-2.0.xsd">

    <context:annotation-config />
    <context:spring-configured />
    <context:component-scan base-package="com.example" />

    <neo4j:repositories base-package="com.example.service"/>

    <mvc:annotation-driven/>

    <bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>

    <tx:annotation-driven />

    <beans profile="default">
        <neo4j:config storeDirectory="target/neo4j-db"/>
    </beans>

    <beans profile="prod">
        <bean class="org.springframework.data.neo4j.rest.SpringRestGraphDatabase" id="graphDatabaseService">
            <constructor-arg index="0" value="#{systemEnvironment['NEO4J_REST_URL']}"/>
            <constructor-arg index="1" value="#{systemEnvironment['NEO4J_LOGIN']}"/>
            <constructor-arg index="2" value="#{systemEnvironment['NEO4J_PASSWORD']}"/>
        </bean>
        
        <neo4j:config graphDatabaseService="graphDatabaseService"/>
    </beans>

</beans>
  1. After testing my changes locally (which actually didn’t work in my webinar due to a problem with Eclipse) I committed my changes to the git repo and pushed them to Heroku: ```bash git push heroku master

If you want to just skip to a working example on the cloud, simply follow the instructions in the [project README][10].

Hopefully that helps you get started with Neo4j and Java applications on the cloud!

BTW: If you watched the webinar, you probably noticed that my Maven and Eclipse were misbehaving. Turns out that M2E didn't read my Maven config and all I had to do was right-click on the project, select Maven, and then Update Project Configuration. That got everything back in sync. My excuse for not being able to figure that out during the demo... I usually use IntelliJ IDEA. :)

 [1]: http://neo4j.org/
 [2]: http://heroku.com/java
 [3]: https://github.com/jamesward/hello-java-spring-neo4j
 [4]: https://addons.heroku.com/neo4j
 [5]: https://github.com/jamesward/hello-java-spring-neo4j/blob/master/pom.xml
 [6]: https://github.com/jamesward/hello-java-spring-neo4j/blob/master/src/main/java/com/example/service/PersonService.java
 [7]: https://github.com/jamesward/hello-java-spring-neo4j/blob/master/src/main/java/com/example/model/Person.java
 [8]: https://github.com/jamesward/hello-java-spring-neo4j/blob/master/src/main/java/com/example/controller/PersonController.java
 [9]: https://github.com/jamesward/hello-java-spring-neo4j/blob/master/src/main/resources/applicationContext.xml
 [10]: https://github.com/jamesward/hello-java-spring-neo4j/blob/master/README.md