Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transaction error using Spanner JDBC with JPA and Spring Cloud GCP Datastore in the same project #944

Closed
lucasoares opened this issue Feb 10, 2022 · 7 comments · Fixed by #1412
Assignees
Labels

Comments

@lucasoares
Copy link
Contributor

lucasoares commented Feb 10, 2022

Hello.

I'm trying to configure a single project using Spring Data JPA + Spanner using JDBC connector and also the Spring Cloud GCP Datastore support.

I'm able to read data from both Spanner and Datastore even tho they are located in different GCP projects. The problem is when I try to save any entity (or perform a primary key operation which have a transaction behind the scenes) I get the following error:

2022-02-10 21:49:27,317 ERROR  [page.PageSync] Error creating page of id 4324324234234 org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'transactionManager' available: No matching TransactionManager bean found for qualifier 'transactionManager' - neither qualifier match nor bean name match!
	at org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils.qualifiedBeanOfType(BeanFactoryAnnotationUtils.java:136) ~[spring-beans-5.3.8.jar:5.3.8]
	at org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils.qualifiedBeanOfType(BeanFactoryAnnotationUtils.java:95) ~[spring-beans-5.3.8.jar:5.3.8]
	at org.springframework.transaction.interceptor.TransactionAspectSupport.determineQualifiedTransactionManager(TransactionAspectSupport.java:515) ~[spring-tx-5.3.8.jar:5.3.8]
	at org.springframework.transaction.interceptor.TransactionAspectSupport.determineTransactionManager(TransactionAspectSupport.java:496) ~[spring-tx-5.3.8.jar:5.3.8]
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:342) ~[spring-tx-5.3.8.jar:5.3.8]
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.8.jar:5.3.8]

I'm basically reading data from Datastore and saving data into Spanner.

Code throwing error:

   this.pageRepository.save(page);

Repository implementation:

@Repository
public interface PageRepository extends JpaRepository<Page, UUID> {}

My project configuration (only what matters for this specific error):

  <properties>
    <!-- Spring -->
    <spring-cloud.version>2020.0.3</spring-cloud.version>
    <spring-cloud-gcp.version>2.0.3</spring-cloud-gcp.version>
    <spring.version>2.5.2</spring.version>
  </properties>

  <!-- I do not use any Spring Parent since I have my own parents -->
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>${spring-cloud.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>com.google.cloud</groupId>
        <artifactId>spring-cloud-gcp-dependencies</artifactId>
        <version>${spring-cloud-gcp.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>${spring.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>spring-cloud-gcp-starter-data-datastore</artifactId>
    </dependency>
    
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-spanner-hibernate-dialect</artifactId>
      <version>1.5.2</version>
    </dependency>
  </dependencies>

WIth these properties:

spring.cloud.gcp.datastore.project-id=my-project-id
spring.datasource.url=jdbc:cloudspanner:/projects/other-project-id/instances/my-instance/databases/my-database
spring.datasource.driver-class-name=com.google.cloud.spanner.jdbc.JdbcDriver
spring.jpa.database-platform=com.google.cloud.spanner.hibernate.SpannerDialect

I can't use Spanner from Spring Cloud GCP because I already have a project using JPA and now I need to access datastore in the same project to copy data to Spanner.

If I remove the spring-cloud-gcp-starter-data-datastore dependency from the project (and comment all codes using datastore) I can save data into the Spanner instance without errors.

@lucasoares
Copy link
Contributor Author

I tested with another application that doesn't use the Datastore itself but had the spring-cloud-gcp-data-datastore dependency in its classpath by mistake (was inheriting from another dependency) and the result is the same. Trying to insert data using JPA repository throws an exception.

For some reason it is not necessary to have the starter dependency spring-cloud-gcp-starter-data-datastore in the classpath to trigger this issue.

In this second application I just removed the spring-cloud-gcp-data-datastore from the classpath (using the exclusion above) and all inserts to Spanner using the JPA repository doesn't throw the exception anymore.

      <exclusions>
        <exclusion>
          <artifactId>spring-cloud-gcp-data-datastore</artifactId>
          <groupId>com.google.cloud</groupId>
        </exclusion>
      </exclusions>

It makes me think if the problem is with the datastore dependency itself or with the JPA starter auto configurations not being configured properly because of the datastore dependency.

@elefeint
Copy link
Contributor

The second issue is because Spring Cloud GCP autoconfiguration for Datastore triggers as soon as the client library is detected.

Let me think through whether this conflict (Spanner with JPA, but Datastore with a custom Spring Data implementation) can be avoided in a sensible way. Are you using Datastore repositories, or just the template to read data?

In the meantime, turn off spring.cloud.gcp.datastore.enabled property to disable autoconfiguration for Datastore. You will likely want to create your own DatastoreTemplate object like the autoconfiguration nomally does.

@elefeint
Copy link
Contributor

I did not look into it, and I am going on vacation; someone else will pick this up.

@lucasoares Did disabling datastore autoconfiguration mitigate the issue for you?

@gomezAlvaro
Copy link

This is also happening for me, using only JPA + Datastore. Disabling the autoconfiguration is a workaround I guess, but not that nice.

It started happening after upgrading the spring-cloud-gcp-data-datastore, from 1.2.8.RELEASE, where the error was not present, to 2.0.10 where it is. I also tried going to the last version 3.2.1, but the same error is still there.

@lucasoares
Copy link
Contributor Author

I did not look into it, and I am going on vacation; someone else will pick this up.

@lucasoares Did disabling datastore autoconfiguration mitigate the issue for you?

I did not tried it, but in my use case this is not possible because I need the datastore. In the other project I had only the spring-cloud-gcp-data-datastore dependency, without the starter dependency and the same error occurs.

I don't think the problem is the autoconfiguration itself, since it comes from the starter dependency.

@elefeint
Copy link
Contributor

elefeint commented Aug 9, 2022

@zhumin8 Could you reproduce this and reason about what's going on here?

@jdfischer-cedreo
Copy link

For those who have the issue with Jpa + datastore you can workaround the issue by declaring in your own configuration the two transaction manager.

@Configuration
public class MixingJpaAndDatastoreConfiguration
{

    // ============================================================ //
    // ========================== DATASTORE ======================= //
    // ============================================================ //

    @Bean
    @ConditionalOnMissingBean(name = "datastoreTransactionManager")
    public DatastoreTransactionManager datastoreTransactionManager(final DatastoreProvider datastore,
                                                                   final ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers)
    {
        final TransactionManagerCustomizers transactionManagerCustomizers1 = transactionManagerCustomizers.getIfAvailable();
        final DatastoreTransactionManager transactionManager = new DatastoreTransactionManager(datastore);
        if (transactionManagerCustomizers1 != null)
        {
            transactionManagerCustomizers1.customize(transactionManager);
        }

        return transactionManager;
    }

    // ============================================================ //
    // =========================== MYSQL ========================== //
    // ============================================================ //

    @Bean
    @ConditionalOnMissingBean(name = "transactionManager")
    public PlatformTransactionManager transactionManager(final ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers)
    {
        final JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManagerCustomizers.ifAvailable((customizers) ->
                                                  {
                                                      customizers.customize(transactionManager);
                                                  });
        return transactionManager;
    }
}

You will need to specify on your @transactional which manager you want but you will have both available. For example, jpa transaction will need @transactional(transactionManager = "transactionManager").

zhumin8 pushed a commit that referenced this issue May 20, 2023
…#1412)

Fixes #944 

Conditional for Spanner is based on missing beans of type SpannerTransactionManager instead of PlatformTransactionManager, this way it always gets created if Spanner libs are pulled in 

Conditional for Datastore is based on missing beans of type DatastoreTransactionManager instead of PlatformTransactionManager, this way it gets created if Datastore libs are pulled in.

It is the user's responsibility to designate the transaction's right transaction manager:

```
@transactional(transactionManager = "spannerTransactionManager")
```

```
@transactional(transactionManager = "datastoreTransactionManager")
```
zhumin8 pushed a commit that referenced this issue May 24, 2023
…#1412)

Fixes #944

Conditional for Spanner is based on missing beans of type SpannerTransactionManager instead of PlatformTransactionManager, this way it always gets created if Spanner libs are pulled in

Conditional for Datastore is based on missing beans of type DatastoreTransactionManager instead of PlatformTransactionManager, this way it gets created if Datastore libs are pulled in.

It is the user's responsibility to designate the transaction's right transaction manager:

```
@transactional(transactionManager = "spannerTransactionManager")
```

```
@transactional(transactionManager = "datastoreTransactionManager")
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants