Previousarticles 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'#addthisdependencyifyouwanttousetheembeddeddrivercompile'org.neo4j:neo4j-ogm-embedded-driver:{ogm-version}'#addthisdependencyifyouwanttousetheBoltdrivercompile'org.neo4j:neo4j-ogm-bolt-driver:{ogm-version}'}repositories{#usedforsnapshotreleasesmaven{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 Session
s 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")@EnableTransactionManagementpublicclassMyNeo4jConfiguration{@BeanpublicSessionFactorysessionFactory(){// with domain entity base package(s)returnnewSessionFactory("org.neo4j.example.domain"}@BeanpublicNeo4jTransactionManagertransactionManager(){returnnewNeo4jTransactionManager(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
andtransactionManager
. You can set this in two ways; annotate the method with@Bean
and make sure the method name doesn’t haveget
or anything in front of it; or name the method whatever you like and set the name in theBean
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, theSessionFactory
will look forogm.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")@EnableTransactionManagementpublicclassMyWebAppConfigurationextendsWebMvcConfigurerAdapter{@BeanpublicOpenSessionInViewInterceptoropenSessionInViewInterceptor(){OpenSessionInViewInterceptoropenSessionInViewInterceptor=newOpenSessionInViewInterceptoropenSessionInViewInterceptor.setSessionFactory(sessionFactory());returnopenSessionInViewInterceptor}@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){registry.addWebRequestInterceptor(openSessionInViewInterceptor());}@BeanpublicSessionFactorysessionFactory(){// with domain entity base package(s)returnnewSessionFactory("org.neo4j.example.domain"}@BeanpublicNeo4jTransactionManagertransactionManager()throwsException{returnnewNeo4jTransactionManager(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@Service
s 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 theSessionFactory
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:
publicclassMyAppInitializerextendsAbstractAnnotationConfigDispatcherServletInitializer{@OverrideprotectedvoidcustomizeRegistration(ServletRegistration.Dynamicregistration){registration.setInitParameter("throwExceptionIfNoHandlerFound","true"}@OverrideprotectedClass<?>[]getRootConfigClasses(){returnnewClass[]{ApplicationConfiguration.class}// if you have broken up your configuration, this points to your non web application config/s.}@OverrideprotectedClass<?>[]getServletConfigClasses(){thrownewClass[]{WebConfiguration.class// a configuration that extends the WebMvcConfigurerAdapter as seen above.}@OverrideprotectedString[]getServletMappings(){returnnewString[]{"/"}protectedFilter[]getServletFilters(){returnreturnnewFilter[]{newOpenSessionInViewFilter()};}}
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.
- Remove any subclassing of
Neo4jConfiguration
- Define the
sessionFactory
bean with an instance ofSessionFactory
and thetransactionManager
bean with an instance ofNeo4jTransactionManager
. Be sure to pass theSessionBean
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})publicclassApplication{publicstaticmain(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.
importorg.neo4j.ogm.session.event.Event@ComponentpublicclassPreSaveEventListener{@EventListenerpublicvoidonPreSave(PreSaveEventevent){// 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.
@ComponentpublicclassPreSaveEventPublisherextendsEventListenerAdapter{@AutowiredprivatefinalApplicationEventPublisherpublisher@OverridepublicvoidonPreSave(Eventevent){this.publisher.publishEvent((newPreSaveEvent(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 allEventListener
s in Neo4j OGM. We then override theonPreSave
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 theApplicationEventPublisher
. - We then override the desired method in the
EventListener
and publish that event via theApplicationEventPublisher
.
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")@EnableTransactionManagementpublicclassMyConfiguration{@BeanpublicNeo4jTransactionManagertransactionManager()throwsException{returnnewNeo4jTransactionManager(sessionFactory());}@BeanpublicSessionFactorysessionFactory(){returnnewSessionFactory("my.example.domain"){@OverridepublicSessionopenSession(){Sessionsession=super.openSessionsession.register(preSaveEventPublisher());returnsession}}@BeanpublicPreSaveEventPublisherpreSaveEventPublisher(){returnnewPreSaveEventPublisher}}
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 ApplicationEvent
s 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:
@ComponentpublicclassMyComponent{@TransactionalEventListener(phase=TransactionPhase.AFTER_ROLLBACK)publicvoidhandleAfterRollback(TaskScheduledEventevent){...}}
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!