Spring Data Neo4j 3.0 migration

As I’m still using Neo4j 1.9 in a project, it was about time to make a switch to Neo4j 2.0. It offers a lot of great features which I want to explore. I’m a Spring framework enthusiast and I really like the entity approach of Spring Data Neo4j. To support Neo4j 2.0, this library needs to be upgraded as well. The first thing I did, was change the maven dependencies from

<dependency>
    <groupId>org.neo4j</groupId>
    <artifactId>neo4j</artifactId>
    <version>1.9.7</version>
</dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-neo4j-aspects</artifactId>
    <version>2.3.5.RELEASE</version>
</dependency>

to

<dependency>
    <groupId>org.neo4j</groupId>
    <artifactId>neo4j</artifactId>
    <version>2.0.3</version>
</dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-neo4j-aspects</artifactId>
    <version>3.0.2.RELEASE</version>
</dependency>

Ideally this would just work, but as expected, that’s not the case. There are quite a few changes in Neo4j 2.0, so you’ll need to migrate a thing or two. This blog post is a good article that gets you started. I had to take the following steps to make my application run again.

  1. Explicitly define the entity packages
  2. We had to make the entity metadata handling an explicit step in the lifecycle and decided to change the base-package attribute for neo4j:config to be mandatory. It allows for one or more (comma separated) package names to be scanned for entities.

    As I’m using annotation based configuration of my Spring beans, I needed to add the following constructor to my configuration class:

    @Configuration
    @EnableNeo4jRepositories(basePackages = { "com...repository" })
    public class SpringConfiguration extends Neo4jAspectConfiguration {
        
        SpringConfiguration() {
            setBasePackage("com...entity");
        }
    
    }
    

    Note that I’m extending the Neo4jAspectConfiguration class, because I’m using the advanced mapping mode. If you’re using the simple mapping mode, the Neo4jConfiguration class will suit your needs.

  3. Switch to Java 1.7
  4. As Java 1.6 is no longer supported by Neo4j 2.0, your application needs to be built with Java 1.7. In my project, I’m solving this with the maven-compiler-plugin where I explicitly define the Java version.

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                        <version>3.1</version>
                        <configuration>
                            <source>1.7</source>
                            <target>1.7</target>
                        </configuration>
                    </plugin>
                </plugins>
        </pluginManagement>
    </build>
    
  5. Every database operation should be transactional
  6. This is an explicit requirement of Neo4j 2.0. Previously, only write operations needed to be wrapped into a transaction. As I frequently perform read queries in my application, I needed to add Spring’s @Transactional annotation on a couple of methods in classes of stereotype Service. In order to get the annotation to work, you should add the @EnableTransactionManagement annotation on a Configuration class. The Neo4jConfiguration class conveniently defines a PlatformTransactionManager for us.
    I’m using an InitializingBean as well and the @Transactional annotation doesn’t work on its afterPropertiesSet() method. This is expected behavior as the class hasn’t been proxied yet. A decent workaround is to use the native Neo4j transaction API that can e.g. be accessed via the Neo4jTemplate. Here’s a code example as illustration:

    @Service
    public class MyService implements InitializingBean, MyServiceInterface {
    
        @Autowired
        private Neo4jTemplate template;
      
        @SuppressWarnings("deprecation")
        @Override
        public void afterPropertiesSet() throws Exception {
            try (final Transaction tx = template.getGraphDatabaseService().beginTx()) {
                final IndexProvider indexProvider = template.getInfrastructure().getIndexProvider();
                try {
                    indexProvider.getIndex("custom_index");
                } catch (final NoSuchIndexException e) {
                    indexProvider.createIndex(Node.class, "custom_index", IndexType.FULLTEXT);
                }
                tx.success();
            }
        }
    
        @Override
        @Transactional
        public void thisIsTransactional() {
        }
    
    }
    
  7. Refactor code that contains removed SDN methods
  8. Lucky for me, I only came across one method that has been removed in the 3.0 release of Spring Data Neo4j. AFAIK it hadn’t been deprecated in a previous release, so I was a bit surprised to see this error. I’m talking about the Neo4jTemplate#queryEngineFor(QueryType type) method. As you’ll see, it was an easy fix and actually, it shouldn’t have been used in the first place.
    template.queryEngineFor(QueryType.Cypher).query("start n=node(*) return count(n)", null);
    becomes
    template.query("start n=node(*) return count(n)", null);

  9. Upgrade Cypher DSL
  10. Cypher DSL is a neat little library that allows you to write beautiful query code in a DSL way. Using this DSL, you can dynamically compose your query based on e.g. user input. So no need to concatenate a bunch of Strings. To be able to use new Cypher keywords, I had to upgrade to the latest version 2.0.1. I noticed that many of the Cypher 2.0 features are included in the library, although it seems to miss a couple of vital keywords.

    <dependency>
        <groupId>org.neo4j</groupId>
        <artifactId>neo4j-cypher-dsl</artifactId>
        <version>2.0.1</version>
    </dependency>
    
  11. Cypher changes
  12. Migrating to Neo4j 2.0 does impact your Cypher queries. Another good thing about Cypher DSL is that it shows compilation errors when certain syntax isn’t supported anymore after a library upgrade. I had errors because the “?” and “!” characters behind property names aren’t allowed anymore. The following example query

    START u=node:__types__("className:com...User") 
    WHERE u.since? > 2010
    RETURN u
    

    would need to be changed to something similar to

    START u=node:__types__("className:com...User") 
    WHERE coalesce(u.since, 2014) > 2010
    RETURN u
    

    Mind that I still don’t use labels intentionally. I’ll get back on that soon. In Cypher DSL, the second query looks like

    import static org.neo4j.cypherdsl.CypherQuery.*;
    
    start(query("u", "__types__", "className:com...User"))
        .where(coalesce(identifier("u").property("year"), literal(2014)).gt(2010))
        .return(dentifier("u"));
    
  13. Keep legacy indexes
  14. I heavily rely on the Lucene indexes for e.g. wildcard searches. These indexes are considered legacy in Neo4j 2.0 in favor of the newly created Label (schema) indexes. Spring Data Neo4j also follows this approach and will use schema indexes by default from version 3.0. Schema indexes do not yet support wildcard searches so moving to this new approach is no option for me. I’m a big fan of the new Label approach, but until Labels are fully implemented, I’m going to have to skip them. An alternative would be the use regex but it’s way slower than Lucene.
    Pre 3.0 versions of SDN used a “__type__” property on nodes and relationships to know which entity type they belonged to. A “__types__” Lucene index would be created as well so you could easily find entities based on their class name. As I heavily rely on this behavior, I needed to reconfigure SDN to override its default Label strategy. A potential way to do this is as follows:

    @Configuration
    public class SpringConfiguration extends Neo4jAspectConfiguration {
      
        // use legacy type representation (Stategy.Labeled is the default)
        @Override
        @Bean
        public TypeRepresentationStrategyFactory typeRepresentationStrategyFactory() throws Exception {
            return new TypeRepresentationStrategyFactory(graphDatabase(), Strategy.Indexed);
        }
        
        // Use FQN by default
        @Override
        @Bean
        protected EntityAlias entityAlias() {
            return new ClassNameAlias();
        }
    
    }
    

Now that the legacy behavior is re-enabled, I can successfully run my unit tests again. A big plus is that there’s no need to migrate my existing data. You’ll also notice that all classes related to legacy indexing are deprecated in SDN, but until Label indexes are fully implemented, that’s something I’ll have to live with (I’ll get over it ;-)).

Having switched to Neo4j 2.0, I’m getting the power of Cypher 2.0 which is more complete than its predecessor and if I’m not mistaken, should be slightly faster as well.

If you have any remarks or question, feel free to leave a constructive comment. Happy graphing (or whatknot…) 🙂

Advertisements

One thought on “Spring Data Neo4j 3.0 migration

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s