Transactions with Spring boot

Hi, We are using Spring data Neo4j with spring boot 2 in our project. We faced issues when we were working with spring transaction management w.r.t Neo4j.

A method was annotated with @Transactional(rollbackFor = Exception.class), method logic involved persisting two or more models. After the first two models were saved, we intentionally threw an exception to check if rollback was happening but found out that the first two models weren't rolled back.

After quite some research on the internet, I found that Spring data neo4j doesn't support transactions in that way. Spring data neo4j uses bolt protocol to connect to the database and the neo4j server is running on a different port than the server.

Is it true that transactions aren't supported in SDN or am I missing something?

If there's a way to handle transactions using spring features, what is the recommended way of handling it? Basically I want transactions to run in a similar way when we have any other database such POSTGRES

1 Like

Hi. We are using spring-boot-starter-data-neo4j v2.1.6 and it works well, just the same as with our sql databases (rollback on exception and @ Rollback unless otherwise annotated). Define your own annotation like this:

package com.yourpackage.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.transaction.annotation.Transactional;


@ ({ElementType.METHOD, ElementType.TYPE})
@ Retention(RetentionPolicy.RUNTIME)
@ Inherited
@ Transactional(transactionManager="neo4jTransactionManager")
public @ interface Neo4jTransactional {
}

Then define a spring @ Configuration like:

package com.yourpackage.config;

import javax.inject.Inject;

import org.neo4j.ogm.session.SessionFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
import org.springframework.data.neo4j.repository.config.Neo4jMappingContextFactoryBean;
import org.springframework.data.neo4j.transaction.Neo4jTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import com.yourpackage.support.SessionFactoryBuilder;


@ Configuration
@ EnableTransactionManagement
@ EnableNeo4jRepositories(
    basePackages = "com.yourpackage.jpa.repository",
    sessionFactoryRef="neo4jSessionFactory",
    transactionManagerRef="neo4jTransactionManager",
    sessionBeanName="neo4jSession",
    mappingContextBeanName="neo4jMappingContextFactory"
)
public class Neo4jConfiguration {

    @ Inject Environment environment;

    @ Bean
    public SessionFactory neo4jSessionFactory() {
        return new SessionFactoryBuilder(environment, "neo4j").build();
    }

    @ Bean
    public Neo4jTransactionManager neo4jTransactionManager() {
        return new Neo4jTransactionManager(neo4jSessionFactory());
    }

    @ Bean
    public Neo4jMappingContextFactoryBean neo4jMappingContextFactory() {
        return new Neo4jMappingContextFactoryBean("neo4jSessionFactory");
    }

}

Add a SessionFactoryBuilder:

package com.yourpackage.support;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

import org.neo4j.ogm.config.Configuration;
import org.neo4j.ogm.config.ConfigurationSource;
import org.neo4j.ogm.session.SessionFactory;
import org.springframework.core.env.Environment;

public class SessionFactoryBuilder {

    private final Environment environment;
    private final String sessionFactoryName;

    public SessionFactoryBuilder(Environment environment, String sessionFactoryName) {
        this.environment = environment;
        this.sessionFactoryName = sessionFactoryName;
    }

    public SessionFactory build() {
        SessionFactory sessionFactory = new SessionFactory(neo4jConfiguration(), buildPackagesToScan());

        return sessionFactory;
    }

    protected Configuration neo4jConfiguration() {
        ConfigurationSource configurationSource = new MapConfigurationSource(PropertiesUtils.getChildValueMap(environment, sessionFactoryName));
        return new Configuration.Builder(configurationSource)
            .build()
        ;
    }

    protected String[] buildPackagesToScan() {
        //find all properties that start with sessionFactoryName + ".packagesToScan"
        //parse each one as a csv list and add them to the list
        List<String> packagesToScan = new ArrayList<>();
        Map<String,String> packagesToScanValuesMap = PropertiesUtils.getChildValueMap(environment, sessionFactoryName + "SessionFactory.packagesToScan");
        for (String packagesToScanValue : packagesToScanValuesMap.values()) {
            packagesToScan.addAll(StringUtils.parseDelimitedData(packagesToScanValue));
        }
        return packagesToScan.toArray(new String[0]);
    }

}

And place the packagesToScan property in your spring properties file containing the path(s) to all entity objects, e.g. neo4jSessionFactory.packagesToScan=com.yourpackage.entity

Then place the @ Neo4jTransactional annotation on any class or method that you want managed. It will rollback or commit based on success or exception, or @ Commit or @ Rollback if specified.

  • Note I placed a space after each @ symbol that you will have to remove. The posting tool would not allow what I had.