Skip to content

Commit

Permalink
feat: allow Spanner and Datastore Transaction Manager in same project (
Browse files Browse the repository at this point in the history
…#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")
```
  • Loading branch information
bijukunjummen authored May 20, 2023
1 parent 998e8c8 commit f937b36
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 16 deletions.
7 changes: 7 additions & 0 deletions docs/src/main/asciidoc/datastore.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,13 @@ This feature requires a bean of `DatastoreTransactionManager`, which is provided
If a method annotated with `@Transactional` calls another method also annotated, then both methods will work within the same transaction.
`performTransaction` cannot be used in `@Transactional` annotated methods because Cloud Datastore does not support transactions within transactions.

Other Google Cloud database-related integrations like Spanner and Firestore can introduce `PlatformTransactionManager` beans, and can interfere with Datastore Transaction Manager. To disambiguate, explicitly specify the name of the transaction manager bean for such `@Transactional` methods. Example:

[source,java]
----
@Transactional(transactionManager = "datastoreTransactionManager")
----

==== Read-Write Support for Maps

You can work with Maps of type `Map<String, ?>` instead of with entity objects by directly reading and writing them to and from Cloud Datastore.
Expand Down
8 changes: 8 additions & 0 deletions docs/src/main/asciidoc/spanner.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,14 @@ This feature requires a bean of `SpannerTransactionManager`, which is provided w
If a method annotated with `@Transactional` calls another method also annotated, then both methods will work within the same transaction.
`performReadOnlyTransaction` and `performReadWriteTransaction` cannot be used in `@Transactional` annotated methods because Cloud Spanner does not support transactions within transactions.

Other Google Cloud database-related integrations like Spanner and Firestore can introduce `PlatformTransactionManager` beans, and can interfere with Datastore Transaction Manager. To disambiguate, explicitly specify the name of the transaction manager bean for such `@Transactional` methods. Example:

[source,java]
----
@Transactional(transactionManager = "spannerTransactionManager")
----


==== DML Statements

`SpannerTemplate` supports https://cloud.google.com/spanner/docs/dml-tasks:[DML] `Statements`.
Expand Down
9 changes: 9 additions & 0 deletions docs/src/main/md/datastore.md
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,15 @@ same transaction. `performTransaction` cannot be used in
`@Transactional` annotated methods because Cloud Datastore does not
support transactions within transactions.

Other Google Cloud database-related integrations like Spanner and Firestore can
introduce `PlatformTransactionManager` beans, and can interfere with Datastore
Transaction Manager. To disambiguate, explicitly specify the name of the
transaction manager bean for such `@Transactional` methods. Example:

```java
@Transactional(transactionManager = "datastoreTransactionManager")
```

#### Read-Write Support for Maps

You can work with Maps of type `Map<String, ?>` instead of with entity
Expand Down
9 changes: 9 additions & 0 deletions docs/src/main/md/spanner.md
Original file line number Diff line number Diff line change
Expand Up @@ -1123,6 +1123,15 @@ same transaction. `performReadOnlyTransaction` and
annotated methods because Cloud Spanner does not support transactions
within transactions.

Other Google Cloud database-related integrations like Spanner and Firestore can
introduce `PlatformTransactionManager` beans, and can interfere with Datastore
Transaction Manager. To disambiguate, explicitly specify the name of the
transaction manager bean for such `@Transactional` methods. Example:

```java
@Transactional(transactionManager = "spannerTransactionManager")
```

#### DML Statements

`SpannerTemplate` supports
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration;
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
import org.springframework.context.annotation.Bean;
import org.springframework.transaction.PlatformTransactionManager;

/**
* Auto-configuration for {@link DatastoreTransactionManager}.
Expand Down Expand Up @@ -59,7 +58,7 @@ static class DatastoreTransactionManagerConfiguration {
}

@Bean
@ConditionalOnMissingBean(PlatformTransactionManager.class)
@ConditionalOnMissingBean
public DatastoreTransactionManager datastoreTransactionManager() {
DatastoreTransactionManager transactionManager =
new DatastoreTransactionManager(this.datastore);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration;
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
import org.springframework.context.annotation.Bean;
import org.springframework.transaction.PlatformTransactionManager;

/**
* Auto-configuration for {@link SpannerTransactionManager}.
Expand Down Expand Up @@ -61,7 +60,7 @@ static class DatabaseClientTransactionManagerConfiguration {
}

@Bean
@ConditionalOnMissingBean(PlatformTransactionManager.class)
@ConditionalOnMissingBean
public SpannerTransactionManager spannerTransactionManager() {
SpannerTransactionManager transactionManager =
new SpannerTransactionManager(this.databaseClientProvider);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
= Spring Framework on Google Cloud Cloud Spanner and Datastore Combined Usage Example

This code sample demonstrates how to use both link:../../spring-cloud-gcp-starters/spring-cloud-gcp-starter-data-spanner[Spring Data Cloud Spanner] and link:../../spring-cloud-gcp-starters/spring-cloud-gcp-starter-data-datastore[Spring Data Cloud Datastore] using the Spring Data Cloud Datastore modules in a single Spring application.
This sample also demonstrates usage of transactions with both modules.

== Running the example

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@
@SpringBootApplication
public class MultipleDataModuleExample {

// A Spring Data Datastore repository
@Autowired PersonRepository personRepository;
// Internally uses a Spring Data Datastore repository
@Autowired private PersonService personService;

// A Spring Data Cloud Spanner repository
@Autowired TraderRepository traderRepository;
// Internally uses a Spring Data Cloud Spanner repository
@Autowired private TraderService traderService;

public static void main(String[] args) {
SpringApplication.run(MultipleDataModuleExample.class, args);
Expand All @@ -41,19 +41,19 @@ ApplicationRunner applicationRunner() {
return args -> {
System.out.println("Deleting all entities.");

this.personRepository.deleteAll();
this.traderRepository.deleteAll();
this.personService.deleteAll();
this.traderService.deleteAll();

System.out.println("The number of Person entities is now: " + this.personRepository.count());
System.out.println("The number of Trader entities is now: " + this.traderRepository.count());
System.out.println("The number of Person entities is now: " + this.personService.count());
System.out.println("The number of Trader entities is now: " + this.traderService.count());

System.out.println("Saving one entity with each repository.");

this.traderRepository.save(new Trader("id1", "trader", "one"));
this.personRepository.save(new Person(1L, "person1"));
this.traderService.save(new Trader("id1", "trader", "one"));
this.personService.save(new Person(1L, "person1"));

System.out.println("The number of Person entities is now: " + this.personRepository.count());
System.out.println("The number of Trader entities is now: " + this.traderRepository.count());
System.out.println("The number of Person entities is now: " + this.personService.count());
System.out.println("The number of Trader entities is now: " + this.traderService.count());
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(transactionManager = "spannerTransactionManager")
public class PersonService {

private final PersonRepository personRepository;

public PersonService(PersonRepository personRepository) {
this.personRepository = personRepository;
}

public Person save(Person person) {
return this.personRepository.save(person);
}

public void deleteAll() {
this.personRepository.deleteAll();
}

public Long count() {
return this.personRepository.count();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(transactionManager = "datastoreTransactionManager")
public class TraderService {

private final TraderRepository traderRepository;

public TraderService(TraderRepository traderRepository) {
this.traderRepository = traderRepository;
}

public Trader save(Trader trader) {
return this.traderRepository.save(trader);
}

public void deleteAll() {
this.traderRepository.deleteAll();
}

public Long count() {
return this.traderRepository.count();
}
}

0 comments on commit f937b36

Please sign in to comment.