Skip to content

Commit 17755a3

Browse files
committed
Internalised marking for success in readTx & writeTx
This commit makes `Session#readTransaction()` and `Session#writeTransaction()` automatically commit when transaction was not marked as failure and no exception was thrown. This effectively removes the need to call `tx.success()` as the last statement in a transaction.
1 parent 0035520 commit 17755a3

File tree

5 files changed

+312
-15
lines changed

5 files changed

+312
-15
lines changed

driver/src/main/java/org/neo4j/driver/internal/NetworkSession.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,21 @@ private synchronized <T> T transaction( AccessMode mode, TransactionWork<T> work
253253
{
254254
try ( Transaction tx = beginTransaction( mode ) )
255255
{
256-
return work.execute( tx );
256+
T result;
257+
try
258+
{
259+
result = work.execute( tx );
260+
}
261+
catch ( Throwable t )
262+
{
263+
// mark transaction for failure if the given unit of work threw exception
264+
// this will override any success marks that were made by the unit of work
265+
tx.failure();
266+
throw t;
267+
}
268+
// given unit of work completed successfully, mark transaction for commit
269+
tx.success();
270+
return result;
257271
}
258272
catch ( Throwable newError )
259273
{

driver/src/main/java/org/neo4j/driver/v1/Config.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import org.neo4j.driver.v1.exceptions.ServiceUnavailableException;
3030
import org.neo4j.driver.v1.exceptions.SessionExpiredException;
3131
import org.neo4j.driver.v1.exceptions.TransientException;
32-
import org.neo4j.driver.v1.util.Function;
3332
import org.neo4j.driver.v1.util.Immutable;
3433
import org.neo4j.driver.v1.util.Resource;
3534

@@ -449,10 +448,11 @@ public ConfigBuilder withConnectionTimeout( long value, TimeUnit unit )
449448
}
450449

451450
/**
452-
* Specify the maximum time transactions are allowed to retry via {@link Session#readTransaction(Function)} and
453-
* {@link Session#writeTransaction(Function)} methods. These methods will retry the given unit of work on
454-
* {@link ServiceUnavailableException}, {@link SessionExpiredException} and {@link TransientException} with
455-
* exponential backoff using initial delay of 1 second.
451+
* Specify the maximum time transactions are allowed to retry via
452+
* {@link Session#readTransaction(TransactionWork)} and {@link Session#writeTransaction(TransactionWork)}
453+
* methods. These methods will retry the given unit of work on {@link ServiceUnavailableException},
454+
* {@link SessionExpiredException} and {@link TransientException} with exponential backoff using initial
455+
* delay of 1 second.
456456
* <p>
457457
* Default value is 30 seconds.
458458
*

driver/src/main/java/org/neo4j/driver/v1/Session.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ public interface Session extends Resource, StatementRunner
8484

8585
/**
8686
* Execute given unit of work in a {@link AccessMode#READ read} transaction.
87+
* <p>
88+
* Transaction will automatically be committed unless exception is thrown from the unit of work itself or from
89+
* {@link Transaction#close()} or transaction is explicitly marked for failure via {@link Transaction#failure()}.
8790
*
8891
* @param work the {@link TransactionWork} to be applied to a new read transaction.
8992
* @param <T> the return type of the given unit of work.
@@ -93,6 +96,9 @@ public interface Session extends Resource, StatementRunner
9396

9497
/**
9598
* Execute given unit of work in a {@link AccessMode#WRITE write} transaction.
99+
* <p>
100+
* Transaction will automatically be committed unless exception is thrown from the unit of work itself or from
101+
* {@link Transaction#close()} or transaction is explicitly marked for failure via {@link Transaction#failure()}.
96102
*
97103
* @param work the {@link TransactionWork} to be applied to a new write transaction.
98104
* @param <T> the return type of the given unit of work.

driver/src/test/java/org/neo4j/driver/v1/integration/SessionIT.java

Lines changed: 285 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
import static org.hamcrest.Matchers.greaterThan;
5656
import static org.junit.Assert.assertEquals;
5757
import static org.junit.Assert.assertFalse;
58+
import static org.junit.Assert.assertNotNull;
59+
import static org.junit.Assert.assertNull;
5860
import static org.junit.Assert.assertThat;
5961
import static org.junit.Assert.assertTrue;
6062
import static org.junit.Assert.fail;
@@ -544,28 +546,297 @@ public void writeTxRetriedUntilFailure()
544546
}
545547

546548
@Test
547-
public void writeTxDoesNotCommitWhenMarkedForFailure()
549+
public void readTxCommittedWithoutTxSuccess()
548550
{
549-
try ( Session session = neo4j.driver().session() )
551+
try ( Driver driver = newDriverWithoutRetries();
552+
Session session = driver.session() )
550553
{
551-
int answer = session.writeTransaction( new TransactionWork<Integer>()
554+
assertNull( session.lastBookmark() );
555+
556+
long answer = session.readTransaction( new TransactionWork<Long>()
557+
{
558+
@Override
559+
public Long execute( Transaction tx )
560+
{
561+
return tx.run( "RETURN 42" ).single().get( 0 ).asLong();
562+
}
563+
} );
564+
assertEquals( 42, answer );
565+
566+
// bookmark should be not-null after commit
567+
assertNotNull( session.lastBookmark() );
568+
}
569+
}
570+
571+
@Test
572+
public void writeTxCommittedWithoutTxSuccess()
573+
{
574+
try ( Driver driver = newDriverWithoutRetries() )
575+
{
576+
try ( Session session = driver.session() )
577+
{
578+
long answer = session.writeTransaction( new TransactionWork<Long>()
579+
{
580+
@Override
581+
public Long execute( Transaction tx )
582+
{
583+
return tx.run( "CREATE (:Person {name: 'Thor Odinson'}) RETURN 42" ).single().get( 0 ).asLong();
584+
}
585+
} );
586+
assertEquals( 42, answer );
587+
}
588+
589+
try ( Session session = driver.session() )
590+
{
591+
StatementResult result = session.run( "MATCH (p:Person {name: 'Thor Odinson'}) RETURN count(p)" );
592+
assertEquals( 1, result.single().get( 0 ).asInt() );
593+
}
594+
}
595+
}
596+
597+
@Test
598+
public void readTxRolledBackWithTxFailure()
599+
{
600+
try ( Driver driver = newDriverWithoutRetries();
601+
Session session = driver.session() )
602+
{
603+
assertNull( session.lastBookmark() );
604+
605+
long answer = session.readTransaction( new TransactionWork<Long>()
552606
{
553607
@Override
554-
public Integer execute( Transaction tx )
608+
public Long execute( Transaction tx )
555609
{
556-
tx.run( "CREATE (:Person {name: 'Natasha Romanoff'})" );
610+
StatementResult result = tx.run( "RETURN 42" );
557611
tx.failure();
558-
return 42;
612+
return result.single().get( 0 ).asLong();
559613
}
560614
} );
615+
assertEquals( 42, answer );
616+
617+
// bookmark should remain null after rollback
618+
assertNull( session.lastBookmark() );
619+
}
620+
}
621+
622+
@Test
623+
public void writeTxRolledBackWithTxFailure()
624+
{
625+
try ( Driver driver = newDriverWithoutRetries() )
626+
{
627+
try ( Session session = driver.session() )
628+
{
629+
int answer = session.writeTransaction( new TransactionWork<Integer>()
630+
{
631+
@Override
632+
public Integer execute( Transaction tx )
633+
{
634+
tx.run( "CREATE (:Person {name: 'Natasha Romanoff'})" );
635+
tx.failure();
636+
return 42;
637+
}
638+
} );
639+
640+
assertEquals( 42, answer );
641+
}
642+
643+
try ( Session session = driver.session() )
644+
{
645+
StatementResult result = session.run( "MATCH (p:Person {name: 'Natasha Romanoff'}) RETURN count(p)" );
646+
assertEquals( 0, result.single().get( 0 ).asInt() );
647+
}
648+
}
649+
}
650+
651+
@Test
652+
public void readTxRolledBackWhenExceptionIsThrown()
653+
{
654+
try ( Driver driver = newDriverWithoutRetries();
655+
Session session = driver.session() )
656+
{
657+
assertNull( session.lastBookmark() );
658+
659+
try
660+
{
661+
session.readTransaction( new TransactionWork<Long>()
662+
{
663+
@Override
664+
public Long execute( Transaction tx )
665+
{
666+
StatementResult result = tx.run( "RETURN 42" );
667+
if ( result.single().get( 0 ).asLong() == 42 )
668+
{
669+
throw new IllegalStateException();
670+
}
671+
return 1L;
672+
}
673+
} );
674+
fail( "Exception expected" );
675+
}
676+
catch ( Exception e )
677+
{
678+
assertThat( e, instanceOf( IllegalStateException.class ) );
679+
}
680+
681+
// bookmark should remain null after rollback
682+
assertNull( session.lastBookmark() );
683+
}
684+
}
561685

686+
@Test
687+
public void writeTxRolledBackWhenExceptionIsThrown()
688+
{
689+
try ( Driver driver = newDriverWithoutRetries() )
690+
{
691+
try ( Session session = driver.session() )
692+
{
693+
try
694+
{
695+
session.writeTransaction( new TransactionWork<Integer>()
696+
{
697+
@Override
698+
public Integer execute( Transaction tx )
699+
{
700+
tx.run( "CREATE (:Person {name: 'Loki Odinson'})" );
701+
throw new IllegalStateException();
702+
}
703+
} );
704+
fail( "Exception expected" );
705+
}
706+
catch ( Exception e )
707+
{
708+
assertThat( e, instanceOf( IllegalStateException.class ) );
709+
}
710+
}
711+
712+
try ( Session session = driver.session() )
713+
{
714+
StatementResult result = session.run( "MATCH (p:Person {name: 'Natasha Romanoff'}) RETURN count(p)" );
715+
assertEquals( 0, result.single().get( 0 ).asInt() );
716+
}
717+
}
718+
}
719+
720+
@Test
721+
public void readTxRolledBackWhenMarkedBothSuccessAndFailure()
722+
{
723+
try ( Driver driver = newDriverWithoutRetries();
724+
Session session = driver.session() )
725+
{
726+
assertNull( session.lastBookmark() );
727+
728+
long answer = session.readTransaction( new TransactionWork<Long>()
729+
{
730+
@Override
731+
public Long execute( Transaction tx )
732+
{
733+
StatementResult result = tx.run( "RETURN 42" );
734+
tx.success();
735+
tx.failure();
736+
return result.single().get( 0 ).asLong();
737+
}
738+
} );
562739
assertEquals( 42, answer );
740+
741+
// bookmark should remain null after rollback
742+
assertNull( session.lastBookmark() );
563743
}
744+
}
564745

565-
try ( Session session = neo4j.driver().session() )
746+
@Test
747+
public void writeTxRolledBackWhenMarkedBothSuccessAndFailure()
748+
{
749+
try ( Driver driver = newDriverWithoutRetries() )
566750
{
567-
StatementResult result = session.run( "MATCH (p:Person {name: 'Natasha Romanoff'}) RETURN count(p)" );
568-
assertEquals( 0, result.single().get( 0 ).asInt() );
751+
try ( Session session = driver.session() )
752+
{
753+
int answer = session.writeTransaction( new TransactionWork<Integer>()
754+
{
755+
@Override
756+
public Integer execute( Transaction tx )
757+
{
758+
tx.run( "CREATE (:Person {name: 'Natasha Romanoff'})" );
759+
tx.success();
760+
tx.failure();
761+
return 42;
762+
}
763+
} );
764+
765+
assertEquals( 42, answer );
766+
}
767+
768+
try ( Session session = driver.session() )
769+
{
770+
StatementResult result = session.run( "MATCH (p:Person {name: 'Natasha Romanoff'}) RETURN count(p)" );
771+
assertEquals( 0, result.single().get( 0 ).asInt() );
772+
}
773+
}
774+
}
775+
776+
@Test
777+
public void readTxRolledBackWhenMarkedAsSuccessAndThrowsException()
778+
{
779+
try ( Driver driver = newDriverWithoutRetries();
780+
Session session = driver.session() )
781+
{
782+
assertNull( session.lastBookmark() );
783+
784+
try
785+
{
786+
session.readTransaction( new TransactionWork<Long>()
787+
{
788+
@Override
789+
public Long execute( Transaction tx )
790+
{
791+
tx.run( "RETURN 42" );
792+
tx.success();
793+
throw new IllegalStateException();
794+
}
795+
} );
796+
fail( "Exception expected" );
797+
}
798+
catch ( Exception e )
799+
{
800+
assertThat( e, instanceOf( IllegalStateException.class ) );
801+
}
802+
803+
// bookmark should remain null after rollback
804+
assertNull( session.lastBookmark() );
805+
}
806+
}
807+
808+
@Test
809+
public void writeTxRolledBackWhenMarkedAsSuccessAndThrowsException()
810+
{
811+
try ( Driver driver = newDriverWithoutRetries() )
812+
{
813+
try ( Session session = driver.session() )
814+
{
815+
try
816+
{
817+
session.writeTransaction( new TransactionWork<Integer>()
818+
{
819+
@Override
820+
public Integer execute( Transaction tx )
821+
{
822+
tx.run( "CREATE (:Person {name: 'Natasha Romanoff'})" );
823+
tx.success();
824+
throw new IllegalStateException();
825+
}
826+
} );
827+
fail( "Exception expected" );
828+
}
829+
catch ( Exception e )
830+
{
831+
assertThat( e, instanceOf( IllegalStateException.class ) );
832+
}
833+
}
834+
835+
try ( Session session = driver.session() )
836+
{
837+
StatementResult result = session.run( "MATCH (p:Person {name: 'Natasha Romanoff'}) RETURN count(p)" );
838+
assertEquals( 0, result.single().get( 0 ).asInt() );
839+
}
569840
}
570841
}
571842

@@ -668,6 +939,11 @@ public Void execute( Transaction tx )
668939
}
669940
}
670941

942+
private Driver newDriverWithoutRetries()
943+
{
944+
return newDriverWithFixedRetries( 0 );
945+
}
946+
671947
private Driver newDriverWithFixedRetries( int maxRetriesCount )
672948
{
673949
DriverFactory driverFactory = new DriverFactoryWithFixedRetryLogic( maxRetriesCount );

0 commit comments

Comments
 (0)