
org.neo4j.helpers.TransactionTemplate Maven / Gradle / Ivy
/*
* Copyright (c) 2002-2016 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.neo4j.helpers;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import org.neo4j.function.Predicates;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.TransactionFailureException;
import org.neo4j.graphdb.TransactionTerminatedException;
import org.neo4j.graphdb.TransientFailureException;
import static org.neo4j.function.Predicates.any;
/**
* Neo4j transaction template that automates the retry-on-exception logic. It uses the builder
* pattern for configuration, with copy-semantics, so you can iteratively build up instances for
* different scenarios.
*
* First instantiate and configure the template using the fluent API methods, and then
* invoke execute which will begin/commit transactions in a loop for the specified number of times.
*
* By default all exceptions (except Errors and TransactionTerminatedException) cause a retry,
* and the monitor does nothing, but these can be overridden with custom behavior.
* A bit more narrow and typical exception to retry on is {@link TransientFailureException},
* which aims to represent exceptions that are most likely to succeed after a retry.
*/
public class TransactionTemplate
{
public interface Monitor
{
void failure( Throwable ex );
void failed( Throwable ex );
void retrying();
class Adapter implements Monitor
{
@Override
public void failure( Throwable ex )
{
}
@Override
public void failed( Throwable ex )
{
}
@Override
public void retrying()
{
}
}
}
private final GraphDatabaseService gds;
private final Monitor monitor;
private final int retries;
private final long backoff;
private final Predicate retryPredicate;
public TransactionTemplate()
{
this( null, new Monitor.Adapter(), 0, 0, any(
Predicates.instanceOf( Error.class ),
Predicates.instanceOf( TransactionTerminatedException.class ) ).negate() );
}
public TransactionTemplate( GraphDatabaseService gds, Monitor monitor, int retries,
long backoff, Predicate retryPredicate )
{
this.gds = gds;
this.monitor = monitor;
this.retries = retries;
this.backoff = backoff;
this.retryPredicate = retryPredicate;
}
public TransactionTemplate with( GraphDatabaseService gds )
{
return new TransactionTemplate( gds, monitor, retries, backoff, retryPredicate );
}
public TransactionTemplate retries( int retries )
{
return new TransactionTemplate( gds, monitor, retries, backoff, retryPredicate );
}
public TransactionTemplate backoff( long backoff, TimeUnit unit )
{
return new TransactionTemplate( gds, monitor, retries, unit.toMillis( backoff ), retryPredicate );
}
public TransactionTemplate monitor( Monitor monitor )
{
return new TransactionTemplate( gds, monitor, retries, backoff, retryPredicate );
}
public TransactionTemplate retryOn( Predicate retryPredicate )
{
return new TransactionTemplate( gds, monitor, retries, backoff, retryPredicate );
}
public void execute( final Consumer txConsumer )
{
execute( transaction -> {
txConsumer.accept( transaction );
return null;
} );
}
public T execute( Function txFunction )
throws TransactionFailureException
{
Throwable txEx = null;
for ( int i = 0; i < retries; i++ )
{
try ( Transaction tx = gds.beginTx() )
{
T result = txFunction.apply( tx );
tx.success();
return result;
}
catch ( Throwable ex )
{
monitor.failure( ex );
txEx = ex;
if ( !retryPredicate.test( ex ) )
{
break;
}
}
if ( i < retries - 1 )
{
try
{
Thread.sleep( backoff );
}
catch ( InterruptedException e )
{
throw new TransactionFailureException( "Interrupted", e );
}
monitor.retrying();
}
}
if ( txEx instanceof TransactionFailureException )
{
throw ((TransactionFailureException) txEx);
}
else if ( txEx instanceof Error )
{
throw ((Error) txEx);
}
else if ( txEx instanceof RuntimeException )
{
throw ((RuntimeException) txEx);
}
else
{
throw new TransactionFailureException( "Failed", txEx );
}
}
}