1

I'm trying to implement transactions with spring-data-r2dbc repositories in combination with the TransactionalDatabaseClient as such:

class SongService(
    private val songRepo: SongRepo,
    private val databaseClient: DatabaseClient

){
    private val tdbc = databaseClient as TransactionalDatabaseClient

    ...
    ...
    fun save(song: Song){
        return tdbc.inTransaction{ 
            songRepo
                .save(mapRow(song, albumId)) //Mapping to a row representation
                .delayUntil { savedSong -> tdbc.execute.sql(...).fetch.rowsUpdated() } //saving a many to many relation
                .map(::mapSong) //Mapping back to actual song and retrieve the relationship data.
        }
    }

}

I currently have a config class (annotated with @Configuration and @EnableR2dbcRepositories) that extends from AbstractR2dbcConfiguration. In here I override the databaseClient method to return a TransactionalDatabaseClient. This should be the same instance as in the SongService class.

When running the code in a test with just subscribing and printing, I get org.springframework.transaction.NoTransactionException: ReactiveTransactionSynchronization not active and the relationship data is not returned.

When using project Reactors stepverifier though, i get java.lang.IllegalStateException: Connection is closed. Also in this case, the relationship data is not returned.

Just for the record, I have seen https://github.com/spring-projects/spring-data-r2dbc/issues/44

Seanvd
  • 186
  • 1
  • 9
  • I wasn't able to reproduce `NoTransactionException`, however `llegalStateException: Connection is closed` is a bug that is fixed with the referenced ticket. Please upgrade to the latest snapshot to see whether this fixes your issue. If not, please provide a [minimal sample](https://stackoverflow.com/help/mcve) that reproduces the problem – mp911de Jan 28 '19 at 08:44
  • @mp911de Updating to the snapshot resolved the errors but sadly enough my data inserted with tdbc.execute gets rolled back (commitTransaction is called) – Seanvd Jan 30 '19 at 09:51
  • Not sure I follow. Please provide some example code (e.g. GitHub repo) to reproduce the issue. – mp911de Jan 30 '19 at 09:53
  • @mp911de Will do. As soon as i have a repo with sample code I will respond again! Thanks for helping – Seanvd Jan 30 '19 at 09:54

1 Answers1

0

Here is a working Java example:

@Autowired TransactionalDatabaseClient txClient;
@Autowired Mono<Connection> connection;
//You Can also use: @Autowired Mono<? extends Publisher> connectionPublisher;

public Flux<Void> example {
txClient.enableTransactionSynchronization(connection);
// Or, txClient.enableTransactionSynchronization(connectionPublisher);

Flux<AuditConfigByClub> audits = txClient.inTransaction(tx -> {
  txClient.beginTransaction();
  return tx.execute().sql("SELECT * FROM audit.items")
  .as(Item.class)
  .fetch()
  .all();
}).doOnTerminate(() -> {
  txClient.commitTransaction();
});
txClient.commitTransaction();

audits.subscribe(item -> System.out.println("anItem: " + item));
  return Flux.empty()
}

I just started reactive so not too sure what I'm doing with my callbacks haha. But I decided to go with TransactionalDatabaseClient over DatabaseClient or Connection since I'll take all the utility I can get while R2dbc is in its current state.

In your code did you actually instantiate a Connection object? If so I think you would have done it in your configuration. It can be utilized throughout the app the same as DatabaseClient, but it is slightly more intricate.

If not:

@Bean
@Override // I also used abstract config
public ConnectionFactory connectionFactory() {
  ...
}

@Bean 
TransactionalDatabaseClient txClient() {
  ...
}

//TransactionalDatabaseClient will take either of these as arg in 
//#enableTransactionSynchronization method

@Bean
public Publisher<? extends Connection> connectionPublisher() {
  return connectionFactory().create();
}

@Bean
public Mono<Connection> connection() {
  return = Mono.from(connectionFactory().create());
}

If you are having problems translating to Kotlin, there is an alternative way to enable synchronization that could work:

// From what I understand, this is a useful way to move between 
// transactions within a single subscription
TransactionResources resources = TransactionResources.create();
resources.registerResource(Resource.class, resource);

ConnectionFactoryUtils
  .currentReactiveTransactionSynchronization()
  .subscribe(currentTx -> sync.registerTransaction(Tx));

Hope this translates well for Kotlin.

wscourge
  • 10,657
  • 14
  • 59
  • 80
Christian Meyer
  • 605
  • 8
  • 15