Upgrading to Spring Data Neo4j 4.2

· 15 min read

Previous articles have shown you how easy using Spring with Neo4j can be. Now the next release of Spring Data Neo4j (SDN), we are going to make this even easier!

This post is first in a series that will explore the exciting improvements that will be available in the first candidate release of SDN 4.2, but these are already available in the current snapshot.

The main highlights we will be covering include:

  • Brand new Spring configuration method.
  • Tighter integration into Spring transactions with support for transactional event listeners and read only transactions.
  • Paging and sorting support for custom queries.
  • Ability to attach multiple labels to nodes.
  • Support for spatial derived finders, indexes and unique lookup & merge keys (coming soon).

And on top of this we have added lots of improvements to the developer documentation and tons of bug fixes.

In this post we will be looking at configuration and what to expect from Spring transactions.

Configuration

Spring Data Neo4j 4.2.0.BUILD-SNAPSHOT has introduced some fundamental improvements to the way you build your Spring based applications. Since version 4.0, developers have really only been able to use SDN if they were using Boot, WebMVC or a stand alone Spring application. SDN 4.2 now allows developers to integrate Neo4j on ANY architecture built on Spring. Let’s see how.

Get the new dependencies

To try out these new features in your apps, you’ll need to download the correct version of SDN from Maven central.

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-neo4j</artifactId>
    <version>4.2.0.BUILD-SNAPSHOT</version>
</dependency>

...

<!-- used for snapshot releases -->
<repository>
    <id>spring-snapshots</id>
    <name>Spring Snapshots</name>
    <url>https://repo.spring.io/libs-snapshot</url>
    <snapshots>
        <enabled>true</enabled>
    </snapshots>
</repository>

By default, SDN will use the Http driver to connect to Neo4j and you don’t need to declare it as a separate dependency in your pom. If you want to use the embedded or Bolt drivers in your production application, you must add the following dependencies as well.

<!-- add this dependency if you want to use the embedded driver -->
<dependency>
    <groupId>org.neo4j</groupId>
    <artifactId>neo4j-ogm-embedded-driver</artifactId>
    <version>{ogm-version}</version>
</dependency>

<!-- add this dependency if you want to use the Bolt driver -->
<dependency>
    <groupId>org.neo4j</groupId>
    <artifactId>neo4j-ogm-bolt-driver</artifactId>
    <version>{ogm-version}</version>
</dependency>

While we aim to release things as bug free as possible there are occasional updates that need to be made before reaching the RELEASE milestone. Should you run into any issues it is worth checking out the Github, Stack Overflow and our forums in case there are work arounds and if it’s a show stopper remember to raise a bug. Remember to use the appropriate Spring repository to get the right versions.

If you use Gradle, then dependencies are more of less the same as Maven:

dependencies {
    compile 'org.springframework.data:spring-data-neo4j:4.2.0.BUILD-SNAPSHOT'

    # add this dependency if you want to use the embedded driver
    compile 'org.neo4j:neo4j-ogm-embedded-driver:{ogm-version}'

    # add this dependency if you want to use the Bolt driver
    compile 'org.neo4j:neo4j-ogm-bolt-driver:{ogm-version}'

}
repositories {
   # used for snapshot releases
   maven { url "https://repo.spring.io/libs-snapshot" }
}

Now you have upgraded let’s take a look at how to configure everything.

Configuring a Simple Spring Application

Neo4jConfiguration has been deprecated and you no longer have to subclass it. Even better, you don’t need to define any beans that define Sessions or their Scope. To get a bare bones Spring based application working, this is all you’ll need:

@Configuration
@EnableNeo4jRepositories(basePackages = "org.neo4j.example.repository")
@EnableTransactionManagement
public class MyNeo4jConfiguration {

    @Bean
    public SessionFactory sessionFactory() {
        // with domain entity base package(s)
        return new SessionFactory("org.neo4j.example.domain");
    }

    @Bean
    public Neo4jTransactionManager transactionManager() {
        return new Neo4jTransactionManager(sessionFactory());
    }

}

Let’s look at this in a little more detail.

  • Notice how this configuration does not extend any particular class. This flexibility means you are free to configure your application in the way that works best for your app; whether that’s all in one file or split across several modules.
  • @EnableNeo4jRepositories will make sure any SDN repositories you have built get special treatment.
  • Bean names are important! The new configuration expects the name: sessionFactory and transactionManager. You can set this in two ways; annotate the method with @Bean and make sure the method name doesn’t have get or anything in front of it; or name the method whatever you like and set the name in the Bean annotation.
  • Note that you are also add an OGM Configuration bean and pass that into the session factory if you need that feature. By default and as per previous releases, the SessionFactory will look for ogm.properties on the root of the classpath to configure the correct driver.

Configuring Web Applications

The simplest way to get a Spring web application running is by using Spring WebMVC. All you need to add to get one up and running is to add a few more lines to the base configuration we saw above:

@Configuration
@EnableWebMvc
@ComponentScan({"org.neo4j.example.web"})
@EnableNeo4jRepositories("org.neo4j.example.repository")
@EnableTransactionManagement
public class MyWebAppConfiguration extends WebMvcConfigurerAdapter {

    @Bean
    public OpenSessionInViewInterceptor openSessionInViewInterceptor() {
        OpenSessionInViewInterceptor openSessionInViewInterceptor =
            new OpenSessionInViewInterceptor();
        openSessionInViewInterceptor.setSessionFactory(sessionFactory());
        return openSessionInViewInterceptor;
    }

    @Override
        public void addInterceptors(InterceptorRegistry registry) {
        registry.addWebRequestInterceptor(openSessionInViewInterceptor());
    }

    @Bean
    public SessionFactory sessionFactory() {
        // with domain entity base package(s)
        return new SessionFactory("org.neo4j.example.domain");
    }

    @Bean
    public Neo4jTransactionManager transactionManager() throws Exception {
        return new Neo4jTransactionManager(sessionFactory());
    }
}

Let’s take a deeper look at what’s going on.

  • @EnableWebMvc enables Spring Data integration with Spring WebMVC. You can read about that here.
  • @ComponentScan will pick up any Spring components you have defined like @Services or any @Component in general and inject beans into things marked as @Autowired or marked as @Transactional.
  • Note in this configuration we have subclassed WebMvcConfigurerAdapter to enable Spring MVC itself.
  • OpenSessionInViewInterceptor essentially will run one session-per-request using the SessionFactory you have defined. We then set this into the WebMVC framework by calling: InterceptorRegistry.addWebRequestInterceptor().

But what if you don’t want to use Spring WebMVC?

There is always some form of Spring integration out there for most web projects (e.g. Jersey) but if your favourite one isn’t covered don’t despair! We have a solution for that scenario too!

As long as the web project can run in a Java Servlet 3.x+ Container, you can configure a Servlet filter with Spring’s AbstractAnnotationConfigDispatcherServletInitializer. The configuration below will open a new session for every web request, just like the interceptor above, then automatically close it on completion. SDN provides the OpenSessionInViewFilter to do this:

public class MyAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

  @Override
  protected void customizeRegistration(ServletRegistration.Dynamic registration) {
      registration.setInitParameter("throwExceptionIfNoHandlerFound", "true");
  }

  @Override
  protected Class<?>[] getRootConfigClasses() {
      return new Class[] {ApplicationConfiguration.class} // if you have broken up your configuration, this points to your non web application config/s.
  }

  @Override
  protected Class<?>[] getServletConfigClasses() {
      throw new Class[] {WebConfiguration.class}; // a configuration that extends the WebMvcConfigurerAdapter as seen above.
  }

  @Override
  protected String[] getServletMappings() {
    return new String[] {"/"};
  }

  protected Filter[] getServletFilters() {
    return return new Filter[] {new OpenSessionInViewFilter()};
  }

}

Migrating from 4.0/4.1 to 4.2

In order to use the “new” version you’ll need to change several things other than the configuration.

  1. Remove any subclassing of Neo4jConfiguration
  2. Define the sessionFactory bean with an instance of SessionFactory and the transactionManager bean with an instance of Neo4jTransactionManager. Be sure to pass the SessionBean into the constructor for the transaction manager. 1.

Support for Spring Boot

Unfortunately, due to the Spring release schedule you cannot use the current or upcoming versions of Spring Boot and expect to use these new improvements out of the box. If you are still using the old configuration though you are in luck; you can simply specify update your SDN version to 4.2.0 in your maven/gradle dependencies.

If you like the new features and want to use them with Spring Boot right now, don’t worry! You will just have to do a a fraction more work.

Remove the reference to the old Neo4j Configuration where you define your Spring Boot application:

@Controller
@SpringBootApplication(exclude={Neo4jDataAutoConfiguration.class})
public class Application {
    public static main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

And then add the the MyNeo4jConfiguration you saw above to a place where boot can scan it and you are all set!

We’ve wired up an example of SDN with Spring Boot for you to try and use as a base for your own projects here.

Transactions and Events

Changes to Spring Transactional Support

We have completely overhauled how Transactions work in SDN 4.2. Neo4jTransactionManager now extends AbstractPlatformTransactionManager and all the goodies that come along with it!

Demarcating @Transactional is required for all methods that interact with SDN. If you use SDN 4.2 repositories though this is done for you. So if you are for example just doing a read operation you won’t need to define a transaction. That said, it is strongly recommended that you always annotate any service boundaries to the database with a @Transactional annotation. This way all your code for that method will always run in one transaction, even if you add a write operation later on.

SDN 4.2 currently only supports TransactionPropagation.REQUIRED transactions. This is due to having to support the various protocols (http, bolt, embedded) and there not being an easy way to “nest” transactions. That said some of the drivers are capable of using a different propagation. However, behaviour with other types of propagation has not been tested in these types of ways can result in unreliable results.

Read only Transactions

SDN 4.2 also introduces support for read only transactions.

You can start a read only transaction by marking a class or method with @Transactional(readOnly=true).

Note that if you open a read only transaction from, for example a service method, and then call a mutating method that is marked as read/write your transaction semantics will always be defined by the outermost transaction. Be wary!

Events

Application Events

We have deprecated all of the event classes in org.springframework.data.neo4j.event and you might be wondering why. Spring 4.2 introduced a new way of building event handling courtesy of a new annotation model to consume events as well as allowing developers to publish any object as an event whilst not you to extend from ApplicationEvent.

We saw above how we can listen to events during the phases of a Transaction and the underlying Neo4j OGM also exposes an event model for you to tap into directly. Let’s try and listen out for pre save events from the OGM framework in SDN.

First we define an event listener component that will perform the code we want to execute once the event is fired.

import org.neo4j.ogm.session.event.Event;

@Component
public class PreSaveEventListener {

    @EventListener
    public void onPreSave(PreSaveEvent event) {
        // do something!
    }
}

You can see this is just a POJO with some Spring annotations. Your handler code for the event would be defined in the onPreSave method. Note that the Event object is native to the Neo4j OGM. Since Spring can publish any type of object, not having to define a wrapper object is a huge plus.

Next we need a way to publish these events.

@Component
public class PreSaveEventPublisher extends EventListenerAdapter {

    @Autowired
    private final ApplicationEventPublisher publisher;

    @Override
    public void onPreSave(Event event) {
        this.publisher.publishEvent((new PreSaveEvent(event));
    }

}

Again this is just a Spring @Component but we have defined a few extra things.

  • We extend EventListenerAdapter which is a simple no-op adapter for all EventListeners in Neo4j OGM. We then override the onPreSave method to get the behaviour we want to capture. You could also use this same class to capture the other types of events that the Neo4jOGM can notify you about:
    • Pre Save events
    • Post Save events
    • Pre Delete events
    • Post Delete events
  • We @Autowired in the Spring infrastructure at this point using field injection. We will be taking the events fired from the Neo4j OGM and then publishing them into Spring through the ApplicationEventPublisher.
  • We then override the desired method in the EventListener and publish that event via the ApplicationEventPublisher.

Great! So we have defined our publishing and consuming code but there is one more thing left to do and that is notify the Neo4j OGM that we are interested in it’s lifecycle. Currently we don’t have any easy way to do that so you will need to configure this at the Session level. You might have noticed that we no longer have declarative access to Session anymore though but don’t fear! We can either Subclass SessionFactory or simply override it in the @Bean definition. We’ll do the latter here:

@Configuration
@ComponentScan({"my.example.events"})
@EnableNeo4jRepositories(basePackages = "my.example.repsotory")
@EnableTransactionManagement
public class MyConfiguration {

	@Bean
	public Neo4jTransactionManager transactionManager() throws Exception {
		return new Neo4jTransactionManager(sessionFactory());
	}

	@Bean
	public SessionFactory sessionFactory() {
		return new SessionFactory("my.example.domain"){

			@Override
			public Session openSession() {
				Session session = super.openSession();
				session.register(preSaveEventPublisher());
				return session;
			}

		};
	}

	@Bean
	public PreSaveEventPublisher preSaveEventPublisher() {
		return new PreSaveEventPublisher();
	}
}

And that’s it! Pretty cool hey?

Remember to include the package your event infrastructure is in (those classes marked with @Component or @Service etc.) in your @ComponentScan.

If more users demand it we may look to add better event registration support to the OGM but right now this is a pretty easy way of wiring up Spring events with the OGM.

Transaction Listener Events

Again, thanks to the changes to the event model in Spring 4.2, you can also be notified of events during the lifecycle of a Transaction.

Event listeners can be bound to the various stages of the Transaction Lifecycle. Similarly to ApplicationEvents All you need to is create a @Component and annotate the handling method with: @TransactionalEventListener. The phases that you can be notified about are:

  • After commit (Default)
  • Before commit
  • After rollback
  • After completion

Here is an example of how to listen to events after a transaction has rolled bacK:

@Component
public class MyComponent {

    @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
    public void handleAfterRollback(TaskScheduledEvent event) {
        ...
    }

}

And that’s it! Your handler code will be called every time a rollback occurs.

You can find out more about both transaction and application events on the Spring blog.

Wrap Up

So we’ve seen the changes to configuration and the improvements to transaction management in this blog post. Next time we will talk about using labels and spatial derived finders.

Happy Spring coding!

Mark Angrish