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