micronaut-projects / micronaut-core

Micronaut Application Framework
http://micronaut.io
Apache License 2.0
6.08k stars 1.07k forks source link

Exception "No transaction manager configured" after update to micronaut 4 #9597

Open viniciusxyz opened 1 year ago

viniciusxyz commented 1 year ago

Expected Behavior

Hopes that when performing the update with openrewrite the application will continue to work correctly including integration with spring jdbcTemplate

Actual Behaviour

When performing an update and calling an endpoint that invokes a service marked with @transaction, the following exception is thrown: "No transaction manager configured"

For comparison purposes the project has a branch h2-micronaut4 and another h2-micronaut3 where using the branch with micronaut 3 the project works correctly.

We chose to use spring's jdbc-template in conjunction with our micronaut applications because it makes migration and switching between frameworks easier and also because it offers better performance in our tests than other options and in the company where I work, performance on database operations is something very critical, for comparison is the graph below:

image

The title at the top of the chart says "Account Search"

Steps To Reproduce

  1. Download the project: Link
  2. Run the project mvn mn:run
  3. Run curl below, also present in the application's readme
curl --location --request POST 'http://localhost:8080/deposit' \
--header 'Content-Type: application/json' \
--data-raw '{
"accountNumber": "0000001",
"amount": 1.0
}'
  1. The exception will be thrown

If there is anything I can do to bring more detailed data and help understand the source of the problem please let me know.

Environment Information

Example Application

https://github.com/viniciusxyz/micronaut-transaction-failed/tree/h2-micronaut4

Version

4.0.1

dstepanov commented 1 year ago

You might be missing implementation("io.micronaut.data:micronaut-data-spring-jdbc"). It's always better to have an added test to reproduce.

Did you try to measure performance with Micronaut Data repositories vs the template?

viniciusxyz commented 1 year ago

@dstepanov Until the last release I used only the micronaut-spring-annotation processor together with the spring-jdbc, micronaut-spring and micronaut-jdbc-hikari dependencies, adding the suggested dependency I start to see the exception:

Message: No current connection present. Consider declaring @Connectable or @Transactional on the surrounding method
Path Taken: new Migration(DataSource dataSource)
io.micronaut.context.exceptions.BeanInstantiationException: Bean definition [my.transaction.app.db.Migration] could not be loaded: Error instantiating bean of type  [my.transaction.app.db.Migration]

Message: No current connection present. Consider declaring @Connectable or @Transactional on the surrounding method
Path Taken: new Migration(DataSource dataSource)

How to use spring-jdbc was discussed in issue #555

image

Regarding the performance tests, we did not manage to add the micronaut-data-repositories, we have a guideline of not "tying" the application to any framework, so we have a set of interfaces to be able to switch between Spring and Micronaut in a very simple way, but unfortunately, as previously the development was focused on spring and a large part of the code was focused on the database, we decided to go with something that worked in both frameworks, which in this case was spring-jdbc, when possible I will evaluate the micronaut-data-repositories and validate the possibility of creating a built-in compatibility layer between it and spring-jdbc for my team to use, but for now spring-jdbc support remains essential for us.

dstepanov commented 1 year ago

Please try annotating your custom repos @Connectable.

viniciusxyz commented 1 year ago

@dstepanov With the dependencies present this annotation is not available, until version 3.x I never needed to add this annotation either

In the application that I left as an example, there is a project in micronaut 3 with everything working correctly and in another branch the project with micronaut 4 with the problems described

dstepanov commented 1 year ago

We extracted the Spring integration into our TX dependency. Please try adding micronaut-data-connection dependency.

viniciusxyz commented 1 year ago

@dstepanov Added, unfortunately I have the same problem :/

dstepanov commented 1 year ago

I will check, please update the project

viniciusxyz commented 1 year ago

@dstepanov Both branches are up to date, they do not have the suggested changes as I preferred to keep micronaut 3 the way it works and micronaut 4 in the format that is delivered by the openrewrite update

viniciusxyz commented 1 year ago

I did an analysis, a little limited by my level of knowledge, but I hope it helps:

Validating the behavior of Micronaut 3 we have the following:

Screenshot_2

Watch the debug result item

Validating the behavior of Micronaut 4 we have the following:

Screenshot_3

Watch the debug result item

Here is the snippet I used to create the PlatformTransactionManager bean

@Factory
public class JdbcTemplateFactory {

    private final DataSource dataSource;

    @Bean
    public PlatformTransactionManager provide(){
        return new DataSourceTransactionManager(dataSource);
    }
}

@dstepanov @graemerocher Is there anything else I can do to try to help resolve the issue? It seems to me that in version 3 the PlatformTranctionManager bean was created automatically and now it is not.

dstepanov commented 1 year ago

I will check next week

dstepanov commented 1 year ago

The solution is add this class:

import io.micronaut.context.annotation.EachBean;
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.annotation.Requires;
import io.micronaut.context.event.BeanCreatedEvent;
import io.micronaut.context.event.BeanCreatedEventListener;
import io.micronaut.context.event.BeanPreDestroyEventListener;
import jakarta.inject.Singleton;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;

import javax.sql.DataSource;

@Factory
@Requires(classes = DataSourceTransactionManager.class)
public class DataSourceTransactionManagerFactory {

    /**
     * For each {@link DataSource} add a {@link DataSourceTransactionManager} bean.
     *
     * @param dataSource The data source
     * @return The bean to add
     */
    @EachBean(DataSource.class)
    DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource) {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource);
        dataSourceTransactionManager.afterPropertiesSet();
        return dataSourceTransactionManager;
    }
    /**
     * For each data source, wrap in a {@link TransactionAwareDataSourceProxy}.
     * @return The listener that will wrap each data source
     */
    @Singleton
    TransactionAwareDataSourceListener transactionAwareDataSourceListener() {
        return new TransactionAwareDataSourceListener();
    }

    @Singleton
    final BeanPreDestroyEventListener<DataSource> transactionAwareDataSourceListenerUnwrapper() {
        return event -> {
            DataSource ds = event.getBean();
            if (ds instanceof TransactionAwareDataSourceProxy transactionAwareDataSourceProxy) {
                return transactionAwareDataSourceProxy.getTargetDataSource();
            }
            return ds;
        };
    }
    /**
     * A {@link BeanCreatedEventListener} that wraps each data source in a transaction aware proxy.
     */
    private static class TransactionAwareDataSourceListener implements BeanCreatedEventListener<DataSource> {
        @Override
        public DataSource onCreated(BeanCreatedEvent<DataSource> event) {
            DataSource dataSource = event.getBean();
            if (dataSource instanceof TransactionAwareDataSourceProxy) {
                return dataSource;
            } else {
                return new TransactionAwareDataSourceProxy(dataSource);
            }
        }
    }
}
viniciusxyz commented 1 year ago

@dstepanov Adding the class informed in the project everything works perfectly fine, I had imagined that an EachBean that configured this type of thing had been removed by mistake or something from 4.5.1 to 5.0.0, but when I searched the tag I did not find any that seems to be right and looking back now actually this class didn't exist in 4.5.1 so I'm not exactly sure how this worked before, can I add this to micronaut-spring and open a PR or is it already something you're working on?

dstepanov commented 1 year ago

It used to be in micronaut-sql, but I'm not sure if we want to add it back.

viniciusxyz commented 1 year ago

@dstepanov I think it's important to at least have some configuration to indicate whether this should be active or not, even if it is disabled by default, when I migrated from spring to micronaut, spring-jdbc worked equally in micronaut and spring was something that certainly reduced our time of migration in a few months and for those who are in the same situation as mine and had done the migration that way, but there are no tests that point out defects in the behavior of the transactions can lead to problems for production with the update to micronaut 4

For my particular project the proposed solution solves the problem, but it would be a shame not to see this working by default in the framework