Matthew Shaw
Matthew Shaw

Reputation: 108

Alternative to the Neo4jTransactionManager to honour REQUIRES_NEW isolation

I'm using spring boot 2 and the spring data neo4j dependency (which I think brings sdn 5.x) in addition to neo4j-ogm 3.1.1 to manage my persistence. I have noted that the default Neo4jTransactionManager does not support nested transactions or requires new propagation.

What I would like to do is having my service method perform essentially multiple steps in seperate transactions.

In order to achieve what I want I have resorted to spring's async / multi threading support to effectively force a new session and transaction around the steps in my service method, shown below. What I would like to know, is there a better way to solve this?

I feel as though I should be able to easily create seperate transactions / units of work but the out of the box spring data neo4j solution is restricting this.

My service method, the first step I wish to isolate is already in a seperate service marked here by the call connectionService.deleteDiagramConnections(diagram.getId());

@Retryable(value = TransientException.class,exceptionExpression="#{message.contains('RWLock')}", maxAttempts = 5)
    public Diagram update(final Diagram diagram) throws GUMLException {

        AtomicReference<Diagram> result = new AtomicReference<Diagram>();
        AtomicReference<Object> isComplete = new AtomicReference<Object>();
        isComplete.set(false);

        ListenableFuture future = connectionService.deleteDiagramConnections(diagram.getId());
        future.addCallback(new ListenableFutureCallback() {
            @Override
            public void onFailure(Throwable throwable) {
                logger.error(throwable);
            }

            @Override
            public void onSuccess(Object o) {

                for (Connection connection : diagram.getConnections()) {
                    connection.setId(null);
                    if (connection.getId() != null && connection.getMoveablePoints() != null) {
                        for (MoveablePoint mp : connection.getMoveablePoints()) {
                            mp.setId(null);
                        }
                    }
                }
                isComplete.set(true);



            }
        });


        while ((boolean)isComplete.get() == false) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new GUMLException(Severity.ERROR, e.getMessage());
            }
            if ((boolean)isComplete.get() == true)
                result.set(umlDiagramRepository.save(diagram));

        }

        return result.get();

    }

The connection service:

@Async
    @Override
    public ListenableFuture<String> deleteDiagramConnections(long diagramId) throws GUMLException {

        connectionRepository.deleteDiagramConnections(diagramId);
        return new AsyncResult<String>("delete complete");
    }

Here is my test app config

@org.springframework.context.annotation.Configuration
@ComponentScan(basePackages = "au.com.guml", lazyInit = true)
@EnableTransactionManagement
@EnableAsync
public class TestConfig {

    @Bean
    public org.neo4j.ogm.config.Configuration configuration() {
        org.neo4j.ogm.config.Configuration configuration = new org.neo4j.ogm.config.Configuration.Builder()
                .uri("bolt://localhost")
                .credentials("neo4j", "password")
                .build();
        return configuration;
    }

//    @Bean
//    public org.neo4j.ogm.config.Configuration configuration() {
//        org.neo4j.ogm.config.Configuration configuration = new org.neo4j.ogm.config.Configuration.Builder()
//                .uri("http://neo4j:password@localhost:7474")
//                .build();
//        return configuration;
//    }

//    @Bean
//    public Configuration getConfiguration() {
//
//        Configuration config = new Configuration();
//        config
//                .driverConfiguration()
//                .setDriverClassName("org.neo4j.ogm.drivers.http.driver.HttpDriver")
//                .setURI("http://neo4j:password@localhost:7474")
//                .setCredentials("neo4j","password");
//
//        return config;
//    }

    @Bean
    public SessionFactory sessionFactory() {
        // with domain entity base package(s)
        return new SessionFactory(configuration() ,"au.com.guml.domain");
    }


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

//    @Bean
//    public TaskExecutor syncTaskExecutor () {
//        SyncTaskExecutor syncTaskExecutor = new SyncTaskExecutor();
//        return syncTaskExecutor;
//    }

    @Bean
    public TaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(4);
        executor.setMaxPoolSize(4);
        executor.setThreadNamePrefix("default_task_executor_thread");
        executor.initialize();
        return executor;
    }

}

Upvotes: 0

Views: 305

Answers (1)

Matthew Shaw
Matthew Shaw

Reputation: 108

So it turns out all I needed to do was inject the neo4j ogm session into my service and invoke session.getTransaction().commit();

I then set the connection ids to null in my outer service call and they all get added as though they are new.

Upvotes: 1

Related Questions