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:
- 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>
- 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
- 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>
- 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