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

feat: allow Spanner and Datastore Transaction Manager in same project #1412

Merged
merged 7 commits into from
May 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
}
}