Graphs in the Cloud: Spring + Neo4j on Heroku

Posted by: James Ward on 05/14/2012

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:

  1. Added the Neo4j Heroku Add-on:
    heroku addons:add neo4j
  2. 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>
  3. 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> {
     
     
    }
  4. Removed the unneeded “src/main/java/com/example/service/PersonServiceImpl.java” DAO.
  5. 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
  6. 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/";
        }
    }
  7. 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>
  8. 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

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
View Event Details »

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...
Learn More »

NFJS, the Magazine

May Issue Now Available
  • On the road to learning

    by Raju Gandhi
  • Refactoring to Modularity

    by Kirk Knoernschild
  • RESTful Groovy

    by Kenneth Kousen
  • Getting Started with D3.js

    by Brian Sletten
Learn More »