Affects: 6.0.4 (spring-r2dbc)

When using @Transactional on a reactive service method, the auto-generated transaction name used by the MSSQL R2DBC driver might be too long in some use cases, leading to the following exception:

org.springframework.transaction.CannotCreateTransactionException: Could not open R2DBC Connection for transaction
    at org.springframework.r2dbc.connection.R2dbcTransactionManager.lambda$doBegin$5(R2dbcTransactionManager.java:234) ~[spring-r2dbc-6.0.4.jar:6.0.4]
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
    ...
Original Stack Trace:
        at org.springframework.r2dbc.connection.R2dbcTransactionManager.lambda$doBegin$5(R2dbcTransactionManager.java:234) ~[spring-r2dbc-6.0.4.jar:6.0.4]
        at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:94) ~[reactor-core-3.5.2.jar:3.5.2]
        ...
Caused by: java.lang.IllegalArgumentException: Transaction names must contain only characters and numbers and must not exceed 32 characters
    at io.r2dbc.mssql.util.Assert.isTrue(Assert.java:106) ~[r2dbc-mssql-1.0.0.RELEASE.jar:1.0.0.RELEASE]
    at io.r2dbc.mssql.MssqlConnection.lambda$beginTransaction$1(MssqlConnection.java:114) ~[r2dbc-mssql-1.0.0.RELEASE.jar:1.0.0.RELEASE]
    at io.r2dbc.mssql.MssqlConnection.lambda$useTransactionStatus$15(MssqlConnection.java:426) ~[r2dbc-mssql-1.0.0.RELEASE.jar:1.0.0.RELEASE]
    ...

package org.example;

@Service
public class MyService {

  @Transactional
  public Mono<Void> myServiceMethod() {
    ...
  }
}

In this example, the auto-generated transaction definition name would be org.example.MyService.myServiceMethod. This is actually the jointpointIdentification as implemented inTransactionAspectSupport.ReactiveTransactionSupport.createTransactionIfNecessary().

This transaction name is then transferred to the MSSQL R2DBC driver (see R2dbcTransactionManager.doBegin).

The driver then sanitises the transaction name into org_example_MyService_myServiceMethod.

However, it also checks that the length of the transaction name is shorter or equals to 32 characters, so that it can be safely used in the MSSQL statement BEGIN TRANSACTION <name> (see length restriction here https://learn.microsoft.com/en-us/sql/t-sql/language-elements/begin-transaction-transact-sql?view=sql-server-ver15#arguments).

It looks like this behaviour has been added by the driver for some time already: https://github.com/r2dbc/r2dbc-mssql/issues/183

Would it be possible to make sure that the transaction name is suitable to be use with the MSSQL driver?

I am not sure wether it is spring-r2dbc or r2dbc-mssql responsibility though...


The current workaround I use, is to override the R2dbcTransactionManager.createTransactionDefinition method and abbreviate the transaction name:

    public class MssqlFriendlyR2dbcTransactionManager extends R2dbcTransactionManager {

        private static final Integer MAX_TRANSACTION_NAME_LENGTH = 32;

        public MssqlFriendlyR2dbcTransactionManager(ConnectionFactory connectionFactory) {
            super(connectionFactory);
        }

        @Override
        protected io.r2dbc.spi.TransactionDefinition createTransactionDefinition(TransactionDefinition definition) {
            return super.createTransactionDefinition(adjustTransactionDefinintionNameLength(definition));
        }

        private DefaultTransactionDefinition adjustTransactionDefinintionNameLength(TransactionDefinition definition) {

            var adjustedDefinition = new DefaultTransactionDefinition(definition);
            adjustedDefinition.setName(Optional.ofNullable(adjustedDefinition.getName())
                .map(this::shortenTransactionDefinitionName)
                .orElse(null));

            return adjustedDefinition;
        }

        private String shortenTransactionDefinitionName(String name) {

            var length = name.length();

            return length > MAX_TRANSACTION_NAME_LENGTH ? name.substring(length - MAX_TRANSACTION_NAME_LENGTH, length)
                : name;
        }

    }

It is good enough, but I would love to rather see it implemented in the framework instead!

Comment From: mp911de

The 32 chars limitation applies only to the SQL Server driver; Specifically, the SQL server imposes this limitation.

Comment From: jhoeller

Same issue as in #29829. We are not quite sure how to address it in the framework by default. I suppose we could provide a configurable property for the maximum transaction name length, but it seems a shame that this has to be explicitly configured. It would be more transparent if the driver truncated the name internally, according to its own conventions.

Comment From: sbrannen

Closing as duplicate of #29829

Comment From: ghost

Sorry to have missed the existing issue #29829. Thank you all for the quick answers though!