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. 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 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.
Here is a quick recap of what I did to switch the template app to use Neo4j:
- Added the Neo4j Heroku Add-on:
heroku addons:add neo4j
- Added the Spring Data Neo4j dependencies (optionally you can remove the unused JPA dependencies) to the “pom.xml” Maven build descriptor:
<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” interface to use the Neo4j GraphRepository:
package com.example.service; import com.example.model.Person; import org.springframework.data.neo4j.repository.GraphRepository; public interface PersonService extends GraphRepository<Person> { }
- Removed the unneeded “src/main/java/com/example/service/PersonServiceImpl.java” DAO.
- Modified the “src/main/java/com/example/model/Person.java” POJO to be a @NodeEntity (instead of JPA Entity) and switched the “id” primary key property to be a Long annotated as a @GraphId:
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” Spring MVC controller to use the new “PersonService”, take a Long parameter in the “deletePerson” method, and make the “deletePerson” and “addPerson” methods transactional:
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/"; } }
- Then I modified the “src/main/resources/applicationContext.xml” 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 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:
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.
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. :)
About James Ward
James Ward (www.jamesward.com) works for Typesafe where he teaches developers the Typesafe Stack (Play Framework, Scala, and Akka) . James frequently presents at conferences around the world such as JavaOne, Devoxx, and many other Java get-togethers. Along with Bruce Eckel, James co-authored First Steps in Flex. He has also published numerous screencasts, blogs, and technical articles. Starting with Pascal and Assembly in the 80′s, James found his passion for writing code. Beginning in the 90′s he began doing web development with HTML, Perl/CGI, then Java. After building a Flex and Java based customer service portal in 2004 for Pillar Data Systems he became a Technical Evangelist for Flex at Adobe. In 2011 James became a Principal Developer Evangelist at Salesforce.com where he taught developers how to deploy apps on the cloud with Heroku. James Tweets as @_JamesWard and posts code at github.com/jamesward.
More About James »Northern Virginia Software Symposium
November 1 - 3, 2013
Reston, VA
Current Topics on the NFJS Tour
- Core Java, JEE
- Dynamic Languages: Groovy, JRuby, Scala, Clojure
- RESTful Web Apps
- Frameworks: Hibernate, Grails, Spring, JSF, GWT, more
- Agility
- Test Driven Design
- Security
- Ajax, Flex, RIA
Why Attend the NFJS Tour?
- » Cutting-Edge Technologies
- » Agile Practices
- » Peer Exchange
Current Topics:
- Languages on the JVM: Scala, Groovy, Clojure
- Enterprise Java
- Core Java, Java 7
- Agility
- Testing: Geb, Spock, Easyb
- REST
- NoSQL: MongoDB, Cassandra
- Hadoop
- Spring 3
- Automation Tools: Git, Hudson, Sonar
- HTML5, Ajax, jQuery, Usability
- Mobile Applications - iPhone and Android
- More...
NFJS, the Magazine
May Issue Now AvailableOn the road to learning
by Raju GandhiRefactoring to Modularity
by Kirk KnoernschildRESTful Groovy
by Kenneth KousenGetting Started with D3.js
by Brian Sletten