From 039422246f93b21662ab306ec2fd8beee578a3fa Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Fri, 27 May 2022 14:25:19 +0100 Subject: [PATCH 01/15] Move CouchbaseTransactionalOperator to use SLF4J, same as rest of the code. --- .../transaction/CouchbaseTransactionalOperator.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionalOperator.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionalOperator.java index 10002be95..5f4ea4e98 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionalOperator.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionalOperator.java @@ -6,6 +6,8 @@ import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; import com.couchbase.client.java.transactions.TransactionGetResult; import com.couchbase.client.java.transactions.TransactionResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.data.couchbase.core.CouchbaseTemplate; import org.springframework.transaction.ReactiveTransaction; import org.springframework.transaction.TransactionException; @@ -38,10 +40,11 @@ * what it finds in the currentContext()? * */ +// todo gpx ongoing discussions on whether this can support retries & error handling natively public class CouchbaseTransactionalOperator implements TransactionalOperator { // package org.springframework.transaction.reactive; - private static final Log logger = LogFactory.getLog(CouchbaseTransactionalOperator.class); + private static final Logger logger = LoggerFactory.getLogger(CouchbaseTransactionalOperator.class); private final ReactiveTransactionManager transactionManager; private final TransactionDefinition transactionDefinition; From 131112523b21772748cc002fabcd287a2c2acc9d Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Fri, 27 May 2022 14:25:55 +0100 Subject: [PATCH 02/15] Handle all propagation levels --- ...hbaseSimpleCallbackTransactionManager.java | 85 +++- ...nsactionalPropagationIntegrationTests.java | 399 ++++++++++++++++++ 2 files changed, 468 insertions(+), 16 deletions(-) create mode 100644 src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalPropagationIntegrationTests.java diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseSimpleCallbackTransactionManager.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseSimpleCallbackTransactionManager.java index 772ed2853..7d5eed74e 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseSimpleCallbackTransactionManager.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseSimpleCallbackTransactionManager.java @@ -15,39 +15,29 @@ */ package org.springframework.data.couchbase.transaction; -import com.couchbase.client.core.transaction.CoreTransactionAttemptContext; import com.couchbase.client.java.transactions.AttemptContextReactiveAccessor; -import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; import com.couchbase.client.java.transactions.TransactionAttemptContext; import com.couchbase.client.java.transactions.TransactionResult; import com.couchbase.client.java.transactions.config.TransactionOptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.data.couchbase.CouchbaseClientFactory; import org.springframework.data.couchbase.ReactiveCouchbaseClientFactory; import org.springframework.lang.Nullable; import org.springframework.transaction.IllegalTransactionStateException; -import org.springframework.transaction.InvalidTimeoutException; -import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionException; import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.reactive.TransactionContextManager; -import org.springframework.transaction.support.AbstractPlatformTransactionManager; import org.springframework.transaction.support.CallbackPreferringPlatformTransactionManager; import org.springframework.transaction.support.DefaultTransactionStatus; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionSynchronizationManager; -import org.springframework.util.Assert; -import reactor.core.publisher.Mono; -import java.lang.reflect.Field; import java.time.Duration; import java.util.concurrent.atomic.AtomicReference; public class CouchbaseSimpleCallbackTransactionManager implements CallbackPreferringPlatformTransactionManager { - private static final Logger LOGGER = LoggerFactory.getLogger(CouchbaseTransactionManager.class); + private static final Logger LOGGER = LoggerFactory.getLogger(CouchbaseSimpleCallbackTransactionManager.class); private final ReactiveCouchbaseClientFactory couchbaseClientFactory; private TransactionOptions options; @@ -59,10 +49,21 @@ public CouchbaseSimpleCallbackTransactionManager(ReactiveCouchbaseClientFactory @Override public T execute(TransactionDefinition definition, TransactionCallback callback) throws TransactionException { - final AtomicReference execResult = new AtomicReference<>(); + boolean createNewTransaction = handlePropagation(definition); setOptionsFromDefinition(definition); + if (createNewTransaction) { + return executeNewTransaction(callback); + } + else { + return callback.doInTransaction(null); + } + } + + private T executeNewTransaction(TransactionCallback callback) { + final AtomicReference execResult = new AtomicReference<>(); + TransactionResult result = couchbaseClientFactory.getCluster().block().transactions().run(ctx -> { CouchbaseTransactionStatus status = new CouchbaseTransactionStatus(null, true, false, false, true, null, null); @@ -70,8 +71,7 @@ public T execute(TransactionDefinition definition, TransactionCallback ca try { execResult.set(callback.doInTransaction(status)); - } - finally { + } finally { TransactionSynchronizationManager.clear(); } }, this.options); @@ -81,6 +81,61 @@ public T execute(TransactionDefinition definition, TransactionCallback ca return execResult.get(); } + // Propagation defines what happens when a @Transactional method is called from another @Transactional method. + private boolean handlePropagation(TransactionDefinition definition) { + boolean isExistingTransaction = TransactionSynchronizationManager.isActualTransactionActive(); + + LOGGER.trace("Deciding propagation behaviour from {} and {}", definition.getPropagationBehavior(), isExistingTransaction); + + switch (definition.getPropagationBehavior()) { + case TransactionDefinition.PROPAGATION_REQUIRED: + // Make a new transaction if required, else just execute the new method in the current transaction. + return !isExistingTransaction; + + case TransactionDefinition.PROPAGATION_SUPPORTS: + // Don't appear to have the ability to execute the callback non-transactionally in this layer. + throw new IllegalTransactionStateException( + "Propagation level 'support' has been specified which is not supported"); + + case TransactionDefinition.PROPAGATION_MANDATORY: + if (!isExistingTransaction) { + throw new IllegalTransactionStateException( + "Propagation level 'mandatory' is specified but not in an active transaction"); + } + return false; + + case TransactionDefinition.PROPAGATION_REQUIRES_NEW: + // This requires suspension of the active transaction. This will be possible to support in a future + // release, if required. + throw new IllegalTransactionStateException( + "Propagation level 'requires_new' has been specified which is not currently supported"); + + case TransactionDefinition.PROPAGATION_NOT_SUPPORTED: + // Don't appear to have the ability to execute the callback non-transactionally in this layer. + throw new IllegalTransactionStateException( + "Propagation level 'not_supported' has been specified which is not supported"); + + case TransactionDefinition.PROPAGATION_NEVER: + if (isExistingTransaction) { + throw new IllegalTransactionStateException( + "Existing transaction found for transaction marked with propagation 'never'"); + } + return true; + + case TransactionDefinition.PROPAGATION_NESTED: + if (isExistingTransaction) { + // Couchbase transactions cannot be nested. + throw new IllegalTransactionStateException( + "Propagation level 'nested' has been specified which is not supported"); + } + return true; + + default: + throw new IllegalTransactionStateException( + "Unknown propagation level " + definition.getPropagationBehavior() + " has been specified"); + } + } + /** * @param definition reflects the @Transactional options */ @@ -96,8 +151,6 @@ private void setOptionsFromDefinition(TransactionDefinition definition) { } // readonly is ignored as it is documented as being a hint that won't necessarily cause writes to fail - - // todo gpx what about propagation? } } diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalPropagationIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalPropagationIntegrationTests.java new file mode 100644 index 000000000..63cdc42a3 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalPropagationIntegrationTests.java @@ -0,0 +1,399 @@ +/* + * Copyright 2012-2022 the original author or authors + * + * 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 org.springframework.data.couchbase.transactions; + +import com.couchbase.client.java.transactions.error.TransactionFailedException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.data.couchbase.CouchbaseClientFactory; +import org.springframework.data.couchbase.config.BeanNames; +import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.data.couchbase.domain.Person; +import org.springframework.data.couchbase.util.ClusterType; +import org.springframework.data.couchbase.util.IgnoreWhen; +import org.springframework.data.couchbase.util.JavaIntegrationTests; +import org.springframework.lang.Nullable; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.springframework.transaction.IllegalTransactionStateException; +import org.springframework.transaction.NoTransactionException; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import java.util.UUID; +import java.util.function.Consumer; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +// todo gpx test repository methods in @Transactional +// todo gpx test queries in @Transactional +// todo gpx chekc what happens when try to do reactive @Transcational (unsupported by CallbackPreferring) +// todo gpx handle synchronization + +/** + * Tests for the various propagation values allowed on @Transactional methods. + */ +@IgnoreWhen(clusterTypes = ClusterType.MOCKED) +@SpringJUnitConfig(Config.class) +public class CouchbaseTransactionalPropagationIntegrationTests extends JavaIntegrationTests { + private static final Logger LOGGER = LoggerFactory.getLogger(CouchbaseTransactionalPropagationIntegrationTests.class); + + @Autowired CouchbaseClientFactory couchbaseClientFactory; + PersonService personService; + @Autowired + CouchbaseTemplate operations; + static GenericApplicationContext context; + + @BeforeAll + public static void beforeAll() { + callSuperBeforeAll(new Object() {}); + context = new AnnotationConfigApplicationContext(Config.class, PersonService.class); + } + + @BeforeEach + public void beforeEachTest() { + personService = context.getBean(PersonService.class); + + Person walterWhite = new Person(1, "Walter", "White"); + try { + couchbaseClientFactory.getBucket().defaultCollection().remove(walterWhite.getId().toString()); + } catch (Exception ex) { + // System.err.println(ex); + } + } + + @AfterEach + public void afterEachTest() { + assertNotInTransaction(); + } + + @DisplayName("Call @Transactional(propagation = DEFAULT) - succeeds, creates a transaction") + @Test + public void callDefault() { + personService.propagationDefault(ops -> { + assertInTransaction(); + }); + } + + @DisplayName("Call @Transactional(propagation = SUPPORTS) - fails as unsupported") + @Test + public void callSupports() { + try { + personService.propagationSupports(ops -> { + }); + fail(); + } + catch (IllegalTransactionStateException ignored) { + } + } + + @DisplayName("Call @Transactional(propagation = MANDATORY) - fails as not in an active transaction") + @Test + public void callMandatory() { + try { + personService.propagationMandatory(ops -> { + }); + fail(); + } + catch (IllegalTransactionStateException ignored) { + } + } + + @DisplayName("Call @Transactional(propagation = REQUIRES_NEW) - fails as unsupported") + @Test + public void callRequiresNew() { + try { + personService.propagationRequiresNew(ops -> { + }); + fail(); + } + catch (IllegalTransactionStateException ignored) { + } + } + + @DisplayName("Call @Transactional(propagation = NOT_SUPPORTED) - fails as unsupported") + @Test + public void callNotSupported() { + try { + personService.propagationNotSupported(ops -> { + }); + fail(); + } + catch (IllegalTransactionStateException ignored) { + } + } + + @DisplayName("Call @Transactional(propagation = NEVER) - succeeds as not in a transaction, starts one") + @Test + public void callNever() { + personService.propagationNever(ops -> { + assertInTransaction(); + }); + } + + @DisplayName("Call @Transactional(propagation = NESTED) - succeeds as not in an existing transaction, starts one") + @Test + public void callNested() { + personService.propagationNested(ops -> { + assertInTransaction(); + }); + } + + // todo gp check retries + + @DisplayName("Call @Transactional that calls @Transactional(propagation = DEFAULT) - succeeds, continues existing") + @Test + public void callDefaultThatCallsDefault() { + UUID id1 = UUID.randomUUID(); + UUID id2 = UUID.randomUUID(); + + personService.propagationDefault(ops -> { + ops.insertById(Person.class).one(new Person(id1, "Ada", "Lovelace")); + + personService.propagationDefault(ops2 -> { + ops2.insertById(Person.class).one(new Person(id2, "Grace", "Hopper")); + + assertInTransaction(); + }); + }); + + // Validate everyting committed + assertNotNull(operations.findById(Person.class).one(id1.toString())); + assertNotNull(operations.findById(Person.class).one(id2.toString())); + } + + @DisplayName("Call @Transactional that calls @Transactional(propagation = REQUIRED) - succeeds, continues existing") + @Test + public void callDefaultThatCallsRequired() { + UUID id1 = UUID.randomUUID(); + UUID id2 = UUID.randomUUID(); + + personService.propagationDefault(ops -> { + ops.insertById(Person.class).one(new Person(id1, "Ada", "Lovelace")); + + personService.propagationRequired(ops2 -> { + ops2.insertById(Person.class).one(new Person(id2, "Grace", "Hopper")); + + assertInTransaction(); + }); + }); + + // Validate everyting committed + assertNotNull(operations.findById(Person.class).one(id1.toString())); + assertNotNull(operations.findById(Person.class).one(id2.toString())); + } + + @DisplayName("Call @Transactional that calls @Transactional(propagation = MANDATORY) - succeeds, continues existing") + @Test + public void callDefaultThatCallsMandatory() { + UUID id1 = UUID.randomUUID(); + UUID id2 = UUID.randomUUID(); + + personService.propagationDefault(ops -> { + ops.insertById(Person.class).one(new Person(id1, "Ada", "Lovelace")); + + personService.propagationMandatory(ops2 -> { + ops2.insertById(Person.class).one(new Person(id2, "Grace", "Hopper")); + + assertInTransaction(); + }); + }); + + // Validate everyting committed + assertNotNull(operations.findById(Person.class).one(id1.toString())); + assertNotNull(operations.findById(Person.class).one(id2.toString())); + } + + @DisplayName("Call @Transactional that calls @Transactional(propagation = REQUIRES_NEW) - fails as unsupported") + @Test + public void callDefaultThatCallsRequiresNew() { + UUID id1 = UUID.randomUUID(); + + try { + personService.propagationDefault(ops -> { + ops.insertById(Person.class).one(new Person(id1, "Ada", "Lovelace")); + + personService.propagationRequiresNew(ops2 -> { + fail(); + }); + }); + fail(); + } + catch (TransactionFailedException err) { + assertTrue(err.getCause() instanceof IllegalTransactionStateException); + } + + // Validate everything rolled back + assertNull(operations.findById(Person.class).one(id1.toString())); + } + + @DisplayName("Call @Transactional that calls @Transactional(propagation = NOT_SUPPORTED) - fails as unsupported") + @Test + public void callDefaultThatCallsNotSupported() { + UUID id1 = UUID.randomUUID(); + + try { + personService.propagationDefault(ops -> { + ops.insertById(Person.class).one(new Person(id1, "Ada", "Lovelace")); + + personService.propagationNotSupported(ops2 -> { + fail(); + }); + }); + fail(); + } + catch (TransactionFailedException err) { + assertTrue(err.getCause() instanceof IllegalTransactionStateException); + } + + // Validate everything rolled back + assertNull(operations.findById(Person.class).one(id1.toString())); + } + + @DisplayName("Call @Transactional that calls @Transactional(propagation = NEVER) - fails as in a transaction") + @Test + public void callDefaultThatCallsNever() { + UUID id1 = UUID.randomUUID(); + + try { + personService.propagationDefault(ops -> { + ops.insertById(Person.class).one(new Person(id1, "Ada", "Lovelace")); + + personService.propagationNever(ops2 -> { + fail(); + }); + }); + fail(); + } + catch (TransactionFailedException err) { + assertTrue(err.getCause() instanceof IllegalTransactionStateException); + } + + // Validate everything rolled back + assertNull(operations.findById(Person.class).one(id1.toString())); + } + + @DisplayName("Call @Transactional that calls @Transactional(propagation = NESTED) - fails as unsupported") + @Test + public void callDefaultThatCallsNested() { + UUID id1 = UUID.randomUUID(); + + try { + personService.propagationDefault(ops -> { + ops.insertById(Person.class).one(new Person(id1, "Ada", "Lovelace")); + + personService.propagationNested(ops2 -> { + fail(); + }); + }); + fail(); + } + catch (TransactionFailedException err) { + assertTrue(err.getCause() instanceof IllegalTransactionStateException); + } + + // Validate everything rolled back + assertNull(operations.findById(Person.class).one(id1.toString())); + } + + void assertInTransaction() { + assertTrue(org.springframework.transaction.support.TransactionSynchronizationManager.isActualTransactionActive()); + } + + void assertNotInTransaction() { + try { + assertFalse(org.springframework.transaction.support.TransactionSynchronizationManager.isActualTransactionActive()); + } + catch (NoTransactionException ignored) { + } + } + + @Service + @Component + @EnableTransactionManagement + static + class PersonService { + final CouchbaseOperations ops; + + public PersonService(CouchbaseOperations ops) { + this.ops = ops; + } + + @Transactional(transactionManager = BeanNames.COUCHBASE_SIMPLE_CALLBACK_TRANSACTION_MANAGER) + public void propagationDefault(@Nullable Consumer callback) { + LOGGER.info("propagationDefault"); + if (callback != null) callback.accept(ops); + } + + @Transactional(transactionManager = BeanNames.COUCHBASE_SIMPLE_CALLBACK_TRANSACTION_MANAGER, propagation = Propagation.REQUIRED) + public void propagationRequired(@Nullable Consumer callback) { + LOGGER.info("propagationRequired"); + if (callback != null) callback.accept(ops); + } + + @Transactional(transactionManager = BeanNames.COUCHBASE_SIMPLE_CALLBACK_TRANSACTION_MANAGER, propagation = Propagation.MANDATORY) + public void propagationMandatory(@Nullable Consumer callback) { + LOGGER.info("propagationMandatory"); + if (callback != null) callback.accept(ops); + } + + @Transactional(transactionManager = BeanNames.COUCHBASE_SIMPLE_CALLBACK_TRANSACTION_MANAGER, propagation = Propagation.NESTED) + public void propagationNested(@Nullable Consumer callback) { + LOGGER.info("propagationNever"); + if (callback != null) callback.accept(ops); + } + + @Transactional(transactionManager = BeanNames.COUCHBASE_SIMPLE_CALLBACK_TRANSACTION_MANAGER, propagation = Propagation.SUPPORTS) + public void propagationSupports(@Nullable Consumer callback) { + LOGGER.info("propagationSupports"); + if (callback != null) callback.accept(ops); + } + + @Transactional(transactionManager = BeanNames.COUCHBASE_SIMPLE_CALLBACK_TRANSACTION_MANAGER, propagation = Propagation.NOT_SUPPORTED) + public void propagationNotSupported(@Nullable Consumer callback) { + LOGGER.info("propagationNotSupported"); + if (callback != null) callback.accept(ops); + } + + @Transactional(transactionManager = BeanNames.COUCHBASE_SIMPLE_CALLBACK_TRANSACTION_MANAGER, propagation = Propagation.REQUIRES_NEW) + public void propagationRequiresNew(@Nullable Consumer callback) { + LOGGER.info("propagationRequiresNew"); + if (callback != null) callback.accept(ops); + } + + @Transactional(transactionManager = BeanNames.COUCHBASE_SIMPLE_CALLBACK_TRANSACTION_MANAGER, propagation = Propagation.NEVER) + public void propagationNever(@Nullable Consumer callback) { + LOGGER.info("propagationNever"); + if (callback != null) callback.accept(ops); + } + } +} From 56201d15a0518eabd4bddbac99e0670e7258cec5 Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Fri, 27 May 2022 17:44:01 +0100 Subject: [PATCH 03/15] Adding new tests for repository calls inside @Transactional One test is failure due to what looks like a bug elsewhere. --- .../data/couchbase/domain/UserRepository.java | 3 + ...ansactionalRepositoryIntegrationTests.java | 156 ++++++++++++++++++ .../util/TransactionTestUtil.java | 41 +++++ 3 files changed, 200 insertions(+) create mode 100644 src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalRepositoryIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/couchbase/transactions/util/TransactionTestUtil.java diff --git a/src/test/java/org/springframework/data/couchbase/domain/UserRepository.java b/src/test/java/org/springframework/data/couchbase/domain/UserRepository.java index 31b5eab3c..09398efaa 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/UserRepository.java +++ b/src/test/java/org/springframework/data/couchbase/domain/UserRepository.java @@ -67,4 +67,7 @@ default List getByFirstname(String firstname) { } catch (InterruptedException ie) {} return findByFirstname(firstname); } + + @Override + User save(User user); } diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalRepositoryIntegrationTests.java new file mode 100644 index 000000000..8de0f821f --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalRepositoryIntegrationTests.java @@ -0,0 +1,156 @@ +/* + * Copyright 2012-2022 the original author or authors + * + * 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 org.springframework.data.couchbase.transactions; + +import com.couchbase.client.java.transactions.error.TransactionFailedException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.data.couchbase.CouchbaseClientFactory; +import org.springframework.data.couchbase.config.BeanNames; +import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.data.couchbase.domain.User; +import org.springframework.data.couchbase.domain.UserRepository; +import org.springframework.data.couchbase.util.ClusterType; +import org.springframework.data.couchbase.util.IgnoreWhen; +import org.springframework.data.couchbase.util.JavaIntegrationTests; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.UUID; +import java.util.function.Consumer; + +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; +import static org.springframework.data.couchbase.transactions.util.TransactionTestUtil.assertInTransaction; +import static org.springframework.data.couchbase.transactions.util.TransactionTestUtil.assertNotInTransaction; + +/** + * Tests @Transactional with repository methods. + */ +@IgnoreWhen(clusterTypes = ClusterType.MOCKED) +@SpringJUnitConfig(Config.class) +public class CouchbaseTransactionalRepositoryIntegrationTests extends JavaIntegrationTests { + @Autowired UserRepository userRepo; + @Autowired CouchbaseClientFactory couchbaseClientFactory; + PersonService personService; + @Autowired + CouchbaseTemplate operations; + static GenericApplicationContext context; + + @BeforeAll + public static void beforeAll() { + callSuperBeforeAll(new Object() {}); + context = new AnnotationConfigApplicationContext(Config.class, PersonService.class); + } + + @BeforeEach + public void beforeEachTest() { + personService = context.getBean(PersonService.class); + } + + @AfterEach + public void afterEachTest() { + assertNotInTransaction(); + } + + + @Test + public void findByFirstname() { + operations.insertById(User.class).one(new User(UUID.randomUUID().toString(), "Ada", "Lovelace")); + + List users = personService.findByFirstname("Ada"); + + assertNotEquals(0, users.size()); + } + + @Test + public void save() { + String id = UUID.randomUUID().toString(); + + personService.run(repo -> { + assertInTransaction(); + + repo.save(new User(id, "Ada", "Lovelace")); + + assertInTransaction(); + + // read your own write + // todo gpx this is failing because it's being executed non-transactionally, due to a bug somewhere + User user = operations.findById(User.class).one(id); + assertNotNull(user); + + assertInTransaction(); + + }); + + User user = operations.findById(User.class).one(id); + assertNotNull(user); + } + + @DisplayName("Test that repo.save() is actually performed transactionally, by forcing a rollback") + @Test + public void saveRolledBack() { + String id = UUID.randomUUID().toString(); + + try { + personService.run(repo -> { + repo.save(new User(id, "Ada", "Lovelace")); + SimulateFailureException.throwEx("fail"); + }); + fail(); + } + catch (TransactionFailedException ignored) { + } + + User user = operations.findById(User.class).one(id); + assertNull(user); + } + + + @Service + @Component + @EnableTransactionManagement + static + class PersonService { + @Autowired UserRepository userRepo; + + @Transactional(transactionManager = BeanNames.COUCHBASE_SIMPLE_CALLBACK_TRANSACTION_MANAGER) + public void run(Consumer callback) { + callback.accept(userRepo); + } + + @Transactional(transactionManager = BeanNames.COUCHBASE_SIMPLE_CALLBACK_TRANSACTION_MANAGER) + public List findByFirstname(String name) { + return userRepo.findByFirstname(name); + } + + } +} diff --git a/src/test/java/org/springframework/data/couchbase/transactions/util/TransactionTestUtil.java b/src/test/java/org/springframework/data/couchbase/transactions/util/TransactionTestUtil.java new file mode 100644 index 000000000..e3bfb4c18 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/transactions/util/TransactionTestUtil.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2022 the original author or authors + * + * 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 org.springframework.data.couchbase.transactions.util; + +import org.springframework.transaction.NoTransactionException; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Utility methods for transaction tests. + */ +public class TransactionTestUtil { + private TransactionTestUtil() {} + + public static void assertInTransaction() { + assertTrue(org.springframework.transaction.support.TransactionSynchronizationManager.isActualTransactionActive()); + } + + public static void assertNotInTransaction() { + try { + assertFalse(org.springframework.transaction.support.TransactionSynchronizationManager.isActualTransactionActive()); + } + catch (NoTransactionException ignored) { + } + } + +} From 673a00d943a6b0e6d4e4b86b104a7f8b490a9790 Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Fri, 27 May 2022 17:45:06 +0100 Subject: [PATCH 04/15] Rename CouchbaseTransactionalIntegrationTests, and check after each test that we're not in a transaction. --- ...chbaseTransactionalTemplateIntegrationTests.java} | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) rename src/test/java/org/springframework/data/couchbase/transactions/{CouchbaseTransactionalIntegrationTests.java => CouchbaseTransactionalTemplateIntegrationTests.java} (98%) diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalTemplateIntegrationTests.java similarity index 98% rename from src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalIntegrationTests.java rename to src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalTemplateIntegrationTests.java index cee3a1fb0..62e1272e3 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalTemplateIntegrationTests.java @@ -18,6 +18,7 @@ import com.couchbase.client.java.transactions.error.TransactionFailedException; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; @@ -57,13 +58,14 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.springframework.data.couchbase.transactions.util.TransactionTestUtil.assertNotInTransaction; /** - * Tests for @Transactional. + * Tests for @Transactional, using template methods (findById etc.) */ @IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED) @SpringJUnitConfig(Config.class) -public class CouchbaseTransactionalIntegrationTests extends JavaIntegrationTests { +public class CouchbaseTransactionalTemplateIntegrationTests extends JavaIntegrationTests { @Autowired CouchbaseClientFactory couchbaseClientFactory; /* DO NOT @Autowired - it will result in no @Transactional annotation behavior */ PersonService personService; @@ -97,6 +99,11 @@ public void beforeEachTest() { } } + @AfterEach + public void afterEachTest() { + assertNotInTransaction(); + } + @DisplayName("A basic golden path insert should succeed") @Test public void committedInsert() { @@ -268,7 +275,6 @@ public void concurrentTxns() { // todo gpx investigate how @Transactional @Rollback/@Commit interacts with us // todo gpx how to provide per-transaction options? - // todo gpx verify we aren't in a transactional context after the transaction ends (success or failure) @Disabled("taking too long - must fix") @DisplayName("Create a Person outside a @Transactional block, modify it, and then replace that person in the @Transactional. The transaction will retry until timeout.") From 1be459f28a31ed4369dfbaade0a68935502821ac Mon Sep 17 00:00:00 2001 From: Michael Reiche <48999328+mikereiche@users.noreply.github.com> Date: Fri, 27 May 2022 10:57:42 -0700 Subject: [PATCH 05/15] Remove unnecessary methods From ReactiveCouchbaseClientFactory. Also rationalized naming of methods and other changes. --- .../ReactiveCouchbaseClientFactory.java | 35 ++---- .../SimpleReactiveCouchbaseClientFactory.java | 119 ++++++------------ .../data/couchbase/core/GenericSupport.java | 64 ---------- .../core/ReactiveCouchbaseTemplate.java | 51 ++------ .../ReactiveExistsByIdOperationSupport.java | 2 +- ...activeFindByAnalyticsOperationSupport.java | 4 +- .../ReactiveFindByIdOperationSupport.java | 7 +- .../ReactiveFindByQueryOperationSupport.java | 112 +++++++++-------- ...eFindFromReplicasByIdOperationSupport.java | 2 +- .../ReactiveInsertByIdOperationSupport.java | 3 +- .../ReactiveRemoveByIdOperationSupport.java | 9 +- ...ReactiveRemoveByQueryOperationSupport.java | 4 +- .../ReactiveReplaceByIdOperationSupport.java | 10 +- .../ReactiveUpsertByIdOperationSupport.java | 3 +- .../couchbase/core/TransactionalSupport.java | 6 +- ...hbaseSimpleCallbackTransactionManager.java | 6 +- .../CouchbaseTransactionalOperator.java | 6 +- .../ReactiveCouchbaseClientUtils.java | 10 +- .../ReactiveCouchbaseTransactionManager.java | 11 +- .../ReactiveTransactionsWrapper.java | 6 +- ...basePersonTransactionIntegrationTests.java | 8 -- ...onTransactionReactiveIntegrationTests.java | 9 -- 22 files changed, 143 insertions(+), 344 deletions(-) delete mode 100644 src/main/java/org/springframework/data/couchbase/core/GenericSupport.java diff --git a/src/main/java/org/springframework/data/couchbase/ReactiveCouchbaseClientFactory.java b/src/main/java/org/springframework/data/couchbase/ReactiveCouchbaseClientFactory.java index 13f0f9773..ca2be1c05 100644 --- a/src/main/java/org/springframework/data/couchbase/ReactiveCouchbaseClientFactory.java +++ b/src/main/java/org/springframework/data/couchbase/ReactiveCouchbaseClientFactory.java @@ -16,12 +16,10 @@ package org.springframework.data.couchbase; import com.couchbase.client.core.transaction.CoreTransactionAttemptContext; -import com.couchbase.client.java.Bucket; import com.couchbase.client.java.Cluster; import com.couchbase.client.java.ClusterInterface; import com.couchbase.client.java.Collection; import com.couchbase.client.java.Scope; -import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; import com.couchbase.client.java.transactions.config.TransactionOptions; import org.springframework.data.couchbase.transaction.CouchbaseTransactionalOperator; import org.springframework.data.couchbase.transaction.ReactiveCouchbaseResourceHolder; @@ -44,46 +42,31 @@ public interface ReactiveCouchbaseClientFactory /*extends CodecRegistryProvider* /** * Provides access to the managed SDK {@link Cluster} reference. */ - Mono getCluster(); + ClusterInterface getCluster(); /** - * Provides access to the managed SDK {@link Cluster} reference. - */ - ClusterInterface getBlockingCluster(); - - /** - * Provides access to the managed SDK {@link Bucket} reference. - */ - Mono getBucket(); - - /** - * Provides access to the managed SDK {@link Scope} reference. + * Provides access to the managed SDK {@link Scope} reference */ - Mono getScope(); + Scope getScope(String scopeName); /** - * Provides access to the managed SDK {@link Scope} reference without block() + * Provides access to the managed SDK {@link Scope} reference */ - Scope getBlockingScope(String scopeName); + Scope getScope(); /** * Provides access to a collection (identified by its name) in managed SDK {@link Scope} reference. * * @param name the name of the collection. If null is passed in, the default collection is assumed. */ - Mono getCollection(String name); + Mono getCollectionMono(String name); /** * Provides access to a collection (identified by its name) without block() * * @param name the name of the collection. If null is passed in, the default collection is assumed. */ - Collection getBlockingCollection(String collectionName); - - /** - * Provides access to the default collection. - */ - Mono getDefaultCollection(); + Collection getCollection(String collectionName); /** * Returns a new {@link CouchbaseClientFactory} set to the scope given as an argument. @@ -98,7 +81,7 @@ public interface ReactiveCouchbaseClientFactory /*extends CodecRegistryProvider* */ PersistenceExceptionTranslator getExceptionTranslator(); - Mono getTransactionResources(TransactionOptions options); + Mono getResourceHolderMono(); String getBucketName(); @@ -106,7 +89,7 @@ public interface ReactiveCouchbaseClientFactory /*extends CodecRegistryProvider* void close() throws IOException; - ReactiveCouchbaseResourceHolder getTransactionResources(TransactionOptions options, CoreTransactionAttemptContext ctx); + ReactiveCouchbaseResourceHolder getResourceHolder(TransactionOptions options, CoreTransactionAttemptContext ctx); /* * (non-Javadoc) diff --git a/src/main/java/org/springframework/data/couchbase/SimpleReactiveCouchbaseClientFactory.java b/src/main/java/org/springframework/data/couchbase/SimpleReactiveCouchbaseClientFactory.java index 5e90fc7e7..7a3402a10 100644 --- a/src/main/java/org/springframework/data/couchbase/SimpleReactiveCouchbaseClientFactory.java +++ b/src/main/java/org/springframework/data/couchbase/SimpleReactiveCouchbaseClientFactory.java @@ -22,14 +22,12 @@ import org.springframework.data.couchbase.transaction.SessionAwareMethodInterceptor; import org.springframework.util.ObjectUtils; -import com.couchbase.client.java.Bucket; import com.couchbase.client.java.Cluster; import com.couchbase.client.java.Collection; import com.couchbase.client.java.Scope; public class SimpleReactiveCouchbaseClientFactory implements ReactiveCouchbaseClientFactory { - final Mono cluster; - final ClusterInterface theCluster; + final ClusterInterface cluster; final String bucketName; final String scopeName; final PersistenceExceptionTranslator exceptionTranslator; @@ -38,9 +36,8 @@ public class SimpleReactiveCouchbaseClientFactory implements ReactiveCouchbaseCl CouchbaseTransactionalOperator transactionalOperator; public SimpleReactiveCouchbaseClientFactory(Cluster cluster, String bucketName, String scopeName, - CouchbaseTransactionalOperator transactionalOperator) { - this.cluster = Mono.just(cluster); - this.theCluster = cluster; + CouchbaseTransactionalOperator transactionalOperator) { + this.cluster = cluster; this.bucketName = bucketName; this.scopeName = scopeName; this.exceptionTranslator = new CouchbaseExceptionTranslator(); @@ -53,20 +50,10 @@ public SimpleReactiveCouchbaseClientFactory(Cluster cluster, String bucketName, this(cluster, bucketName, scopeName, null); } - @Override - public Mono getCluster() { - return cluster; - } - - - @Override - public ClusterInterface getBlockingCluster() { - return theCluster; - } @Override - public Mono getBucket() { - return cluster.map((c) -> c.bucket(bucketName)); + public ClusterInterface getCluster() { + return cluster; } @Override @@ -75,13 +62,13 @@ public String getBucketName() { } @Override - public Mono getScope() { - return cluster.map((c) -> c.bucket(bucketName).scope(scopeName != null ? scopeName : DEFAULT_SCOPE)); + public Scope getScope(String scopeName) { + return cluster.bucket(bucketName) + .scope(scopeName != null ? scopeName : (this.scopeName != null ? this.scopeName : DEFAULT_SCOPE)); } - @Override - public Scope getBlockingScope(String scopeName) { - return theCluster.bucket(bucketName).scope(scopeName != null ? scopeName : (this.scopeName != null ? this.scopeName : DEFAULT_SCOPE)); + @Override public Scope getScope(){ + return getScope(null); } @Override @@ -90,7 +77,7 @@ public String getScopeName() { } @Override - public Mono getCollection(String collectionName) { + public Mono getCollectionMono(String collectionName) { if (getScopeName() != null && !DEFAULT_SCOPE.equals(getScopeName())) { if (collectionName == null || DEFAULT_COLLECTION.equals(collectionName)) { throw new IllegalStateException("A collectionName must be provided if a non-default scope is used."); @@ -102,11 +89,11 @@ public Mono getCollection(String collectionName) { "A collectionName must be null or " + DEFAULT_COLLECTION + " if scope is null or " + DEFAULT_SCOPE); } } - return getScope().map((s) -> s.collection(collectionName != null ? collectionName : DEFAULT_COLLECTION)); + return Mono.just(getScope()).map((s) -> s.collection(collectionName != null ? collectionName : DEFAULT_COLLECTION)); } @Override - public Collection getBlockingCollection(String collectionName) { + public Collection getCollection(String collectionName) { if (getScopeName() != null && !DEFAULT_SCOPE.equals(getScopeName())) { if (collectionName == null || DEFAULT_COLLECTION.equals(collectionName)) { throw new IllegalStateException("A collectionName must be provided if a non-default scope is used."); @@ -118,20 +105,13 @@ public Collection getBlockingCollection(String collectionName) { "A collectionName must be null or " + DEFAULT_COLLECTION + " if scope is null or " + DEFAULT_SCOPE); } } - return theCluster.bucket(bucketName).scope(scopeName != null ? scopeName : DEFAULT_SCOPE).collection(collectionName != null ? collectionName : DEFAULT_COLLECTION); - } - - @Override - public Mono getDefaultCollection() { - if (getScopeName() != null && DEFAULT_SCOPE.equals(getScopeName())) { - throw new IllegalStateException("A collectionName must be provided if a non-default scope is used."); - } - return cluster.map((c) -> c.bucket(bucketName).defaultCollection()); + return cluster.bucket(bucketName).scope(scopeName != null ? scopeName : DEFAULT_SCOPE) + .collection(collectionName != null ? collectionName : DEFAULT_COLLECTION); } @Override public ReactiveCouchbaseClientFactory withScope(String scopeName) { - return new SimpleReactiveCouchbaseClientFactory((Cluster) cluster.block(), bucketName, + return new SimpleReactiveCouchbaseClientFactory((Cluster) cluster, bucketName, scopeName != null ? scopeName : this.scopeName); } @@ -142,17 +122,17 @@ public PersistenceExceptionTranslator getExceptionTranslator() { @Override public void close() { - cluster.block().disconnect(); + cluster.disconnect(); } @Override - public Mono getTransactionResources(TransactionOptions options) { - return Mono.just(new ReactiveCouchbaseResourceHolder(null)); + public Mono getResourceHolderMono() { + return Mono.just(new ReactiveCouchbaseResourceHolder(null)); } @Override - public ReactiveCouchbaseResourceHolder getTransactionResources(TransactionOptions options, - CoreTransactionAttemptContext atr) { + public ReactiveCouchbaseResourceHolder getResourceHolder(TransactionOptions options, + CoreTransactionAttemptContext atr) { if (atr == null) { atr = AttemptContextReactiveAccessor .newCoreTranactionAttemptContext(AttemptContextReactiveAccessor.reactive(transactions)); @@ -177,7 +157,7 @@ public CouchbaseTransactionalOperator getTransactionalOperator() { @Override public ReactiveCouchbaseClientFactory with(CouchbaseTransactionalOperator txOp) { - return new SimpleReactiveCouchbaseClientFactory((Cluster) getCluster().block(), bucketName, scopeName, txOp); + return new SimpleReactiveCouchbaseClientFactory((Cluster) getCluster(), bucketName, scopeName, txOp); } private T createProxyInstance(ReactiveCouchbaseResourceHolder session, T target, Class targetType) { @@ -213,56 +193,37 @@ static final class CoreTransactionAttemptContextBoundCouchbaseClientFactory private final ReactiveCouchbaseResourceHolder transactionResources; private final ReactiveCouchbaseClientFactory delegate; - // private final Transactions transactions; CoreTransactionAttemptContextBoundCouchbaseClientFactory(ReactiveCouchbaseResourceHolder transactionResources, - ReactiveCouchbaseClientFactory delegate, Transactions transactions) { + ReactiveCouchbaseClientFactory delegate, Transactions transactions) { this.transactionResources = transactionResources; this.delegate = delegate; - // this.transactions = transactions; } - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.ReactiveMongoDatabaseFactory#getMongoDatabase() - */ - @Override - public Mono getCluster() throws DataAccessException { - return delegate.getCluster().map(this::decorateDatabase); - } @Override - public ClusterInterface getBlockingCluster() throws DataAccessException { - return decorateDatabase(delegate.getBlockingCluster()); + public ClusterInterface getCluster() throws DataAccessException { + return decorateDatabase(delegate.getCluster()); } @Override - public Mono getBucket() { - return delegate.getBucket(); + public Mono getCollectionMono(String name) { + return Mono.just(delegate.getCollection(name)); } @Override - public Mono getScope() { - return delegate.getScope(); + public Collection getCollection(String collectionName) { + return delegate.getCollection(collectionName); } @Override - public Mono getCollection(String name) { - return delegate.getCollection(name); + public Scope getScope(String scopeName) { + return delegate.getScope(scopeName); } @Override - public Collection getBlockingCollection(String collectionName) { - return delegate.getBlockingCollection(collectionName); - } - - @Override - public Scope getBlockingScope(String scopeName) { - return delegate.getBlockingScope(scopeName); - } - @Override - public Mono getDefaultCollection() { - return delegate.getDefaultCollection(); + public Scope getScope() { + return delegate.getScope(); } @Override @@ -300,14 +261,14 @@ public void close() throws IOException { */ @Override - public Mono getTransactionResources(TransactionOptions options) { + public Mono getResourceHolderMono() { return Mono.just(transactionResources); } @Override - public ReactiveCouchbaseResourceHolder getTransactionResources(TransactionOptions options, - CoreTransactionAttemptContext atr) { - ReactiveCouchbaseResourceHolder holder = delegate.getTransactionResources(options, atr); + public ReactiveCouchbaseResourceHolder getResourceHolder(TransactionOptions options, + CoreTransactionAttemptContext atr) { + ReactiveCouchbaseResourceHolder holder = delegate.getResourceHolder(options, atr); return holder; } @@ -364,10 +325,6 @@ private T createProxyInstance(ReactiveCouchbaseResourceHolder session, T tar return targetType.cast(factory.getProxy(target.getClass().getClassLoader())); } - public ReactiveCouchbaseResourceHolder getTransactionResources() { - return this.transactionResources; - } - public ReactiveCouchbaseClientFactory getDelegate() { return this.delegate; } @@ -396,7 +353,7 @@ public int hashCode() { public String toString() { return "SimpleReactiveCouchbaseDatabaseFactory.CoreTransactionAttemptContextBoundCouchDbFactory(session=" - + this.getTransactionResources() + ", delegate=" + this.getDelegate() + ")"; + + this.getResourceHolderMono() + ", delegate=" + this.getDelegate() + ")"; } } } diff --git a/src/main/java/org/springframework/data/couchbase/core/GenericSupport.java b/src/main/java/org/springframework/data/couchbase/core/GenericSupport.java deleted file mode 100644 index adf8f0153..000000000 --- a/src/main/java/org/springframework/data/couchbase/core/GenericSupport.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.springframework.data.couchbase.core; - -import com.couchbase.client.core.transaction.CoreTransactionAttemptContext; -import reactor.core.publisher.Mono; - -import java.util.function.Function; - -import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; -import org.springframework.data.couchbase.transaction.CouchbaseTransactionalOperator; -import org.springframework.lang.Nullable; - -import com.couchbase.client.core.annotation.Stability; -import com.couchbase.client.java.ReactiveCollection; - -// todo gp better name -@Stability.Internal -class GenericSupportHelper { - public final CouchbaseDocument converted; - public final Long cas; - public final ReactiveCollection collection; - public final @Nullable CoreTransactionAttemptContext ctx; - - public GenericSupportHelper(CouchbaseDocument doc, Long cas, ReactiveCollection collection, - @Nullable CoreTransactionAttemptContext ctx) { - this.converted = doc; - this.cas = cas; - this.collection = collection; - this.ctx = ctx; - } -} - -// todo gp better name -@Stability.Internal -public class GenericSupport { - public static Mono one(Mono tmpl, CouchbaseTransactionalOperator transactionalOperator, - String scopeName, String collectionName, ReactiveTemplateSupport support, T object, - Function> nonTransactional, Function> transactional) { - // todo gp how safe is this? I think we can switch threads potentially - // Optional ctxr = Optional.ofNullable((TransactionAttemptContext) - // org.springframework.transaction.support.TransactionSynchronizationManager.getResource(TransactionAttemptContext.class)); - - return tmpl.flatMap(template -> template.getCouchbaseClientFactory().withScope(scopeName) - .getCollection(collectionName).flatMap(collection -> support.encodeEntity(object) - .flatMap(converted -> tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getTransactionResources(null).flatMap(s -> { - GenericSupportHelper gsh = new GenericSupportHelper(converted, support.getCas(object), - collection.reactive(), s.getCore() != null ? s.getCore() - : (transactionalOperator != null ? transactionalOperator.getAttemptContext() : null)); - if (gsh.ctx == null) { - System.err.println("non-tx"); - return nonTransactional.apply(gsh); - } else { - System.err.println("tx"); - return transactional.apply(gsh); - } - })).onErrorMap(throwable -> { - if (throwable instanceof RuntimeException) { - return template.potentiallyConvertRuntimeException((RuntimeException) throwable); - } else { - return throwable; - } - })))); - } - -} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplate.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplate.java index 4450da160..98a5c6546 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplate.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplate.java @@ -63,7 +63,7 @@ public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations, A private final PersistenceExceptionTranslator exceptionTranslator; private final ReactiveCouchbaseTemplateSupport templateSupport; private ThreadLocal> threadLocalArgs = new ThreadLocal<>(); - private QueryScanConsistency scanConsistency; + private final QueryScanConsistency scanConsistency; public ReactiveCouchbaseTemplate with(CouchbaseTransactionalOperator txOp) { // TODO: why does txOp go on the clientFactory? can't we just put it on the template?? @@ -90,19 +90,6 @@ public ReactiveCouchbaseTemplate(final ReactiveCouchbaseClientFactory clientFact this.scanConsistency = scanConsistency; } - // public ReactiveCouchbaseTemplate(final CouchbaseClientFactory clientFactory, final CouchbaseConverter converter) { - // this(clientFactory, converter, new JacksonTranslationService()); - // } - - // public ReactiveCouchbaseTemplate(final ReactiveCouchbaseClientFactory clientFactory, final CouchbaseConverter - // converter, - // final TranslationService translationService) { - // this.clientFactory = clientFactory; - // this.converter = converter; - // this.exceptionTranslator = this.clientFactory.getExceptionTranslator(); - // this.templateSupport = new ReactiveCouchbaseTemplateSupport(this, converter, translationService); - // } - public Mono save(T entity) { Assert.notNull(entity, "Entity must not be null!"); Mono result; @@ -195,12 +182,12 @@ public ReactiveUpsertById upsertById(Class domainType) { @Override public String getBucketName() { - return clientFactory.getBucket().block().name(); + return clientFactory.getBucketName(); } @Override public String getScopeName() { - return clientFactory.getScope().block().name(); + return clientFactory.getScopeName(); } @Override @@ -215,7 +202,7 @@ public ReactiveCouchbaseClientFactory getCouchbaseClientFactory() { * @return the collection instance. */ public Collection getCollection(final String collectionName) { - return clientFactory.getCollection(collectionName).block(); + return clientFactory.getCollection(collectionName); } @Override @@ -276,36 +263,14 @@ protected Mono doGetTemplate() { this.getConverter()); } - /* - private Flux withSession(ReactiveSessionCallback action, ClientSession session) { - - ReactiveSessionBoundCouchbaseTemplate operations = new ReactiveSessionBoundCouchbaseTemplate(session, - ReactiveCouchbaseTemplate.this); - - return Flux.from(action.doInSession(operations)) // - .contextWrite(ctx -> ReactiveMongoContext.setSession(ctx, Mono.just(session))); - } - */ /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#withSession(com.mongodb.session.ClientSession) */ - public ReactiveCouchbaseOperations withCore(ReactiveCouchbaseResourceHolder core) { - return new ReactiveSessionBoundCouchbaseTemplate(core, ReactiveCouchbaseTemplate.this); + public ReactiveCouchbaseOperations withResources(ReactiveCouchbaseResourceHolder resources) { + return new ReactiveResourcesBoundCouchbaseTemplate(resources, ReactiveCouchbaseTemplate.this); } - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#withSession(com.mongodb.ClientSessionOptions) - */ - /* - @Override - public ReactiveSessionScoped withSession(ClientSessionOptions sessionOptions) { - return withSession(mongoDatabaseFactory.getSession(sessionOptions)); - } - - */ - /** * {@link CouchbaseTemplate} extension bound to a specific {@link CoreTransactionAttemptContext} that is applied when * interacting with the server through the driver API.
@@ -315,7 +280,7 @@ public ReactiveSessionScoped withSession(ClientSessionOptions sessionOptions) { * @author Christoph Strobl * @since 2.1 */ - static class ReactiveSessionBoundCouchbaseTemplate extends ReactiveCouchbaseTemplate { + static class ReactiveResourcesBoundCouchbaseTemplate extends ReactiveCouchbaseTemplate { private final ReactiveCouchbaseTemplate delegate; private final ReactiveCouchbaseResourceHolder holder; @@ -324,7 +289,7 @@ static class ReactiveSessionBoundCouchbaseTemplate extends ReactiveCouchbaseTemp * @param holder must not be {@literal null}. * @param that must not be {@literal null}. */ - ReactiveSessionBoundCouchbaseTemplate(ReactiveCouchbaseResourceHolder holder, ReactiveCouchbaseTemplate that) { + ReactiveResourcesBoundCouchbaseTemplate(ReactiveCouchbaseResourceHolder holder, ReactiveCouchbaseTemplate that) { super(that.clientFactory.withCore(holder), that.getConverter()); diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveExistsByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveExistsByIdOperationSupport.java index 778299930..94e2dddb5 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveExistsByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveExistsByIdOperationSupport.java @@ -77,7 +77,7 @@ public Mono one(final String id) { return TransactionalSupport.verifyNotInTransaction(template.doGetTemplate(), "existsById") .then(Mono.just(id)) .flatMap(docId -> template.getCouchbaseClientFactory().withScope(pArgs.getScope()) - .getBlockingCollection(pArgs.getCollection()).reactive().exists(id, buildOptions(pArgs.getOptions())) + .getCollection(pArgs.getCollection()).reactive().exists(id, buildOptions(pArgs.getOptions())) .map(ExistsResult::exists)) .onErrorMap(throwable -> { if (throwable instanceof RuntimeException) { diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperationSupport.java index ea47bc77e..8fa592eca 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperationSupport.java @@ -110,7 +110,7 @@ public Flux all() { return Flux.defer(() -> { String statement = assembleEntityQuery(false); return TransactionalSupport.verifyNotInTransaction(template.doGetTemplate(), "findByAnalytics") - .then(template.getCouchbaseClientFactory().getCluster().block().reactive() + .then(template.getCouchbaseClientFactory().getCluster().reactive() .analyticsQuery(statement, buildAnalyticsOptions())).onErrorMap(throwable -> { if (throwable instanceof RuntimeException) { return template.potentiallyConvertRuntimeException((RuntimeException) throwable); @@ -143,7 +143,7 @@ public Flux all() { public Mono count() { return Mono.defer(() -> { String statement = assembleEntityQuery(true); - return template.getCouchbaseClientFactory().getBlockingCluster().reactive() + return template.getCouchbaseClientFactory().getCluster().reactive() .analyticsQuery(statement, buildAnalyticsOptions()).onErrorMap(throwable -> { if (throwable instanceof RuntimeException) { return template.potentiallyConvertRuntimeException((RuntimeException) throwable); diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java index 475b719cc..e456e0147 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java @@ -18,13 +18,10 @@ import static com.couchbase.client.java.kv.GetAndTouchOptions.getAndTouchOptions; import static com.couchbase.client.java.transactions.internal.ConverterUtil.makeCollectionIdentifier; -import com.couchbase.client.core.transaction.CoreTransactionGetResult; -import com.couchbase.client.java.transactions.TransactionGetResult; import org.springframework.data.couchbase.repository.support.TransactionResultHolder; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Arrays; @@ -93,12 +90,12 @@ public Mono one(final String id) { LOG.trace("findById {}", pArgs); ReactiveCollection rc = template.getCouchbaseClientFactory().withScope(pArgs.getScope()) - .getBlockingCollection(pArgs.getCollection()).reactive(); + .getCollection(pArgs.getCollection()).reactive(); // this will get me a template with a session holding tx Mono tmpl = template.doGetTemplate(); - Mono reactiveEntity = tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getTransactionResources(null) + Mono reactiveEntity = tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getResourceHolderMono() .flatMap(s -> { System.err.println("Session: "+s); //Mono reactiveEntity = Mono.defer(() -> { diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java index cb123fdd5..b717b589a 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java @@ -76,9 +76,9 @@ static class ReactiveFindByQuerySupport implements ReactiveFindByQuery { private final ReactiveTemplateSupport support; ReactiveFindByQuerySupport(final ReactiveCouchbaseTemplate template, final Class domainType, - final Class returnType, final Query query, final QueryScanConsistency scanConsistency, final String scope, - final String collection, final QueryOptions options, final String[] distinctFields, String[] fields, - final CouchbaseTransactionalOperator txCtx, final ReactiveTemplateSupport support) { + final Class returnType, final Query query, final QueryScanConsistency scanConsistency, final String scope, + final String collection, final QueryOptions options, final String[] distinctFields, String[] fields, + final CouchbaseTransactionalOperator txCtx, final ReactiveTemplateSupport support) { Assert.notNull(domainType, "domainType must not be null!"); Assert.notNull(returnType, "returnType must not be null!"); this.template = template; @@ -190,20 +190,21 @@ public Flux all() { LOG.trace("findByQuery {} statement: {}", pArgs, statement); ReactiveCouchbaseClientFactory clientFactory = template.getCouchbaseClientFactory(); - ReactiveScope rs = clientFactory.getBlockingScope(pArgs.getScope()).reactive(); + ReactiveScope rs = clientFactory.getScope(pArgs.getScope()).reactive(); Mono tmpl = template.doGetTemplate(); - Mono allResult = tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getTransactionResources(null).flatMap(s -> { - if (s.getCore() == null) { - QueryOptions opts = buildOptions(pArgs.getOptions()); - return pArgs.getScope() == null ? clientFactory.getCluster().block().reactive().query(statement, opts) - : rs.query(statement, opts); - } else { - TransactionQueryOptions opts = buildTransactionOptions(pArgs.getOptions()); - return (AttemptContextReactiveAccessor.createReactiveTransactionAttemptContext(s.getCore(), - clientFactory.getCluster().block().environment().jsonSerializer())).query(statement, opts); - } - })); + Mono allResult = tmpl + .flatMap(tp -> tp.getCouchbaseClientFactory().getResourceHolderMono().flatMap(s -> { + if (s.getCore() == null) { + QueryOptions opts = buildOptions(pArgs.getOptions()); + return pArgs.getScope() == null ? clientFactory.getCluster().reactive().query(statement, opts) + : rs.query(statement, opts); + } else { + TransactionQueryOptions opts = buildTransactionOptions(pArgs.getOptions()); + return (AttemptContextReactiveAccessor.createReactiveTransactionAttemptContext(s.getCore(), + clientFactory.getCluster().environment().jsonSerializer())).query(statement, opts); + } + })); return allResult.onErrorMap(throwable -> { if (throwable instanceof RuntimeException) { @@ -213,27 +214,27 @@ public Flux all() { } }).flatMapMany(o -> o instanceof ReactiveQueryResult ? ((ReactiveQueryResult) o).rowsAsObject() : Flux.fromIterable(((TransactionQueryResult) o).rowsAsObject())).flatMap(row -> { - String id = ""; - long cas = 0; - if (!query.isDistinct() && distinctFields == null) { - if (row.getString(TemplateUtils.SELECT_ID) == null) { - return Flux.error(new CouchbaseException("query did not project " + TemplateUtils.SELECT_ID - + ". Either use #{#n1ql.selectEntity} or project " + TemplateUtils.SELECT_ID + " and " - + TemplateUtils.SELECT_CAS + " : " + statement)); - } - id = row.getString(TemplateUtils.SELECT_ID); - if (row.getLong(TemplateUtils.SELECT_CAS) == null) { - return Flux.error(new CouchbaseException("query did not project " + TemplateUtils.SELECT_CAS - + ". Either use #{#n1ql.selectEntity} or project " + TemplateUtils.SELECT_ID + " and " - + TemplateUtils.SELECT_CAS + " : " + statement)); - } - cas = row.getLong(TemplateUtils.SELECT_CAS); - row.removeKey(TemplateUtils.SELECT_ID); - row.removeKey(TemplateUtils.SELECT_CAS); - } - return support.decodeEntity(id, row.toString(), cas, returnType, pArgs.getScope(), pArgs.getCollection(), - null); - }); + String id = ""; + long cas = 0; + if (!query.isDistinct() && distinctFields == null) { + if (row.getString(TemplateUtils.SELECT_ID) == null) { + return Flux.error(new CouchbaseException("query did not project " + TemplateUtils.SELECT_ID + + ". Either use #{#n1ql.selectEntity} or project " + TemplateUtils.SELECT_ID + " and " + + TemplateUtils.SELECT_CAS + " : " + statement)); + } + id = row.getString(TemplateUtils.SELECT_ID); + if (row.getLong(TemplateUtils.SELECT_CAS) == null) { + return Flux.error(new CouchbaseException("query did not project " + TemplateUtils.SELECT_CAS + + ". Either use #{#n1ql.selectEntity} or project " + TemplateUtils.SELECT_ID + " and " + + TemplateUtils.SELECT_CAS + " : " + statement)); + } + cas = row.getLong(TemplateUtils.SELECT_CAS); + row.removeKey(TemplateUtils.SELECT_ID); + row.removeKey(TemplateUtils.SELECT_CAS); + } + return support.decodeEntity(id, row.toString(), cas, returnType, pArgs.getScope(), pArgs.getCollection(), + null); + }); } public QueryOptions buildOptions(QueryOptions options) { @@ -253,29 +254,30 @@ public Mono count() { LOG.trace("findByQuery {} statement: {}", pArgs, statement); ReactiveCouchbaseClientFactory clientFactory = template.getCouchbaseClientFactory(); - ReactiveScope rs = clientFactory.getBlockingScope(pArgs.getScope()).reactive(); + ReactiveScope rs = clientFactory.getScope(pArgs.getScope()).reactive(); Mono tmpl = template.doGetTemplate(); - Mono allResult = tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getTransactionResources(null).flatMap(s -> { - if (s.getCore() == null) { - QueryOptions opts = buildOptions(pArgs.getOptions()); - return pArgs.getScope() == null ? clientFactory.getBlockingCluster().reactive().query(statement, opts) - : rs.query(statement, opts); - } else { - TransactionQueryOptions opts = buildTransactionOptions(pArgs.getOptions()); - return (AttemptContextReactiveAccessor.createReactiveTransactionAttemptContext(s.getCore(), - clientFactory.getBlockingCluster().environment().jsonSerializer())).query(statement, opts); - } - })); - - return allResult.onErrorMap(throwable -> { - if (throwable instanceof RuntimeException) { - return template.potentiallyConvertRuntimeException((RuntimeException) throwable); + Mono allResult = tmpl + .flatMap(tp -> tp.getCouchbaseClientFactory().getResourceHolderMono().flatMap(s -> { + if (s.getCore() == null) { + QueryOptions opts = buildOptions(pArgs.getOptions()); + return pArgs.getScope() == null ? clientFactory.getCluster().reactive().query(statement, opts) + : rs.query(statement, opts); } else { - return throwable; + TransactionQueryOptions opts = buildTransactionOptions(pArgs.getOptions()); + return (AttemptContextReactiveAccessor.createReactiveTransactionAttemptContext(s.getCore(), + clientFactory.getCluster().environment().jsonSerializer())).query(statement, opts); } - }).flatMapMany(o -> o instanceof ReactiveQueryResult ? ((ReactiveQueryResult) o).rowsAsObject() - : Flux.fromIterable(((TransactionQueryResult) o).rowsAsObject())) + })); + + return allResult.onErrorMap(throwable -> { + if (throwable instanceof RuntimeException) { + return template.potentiallyConvertRuntimeException((RuntimeException) throwable); + } else { + return throwable; + } + }).flatMapMany(o -> o instanceof ReactiveQueryResult ? ((ReactiveQueryResult) o).rowsAsObject() + : Flux.fromIterable(((TransactionQueryResult) o).rowsAsObject())) .map(row -> row.getLong(row.getNames().iterator().next())).next(); } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperationSupport.java index cadca3839..78bf44bb3 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperationSupport.java @@ -78,7 +78,7 @@ public Mono any(final String id) { return TransactionalSupport.verifyNotInTransaction(template.doGetTemplate(), "findFromReplicasById") .then(Mono.just(id)) .flatMap(docId -> template.getCouchbaseClientFactory().withScope(pArgs.getScope()) - .getBlockingCollection(pArgs.getCollection()).reactive().getAnyReplica(docId, pArgs.getOptions())) + .getCollection(pArgs.getCollection()).reactive().getAnyReplica(docId, pArgs.getOptions())) .flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), returnType, pArgs.getScope(), pArgs.getCollection(), null)) .onErrorMap(throwable -> { if (throwable instanceof RuntimeException) { diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java index 4de3a5f3c..8429c359f 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java @@ -19,7 +19,6 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import java.lang.reflect.Method; import java.time.Duration; import java.util.Collection; @@ -125,7 +124,7 @@ public Mono one(T object) { return support.ctx .insert(makeCollectionIdentifier(support.collection.async()), support.converted.getId(), - template.getCouchbaseClientFactory().getBlockingCluster().environment().transcoder() + template.getCouchbaseClientFactory().getCluster().environment().transcoder() .encode(support.converted.export()).encoded()) .flatMap(result -> this.support.applyResult(object, support.converted, support.converted.getId(), getCas(result), new TransactionResultHolder(result), null)); diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java index fb8f3ee84..5d71a5799 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java @@ -15,17 +15,12 @@ */ package org.springframework.data.couchbase.core; -import com.couchbase.client.core.error.CasMismatchException; -import com.couchbase.client.core.error.transaction.RetryTransactionException; -import com.couchbase.client.core.error.transaction.TransactionOperationFailedException; import com.couchbase.client.core.transaction.CoreTransactionGetResult; -import com.couchbase.client.java.transactions.TransactionGetResult; import org.springframework.data.couchbase.ReactiveCouchbaseClientFactory; import org.springframework.data.couchbase.transaction.CouchbaseTransactionalOperator; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import java.lang.reflect.Method; import java.util.Collection; import org.slf4j.Logger; @@ -96,13 +91,13 @@ public Mono one(final String id) { PseudoArgs pArgs = new PseudoArgs<>(template, scope, collection, options, txCtx, domainType); LOG.trace("removeById {}", pArgs); ReactiveCouchbaseClientFactory clientFactory = template.getCouchbaseClientFactory(); - ReactiveCollection rc = clientFactory.withScope(pArgs.getScope()).getCollection(pArgs.getCollection()).block() + ReactiveCollection rc = clientFactory.withScope(pArgs.getScope()).getCollection(pArgs.getCollection()) .reactive(); Mono tmpl = template.doGetTemplate(); final Mono removeResult; // todo gpx convert to TransactionalSupport - Mono allResult = tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getTransactionResources(null).flatMap(s -> { + Mono allResult = tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getResourceHolderMono().flatMap(s -> { if (s.getCore() == null) { System.err.println("non-tx remove"); return rc.remove(id, buildRemoveOptions(pArgs.getOptions())).map(r -> RemoveResult.from(id, r)); diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java index d21bcab01..8428e6f83 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java @@ -83,10 +83,10 @@ public Flux all() { LOG.trace("removeByQuery {} statement: {}", pArgs, statement); Mono allResult = null; ReactiveCouchbaseClientFactory clientFactory = template.getCouchbaseClientFactory(); - ReactiveScope rs = clientFactory.getBlockingScope(pArgs.getScope()).reactive(); + ReactiveScope rs = clientFactory.getScope(pArgs.getScope()).reactive(); if (pArgs.getTxOp() == null) { QueryOptions opts = buildQueryOptions(pArgs.getOptions()); - allResult = pArgs.getScope() == null ? clientFactory.getBlockingCluster().reactive().query(statement, opts) + allResult = pArgs.getScope() == null ? clientFactory.getCluster().reactive().query(statement, opts) : rs.query(statement, opts); } else { TransactionQueryOptions opts = buildTransactionOptions(buildQueryOptions(pArgs.getOptions())); diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java index 383ed9c5f..37c55cd79 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java @@ -15,19 +15,12 @@ */ package org.springframework.data.couchbase.core; -import com.couchbase.client.core.error.CasMismatchException; -import com.couchbase.client.core.error.transaction.RetryTransactionException; -import com.couchbase.client.core.error.transaction.TransactionOperationFailedException; import com.couchbase.client.core.transaction.CoreTransactionGetResult; -import com.couchbase.client.java.transactions.TransactionGetResult; -import com.couchbase.client.core.error.transaction.RetryTransactionException; import com.couchbase.client.core.io.CollectionIdentifier; -import com.couchbase.client.core.transaction.CoreTransactionGetResult; import com.couchbase.client.core.transaction.util.DebugUtil; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import java.lang.reflect.Method; import java.time.Duration; import java.util.Collection; @@ -39,7 +32,6 @@ import org.springframework.data.couchbase.transaction.CouchbaseTransactionalOperator; import org.springframework.util.Assert; -import com.couchbase.client.core.error.CouchbaseException; import com.couchbase.client.core.msg.kv.DurabilityLevel; import com.couchbase.client.java.kv.PersistTo; import com.couchbase.client.java.kv.ReplaceOptions; @@ -124,7 +116,7 @@ public Mono one(T object) { if (getResult.cas() != support.cas) { return Mono.error(TransactionalSupport.retryTransactionOnCasMismatch(support.ctx, getResult.cas(), support.cas)); } - return support.ctx.replace(getResult, template.getCouchbaseClientFactory().getCluster().block().environment().transcoder() + return support.ctx.replace(getResult, template.getCouchbaseClientFactory().getCluster().environment().transcoder() .encode(support.converted.export()).encoded()); }).flatMap(result -> this.support.applyResult(object, converted, converted.getId(), 0L, null, null)); }); diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java index 32949e2c9..d216b8afa 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java @@ -28,7 +28,6 @@ import org.springframework.data.couchbase.core.support.PseudoArgs; import org.springframework.util.Assert; -import com.couchbase.client.core.error.CouchbaseException; import com.couchbase.client.core.msg.kv.DurabilityLevel; import com.couchbase.client.java.kv.PersistTo; import com.couchbase.client.java.kv.ReplicateTo; @@ -87,7 +86,7 @@ public Mono one(T object) { .then(support.encodeEntity(object)) .flatMap(converted -> tmpl.flatMap(tp -> { return tp.getCouchbaseClientFactory().withScope(pArgs.getScope()) - .getCollection(pArgs.getCollection()).flatMap(collection -> collection.reactive() + .getCollectionMono(pArgs.getCollection()).flatMap(collection -> collection.reactive() .upsert(converted.getId(), converted.export(), buildUpsertOptions(pArgs.getOptions(), converted)) .flatMap(result -> support.applyResult(object, converted, converted.getId(), result.cas(), null))); })); diff --git a/src/main/java/org/springframework/data/couchbase/core/TransactionalSupport.java b/src/main/java/org/springframework/data/couchbase/core/TransactionalSupport.java index 37d5c22dd..d93b69a9e 100644 --- a/src/main/java/org/springframework/data/couchbase/core/TransactionalSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/TransactionalSupport.java @@ -41,8 +41,8 @@ public static Mono one(Mono tmpl, CouchbaseTra String scopeName, String collectionName, ReactiveTemplateSupport support, T object, Function> nonTransactional, Function> transactional) { return tmpl.flatMap(template -> template.getCouchbaseClientFactory().withScope(scopeName) - .getCollection(collectionName).flatMap(collection -> support.encodeEntity(object) - .flatMap(converted -> tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getTransactionResources(null).flatMap(s -> { + .getCollectionMono(collectionName).flatMap(collection -> support.encodeEntity(object) + .flatMap(converted -> tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getResourceHolderMono().flatMap(s -> { TransactionalSupportHelper gsh = new TransactionalSupportHelper(converted, support.getCas(object), collection.reactive(), s.getCore() != null ? s.getCore() : (transactionalOperator != null ? transactionalOperator.getAttemptContext() : null)); @@ -63,7 +63,7 @@ public static Mono one(Mono tmpl, CouchbaseTra } public static Mono verifyNotInTransaction(Mono tmpl, String methodName) { - return tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getTransactionResources(null) + return tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getResourceHolderMono() .flatMap(s -> { if (s.hasActiveTransaction()) { return Mono.error(new IllegalArgumentException(methodName + "can not be used inside a transaction")); diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseSimpleCallbackTransactionManager.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseSimpleCallbackTransactionManager.java index 7d5eed74e..c0d7db2b7 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseSimpleCallbackTransactionManager.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseSimpleCallbackTransactionManager.java @@ -64,7 +64,7 @@ public T execute(TransactionDefinition definition, TransactionCallback ca private T executeNewTransaction(TransactionCallback callback) { final AtomicReference execResult = new AtomicReference<>(); - TransactionResult result = couchbaseClientFactory.getCluster().block().transactions().run(ctx -> { + TransactionResult result = couchbaseClientFactory.getCluster().transactions().run(ctx -> { CouchbaseTransactionStatus status = new CouchbaseTransactionStatus(null, true, false, false, true, null, null); populateTransactionSynchronizationManager(ctx); @@ -160,8 +160,8 @@ private void populateTransactionSynchronizationManager(TransactionAttemptContext TransactionSynchronizationManager.setActualTransactionActive(true); TransactionSynchronizationManager.initSynchronization(); ReactiveCouchbaseResourceHolder resourceHolder = new ReactiveCouchbaseResourceHolder(AttemptContextReactiveAccessor.getCore(ctx)); - TransactionSynchronizationManager.unbindResourceIfPossible(couchbaseClientFactory.getCluster().block()); - TransactionSynchronizationManager.bindResource(couchbaseClientFactory.getCluster().block(), resourceHolder); + TransactionSynchronizationManager.unbindResourceIfPossible(couchbaseClientFactory.getCluster()); + TransactionSynchronizationManager.bindResource(couchbaseClientFactory.getCluster(), resourceHolder); } /** diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionalOperator.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionalOperator.java index 5f4ea4e98..c64cc47a8 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionalOperator.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionalOperator.java @@ -4,11 +4,9 @@ import com.couchbase.client.core.transaction.CoreTransactionGetResult; import com.couchbase.client.java.transactions.AttemptContextReactiveAccessor; import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; -import com.couchbase.client.java.transactions.TransactionGetResult; import com.couchbase.client.java.transactions.TransactionResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.data.couchbase.core.CouchbaseTemplate; import org.springframework.transaction.ReactiveTransaction; import org.springframework.transaction.TransactionException; import org.springframework.transaction.TransactionSystemException; @@ -22,8 +20,6 @@ import java.util.Map; import java.util.function.Function; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations; import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate; import org.springframework.data.couchbase.repository.DynamicProxyable; @@ -95,7 +91,7 @@ public Mono reactive(Function { + return ((ReactiveCouchbaseTransactionManager) transactionManager).getDatabaseFactory().getCluster().reactive().transactions().run(ctx -> { setAttemptContextReactive(ctx); // for getTxOp().getCtx() in Reactive*OperationSupport // for transactional(), transactionDefinition.setAtr(ctx) is called at the beginning of that method // and is eventually added to the ClientSession in transactionManager.doBegin() via newResourceHolder() diff --git a/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseClientUtils.java b/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseClientUtils.java index 31865f1b0..7f7de3736 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseClientUtils.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseClientUtils.java @@ -2,7 +2,6 @@ import com.couchbase.client.core.transaction.CoreTransactionAttemptContext; import com.couchbase.client.java.ClusterInterface; -import com.couchbase.client.java.transactions.config.TransactionOptions; import org.springframework.data.couchbase.ReactiveCouchbaseClientFactory; import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate; import org.springframework.data.couchbase.core.convert.CouchbaseConverter; @@ -13,7 +12,6 @@ import org.springframework.transaction.reactive.TransactionSynchronizationManager; import org.springframework.transaction.support.ResourceHolderSynchronization; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; import reactor.core.publisher.Mono; import reactor.util.context.Context; @@ -156,18 +154,18 @@ private static Mono doGetCouchbaseTemplate(@Nullable private static ReactiveCouchbaseResourceHolder getNonReactiveSession(ReactiveCouchbaseClientFactory factory) { ReactiveCouchbaseResourceHolder h = ((ReactiveCouchbaseResourceHolder) org.springframework.transaction.support.TransactionSynchronizationManager - .getResource(factory.getCluster().block())); + .getResource(factory.getCluster())); if( h == null){ // no longer used h = ((ReactiveCouchbaseResourceHolder) org.springframework.transaction.support.TransactionSynchronizationManager .getResource(factory));// MN's CouchbaseTransactionManager } - //System.err.println("getNonreactiveSession: "+ h); return h; } + // TODO mr - unnecessary? private static Mono getCouchbaseClusterOrDefault(@Nullable String dbName, ReactiveCouchbaseClientFactory factory) { - return StringUtils.hasText(dbName) ? factory.getCluster() : factory.getCluster(); + return Mono.just(factory.getCluster()); } private static Mono getCouchbaseTemplateOrDefault(@Nullable String dbName, @@ -179,7 +177,7 @@ private static Mono doGetSession(TransactionSyn ReactiveCouchbaseClientFactory dbFactory, SessionSynchronization sessionSynchronization) { final ReactiveCouchbaseResourceHolder registeredHolder = (ReactiveCouchbaseResourceHolder) synchronizationManager - .getResource(dbFactory.getCluster().block()); // make sure this wasn't saved under the wrong key!!! + .getResource(dbFactory.getCluster()); // make sure this wasn't saved under the wrong key!!! // check for native MongoDB transaction if (registeredHolder != null diff --git a/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseTransactionManager.java b/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseTransactionManager.java index 80665b95f..0740b897b 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseTransactionManager.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseTransactionManager.java @@ -117,7 +117,7 @@ protected Object doGetTransaction(TransactionSynchronizationManager synchronizat // creation of a new ReactiveCouchbaseTransactionObject (i.e. transaction). // with an attempt to get the resourceHolder from the synchronizationManager ReactiveCouchbaseResourceHolder resourceHolder = (ReactiveCouchbaseResourceHolder) synchronizationManager - .getResource(getRequiredDatabaseFactory().getCluster().block()); + .getResource(getRequiredDatabaseFactory().getCluster()); // TODO ACR from couchbase // resourceHolder.getSession().setAttemptContextReactive(null); return new ReactiveCouchbaseTransactionObject(resourceHolder); @@ -166,10 +166,7 @@ protected Mono doBegin(TransactionSynchronizationManager synchronizationMa debugString(couchbaseTransactionObject.getCore())), ex)) .doOnSuccess(resourceHolder -> { - System.err.println("ReactiveCouchbaseTransactionManager: " + this); - System.err.println( - "bindResource: " + getRequiredDatabaseFactory().getCluster().block() + " value: " + resourceHolder); - synchronizationManager.bindResource(getRequiredDatabaseFactory().getCluster().block(), resourceHolder); + synchronizationManager.bindResource(getRequiredDatabaseFactory().getCluster(), resourceHolder); }).then(); }); } @@ -294,7 +291,7 @@ protected Mono doCleanupAfterCompletion(TransactionSynchronizationManager ReactiveCouchbaseTransactionObject couchbaseTransactionObject = (ReactiveCouchbaseTransactionObject) transaction; // Remove the connection holder from the thread. - synchronizationManager.unbindResource(getRequiredDatabaseFactory().getCluster().block()); + synchronizationManager.unbindResource(getRequiredDatabaseFactory().getCluster()); couchbaseTransactionObject.getRequiredResourceHolder().clear(); if (logger.isDebugEnabled()) { @@ -341,7 +338,7 @@ private Mono newResourceHolder(TransactionDefin ReactiveCouchbaseClientFactory dbFactory = getRequiredDatabaseFactory(); // TODO MSR : config should be derived from config that was used for `transactions` - Mono sess = Mono.just(dbFactory.getTransactionResources(options, null)); + Mono sess = Mono.just(dbFactory.getResourceHolder(options, null)); return sess; } diff --git a/src/main/java/org/springframework/data/couchbase/transaction/ReactiveTransactionsWrapper.java b/src/main/java/org/springframework/data/couchbase/transaction/ReactiveTransactionsWrapper.java index b4aa21ce7..79b89946c 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/ReactiveTransactionsWrapper.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/ReactiveTransactionsWrapper.java @@ -37,11 +37,11 @@ public Mono run(Function> newTransactionLogic = (ctx) -> { - ReactiveCouchbaseResourceHolder resourceHolder = reactiveCouchbaseClientFactory.getTransactionResources( + ReactiveCouchbaseResourceHolder resourceHolder = reactiveCouchbaseClientFactory.getResourceHolder( TransactionOptions.transactionOptions(), AttemptContextReactiveAccessor.getCore(ctx)); Mono sync = TransactionContextManager.currentContext() .map(TransactionSynchronizationManager::new).flatMap(synchronizationManager -> { - synchronizationManager.bindResource(reactiveCouchbaseClientFactory.getBlockingCluster(), resourceHolder); + synchronizationManager.bindResource(reactiveCouchbaseClientFactory.getCluster(), resourceHolder); prepareSynchronization(synchronizationManager, null, new CouchbaseTransactionDefinition()); return transactionLogic.apply(ctx) // <---- execute the transaction .thenReturn(ctx).then(Mono.just(synchronizationManager)); @@ -50,7 +50,7 @@ public Mono run(Function Date: Fri, 27 May 2022 15:18:29 -0700 Subject: [PATCH 06/15] Cleanup of test classes. --- .../ExecutableFindByIdOperationSupport.java | 4 +- .../ReactiveFindByIdOperationSupport.java | 44 ++-- ...basePersonTransactionIntegrationTests.java | 121 ++++----- ...onTransactionReactiveIntegrationTests.java | 43 +--- ...uchbaseReactiveTransactionNativeTests.java | 232 ++++-------------- ...seTemplateTransactionIntegrationTests.java | 12 +- ...ansactionalRepositoryIntegrationTests.java | 89 ++++--- ...TransactionalTemplateIntegrationTests.java | 20 +- .../util/TransactionTestUtil.java | 13 + 9 files changed, 231 insertions(+), 347 deletions(-) diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperationSupport.java index e17596292..188418657 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperationSupport.java @@ -67,9 +67,7 @@ static class ExecutableFindByIdSupport implements ExecutableFindById { @Override public T one(final String id) { - //Mono.deferContextual(ctx -> { System.err.println("ExecutableFindById.ctx: "+ctx); return Mono.empty();}).block(); - return reactiveSupport.one(id)/*.contextWrite(TransactionContextManager.getOrCreateContext()) - .contextWrite(TransactionContextManager.getOrCreateContextHolder())*/.block(); + return reactiveSupport.one(id).block(); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java index e456e0147..b20f2957b 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java @@ -69,8 +69,8 @@ static class ReactiveFindByIdSupport implements ReactiveFindById { private final Duration expiry; ReactiveFindByIdSupport(ReactiveCouchbaseTemplate template, Class domainType, String scope, String collection, - CommonOptions options, List fields, Duration expiry, CouchbaseTransactionalOperator txCtx, - ReactiveTemplateSupport support) { + CommonOptions options, List fields, Duration expiry, CouchbaseTransactionalOperator txCtx, + ReactiveTemplateSupport support) { this.template = template; this.domainType = domainType; this.scope = scope; @@ -95,28 +95,24 @@ public Mono one(final String id) { // this will get me a template with a session holding tx Mono tmpl = template.doGetTemplate(); - Mono reactiveEntity = tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getResourceHolderMono() - .flatMap(s -> { - System.err.println("Session: "+s); - //Mono reactiveEntity = Mono.defer(() -> { - if (s == null || s.getCore() == null) { - if (pArgs.getOptions() instanceof GetAndTouchOptions) { - return rc.getAndTouch(id, expiryToUse(), (GetAndTouchOptions) pArgs.getOptions()) - .flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), domainType, - pArgs.getScope(), pArgs.getCollection(), null)); - } else { - return rc.get(id, (GetOptions) pArgs.getOptions()) - .flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), domainType, - pArgs.getScope(), pArgs.getCollection(), null)); - } - } else { - return s.getCore().get(makeCollectionIdentifier(rc.async()), id) - .flatMap( result -> { - return support.decodeEntity(id, new String(result.contentAsBytes(), StandardCharsets.UTF_8), result.cas(), domainType, pArgs.getScope(), - pArgs.getCollection(), new TransactionResultHolder(result), null); - }); - } - })); + Mono reactiveEntity = tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getResourceHolderMono().flatMap(s -> { + if (s == null || s.getCore() == null) { + if (pArgs.getOptions() instanceof GetAndTouchOptions) { + return rc.getAndTouch(id, expiryToUse(), (GetAndTouchOptions) pArgs.getOptions()) + .flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), domainType, + pArgs.getScope(), pArgs.getCollection(), null)); + } else { + return rc.get(id, (GetOptions) pArgs.getOptions()) + .flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), domainType, + pArgs.getScope(), pArgs.getCollection(), null)); + } + } else { + return s.getCore().get(makeCollectionIdentifier(rc.async()), id) + .flatMap(result -> support.decodeEntity(id, new String(result.contentAsBytes(), StandardCharsets.UTF_8), + result.cas(), domainType, pArgs.getScope(), pArgs.getCollection(), + new TransactionResultHolder(result), null)); + } + })); return reactiveEntity.onErrorResume(throwable -> { if (throwable instanceof DocumentNotFoundException) { diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionIntegrationTests.java index f8814b908..2748ac525 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionIntegrationTests.java @@ -22,12 +22,20 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.data.couchbase.transactions.util.TransactionTestUtil.assertInReactiveTransaction; +import static org.springframework.data.couchbase.transactions.util.TransactionTestUtil.assertInTransaction; import static org.springframework.data.couchbase.util.Util.assertInAnnotationTransaction; import com.couchbase.client.core.transaction.CoreTransactionAttemptContext; +import com.couchbase.client.java.Cluster; import com.couchbase.client.java.transactions.AttemptContextReactiveAccessor; import com.couchbase.client.java.transactions.config.TransactionOptions; import lombok.Data; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; +import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; +import org.springframework.data.couchbase.repository.config.EnableReactiveCouchbaseRepositories; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.retry.Retry; @@ -102,9 +110,9 @@ * @author Michael Reiche */ @IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED) -@SpringJUnitConfig(classes = { Config.class, CouchbasePersonTransactionIntegrationTests.PersonService.class }) +@SpringJUnitConfig(classes = { CouchbasePersonTransactionIntegrationTests.Config.class, CouchbasePersonTransactionIntegrationTests.PersonService.class }) public class CouchbasePersonTransactionIntegrationTests extends JavaIntegrationTests { - + // intellij flags "Could not autowire" when config classes are specified with classes={...}. But they are populated. @Autowired CouchbaseClientFactory couchbaseClientFactory; @Autowired ReactiveCouchbaseClientFactory reactiveCouchbaseClientFactory; @Autowired ReactiveCouchbaseTransactionManager reactiveCouchbaseTransactionManager; @@ -119,6 +127,7 @@ public class CouchbasePersonTransactionIntegrationTests extends JavaIntegrationT String sName = "_default"; String cName = "_default"; private TransactionalOperator transactionalOperator; + private ReactiveTransactionsWrapper reactiveTransactionsWrapper; @BeforeAll public static void beforeAll() { @@ -149,6 +158,8 @@ public void beforeEachTest() { Person walterWhite = new Person(1, "Walter", "White"); remove(cbTmpl, sName, cName, walterWhite.getId().toString()); transactionalOperator = TransactionalOperator.create(reactiveCouchbaseTransactionManager); + reactiveTransactionsWrapper = new ReactiveTransactionsWrapper( + reactiveCouchbaseClientFactory); } @Test @@ -198,7 +209,7 @@ public void replaceInTxAnnotatedCallback() { cbTmpl.insertById(Person.class).one(person); AtomicInteger tryCount = new AtomicInteger(0); Person p = personService.declarativeFindReplacePersonCallback(switchedPerson, tryCount); - Person pFound = rxCBTmpl.findById(Person.class).one(person.getId().toString()).block(); + Person pFound = cbTmpl.findById(Person.class).one(person.getId().toString()); assertEquals(switchedPerson.getFirstname(), pFound.getFirstname(), "should have been switched"); } @@ -256,19 +267,17 @@ public void errorAfterTxShouldNotAffectPreviousStep() { assertEquals(1, count, "should have saved and found 1"); } - /** - * This will appear to work even if replaceById does not use a transaction. - */ @Test @Disabled public void replacePersonCBTransactionsRxTmpl() { Person person = new Person(1, "Walter", "White"); cbTmpl.insertById(Person.class).one(person); Mono result = rxCBTmpl.findById(Person.class).one(person.getId().toString()) // - .flatMap((pp) -> rxCBTmpl.replaceById(Person.class).one(pp)) // + .flatMap(pp -> rxCBTmpl.replaceById(Person.class).one(pp)) + .flatMap(ppp -> assertInReactiveTransaction(ppp)) .as(transactionalOperator::transactional); result.block(); - Person pFound = rxCBTmpl.findById(Person.class).one(person.getId().toString()).block(); + Person pFound = cbTmpl.findById(Person.class).one(person.getId().toString()); assertEquals(person, pFound, "should have found expected " + person); } @@ -276,9 +285,10 @@ public void replacePersonCBTransactionsRxTmpl() { public void insertPersonCBTransactionsRxTmplRollback() { Person person = new Person(1, "Walter", "White"); Mono result = rxCBTmpl.insertById(Person.class).one(person) // + .flatMap(ppp -> assertInReactiveTransaction(ppp)) .flatMap(p -> throwSimulatedFailure(p)).as(transactionalOperator::transactional); // tx assertThrows(SimulateFailureException.class, result::block); - Person pFound = rxCBTmpl.findById(Person.class).one(person.getId().toString()).block(); + Person pFound = cbTmpl.findById(Person.class).one(person.getId().toString()); assertNull(pFound, "insert should have been rolled back"); } @@ -286,10 +296,10 @@ public void insertPersonCBTransactionsRxTmplRollback() { public void insertTwicePersonCBTransactionsRxTmplRollback() { Person person = new Person(1, "Walter", "White"); Mono result = rxCBTmpl.insertById(Person.class).one(person) // - .flatMap((ppp) -> rxCBTmpl.insertById(Person.class).one(ppp)) // + .flatMap(ppp -> rxCBTmpl.insertById(Person.class).one(ppp)) // .as(transactionalOperator::transactional); assertThrows(DuplicateKeyException.class, result::block); - Person pFound = rxCBTmpl.findById(Person.class).one(person.getId().toString()).block(); + Person pFound = cbTmpl.findById(Person.class).one(person.getId().toString()); assertNull(pFound, "insert should have been rolled back"); } @@ -304,7 +314,6 @@ public void wrapperReplaceWithCasConflictResolvedViaRetry() { Person switchedPerson = new Person(1, "Dave", "Reynolds"); AtomicInteger tryCount = new AtomicInteger(0); cbTmpl.insertById(Person.class).one(person); - for (int i = 0; i < 50; i++) { // the transaction sometimes succeeds on the first try ReplaceLoopThread t = new ReplaceLoopThread(switchedPerson); t.start(); @@ -337,21 +346,18 @@ public void wrapperReplaceWithCasConflictResolvedViaRetryReactive() { ReplaceLoopThread t = new ReplaceLoopThread(switchedPerson); t.start(); tryCount.set(0); - ReactiveTransactionsWrapper reactiveTransactionsWrapper = new ReactiveTransactionsWrapper( - reactiveCouchbaseClientFactory); Mono result = reactiveTransactionsWrapper.run(ctx -> { System.err.println("try: " + tryCount.incrementAndGet()); return rxCBTmpl.findById(Person.class).one(person.getId().toString()) // - .flatMap((ppp) -> rxCBTmpl.replaceById(Person.class).one(ppp)).then(); + .flatMap(ppp -> rxCBTmpl.replaceById(Person.class).one(ppp)).then(); }); TransactionResult txResult = result.block(); - System.out.println("txResult: " + txResult); t.setStopFlag(); if (tryCount.get() > 1) { break; } } - Person pFound = rxCBTmpl.findById(Person.class).one(person.getId().toString()).block(); + Person pFound = cbTmpl.findById(Person.class).one(person.getId().toString()); assertTrue(tryCount.get() > 1, "should have been more than one try. tries: " + tryCount.get()); assertEquals(switchedPerson.getFirstname(), pFound.getFirstname(), "should have been switched"); } @@ -366,7 +372,6 @@ public void replaceWithCasConflictResolvedViaRetryAnnotatedCallback() { Person switchedPerson = new Person(1, "Dave", "Reynolds"); AtomicInteger tryCount = new AtomicInteger(0); cbTmpl.insertById(Person.class).one(person); - for (int i = 0; i < 50; i++) { // the transaction sometimes succeeds on the first try ReplaceLoopThread t = new ReplaceLoopThread(switchedPerson); t.start(); @@ -377,7 +382,7 @@ public void replaceWithCasConflictResolvedViaRetryAnnotatedCallback() { break; } } - Person pFound = rxCBTmpl.findById(Person.class).one(person.getId().toString()).block(); + Person pFound = cbTmpl.findById(Person.class).one(person.getId().toString()); assertEquals(switchedPerson.getFirstname(), pFound.getFirstname(), "should have been switched"); assertTrue(tryCount.get() > 1, "should have been more than one try. tries: " + tryCount.get()); } @@ -409,7 +414,7 @@ public void replaceWithCasConflictResolvedViaRetryAnnotatedReactive() { break; } } - Person pFound = rxCBTmpl.findById(Person.class).one(person.getId().toString()).block(); + Person pFound = cbTmpl.findById(Person.class).one(person.getId().toString()); assertEquals(switchedPerson.getFirstname(), pFound.getFirstname(), "should have been switched"); assertTrue(tryCount.get() > 1, "should have been more than one try. tries: " + tryCount.get()); } @@ -435,7 +440,7 @@ public void replaceWithCasConflictResolvedViaRetryAnnotated() { break; } } - Person pFound = rxCBTmpl.findById(Person.class).one(person.getId().toString()).block(); + Person pFound = cbTmpl.findById(Person.class).one(person.getId().toString()); System.out.println("pFound: " + pFound); assertEquals(switchedPerson.getFirstname(), pFound.getFirstname(), "should have been switched"); assertTrue(tryCount.get() > 1, "should have been more than one try. tries: " + tryCount.get()); @@ -445,18 +450,13 @@ public void replaceWithCasConflictResolvedViaRetryAnnotated() { public void replacePersonCBTransactionsRxTmplRollback() { Person person = new Person(1, "Walter", "White"); String newName = "Walt"; - rxCBTmpl.insertById(Person.class).one(person).block(); - - // doesn't TransactionWrapper do the same thing? - ReactiveTransactionsWrapper reactiveTransactionsWrapper = new ReactiveTransactionsWrapper( - reactiveCouchbaseClientFactory); + cbTmpl.insertById(Person.class).one(person); Mono result = reactiveTransactionsWrapper.run(ctx -> { // return rxCBTmpl.findById(Person.class).one(person.getId().toString()) // .flatMap(pp -> rxCBTmpl.replaceById(Person.class).one(pp.withFirstName(newName))).then(Mono.empty()); }); - result.block(); - Person pFound = rxCBTmpl.findById(Person.class).one(person.getId().toString()).block(); + Person pFound = cbTmpl.findById(Person.class).one(person.getId().toString()); System.err.println(pFound); assertEquals(newName, pFound.getFirstname()); } @@ -465,15 +465,12 @@ public void replacePersonCBTransactionsRxTmplRollback() { public void deletePersonCBTransactionsRxTmpl() { Person person = new Person(1, "Walter", "White"); remove(cbTmpl, sName, cName, person.getId().toString()); - rxCBTmpl.insertById(Person.class).inCollection(cName).one(person).block(); - - ReactiveTransactionsWrapper reactiveTransactionsWrapper = new ReactiveTransactionsWrapper( - reactiveCouchbaseClientFactory); + cbTmpl.insertById(Person.class).inCollection(cName).one(person); Mono result = reactiveTransactionsWrapper.run(ctx -> { // get the ctx return rxCBTmpl.removeById(Person.class).inCollection(cName).oneEntity(person).then(); }); result.block(); - Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); + Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); assertNull(pFound, "Should not have found " + pFound); } @@ -482,9 +479,6 @@ public void deletePersonCBTransactionsRxTmplFail() { Person person = new Person(1, "Walter", "White"); remove(cbTmpl, sName, cName, person.getId().toString()); cbTmpl.insertById(Person.class).inCollection(cName).one(person); - - ReactiveTransactionsWrapper reactiveTransactionsWrapper = new ReactiveTransactionsWrapper( - reactiveCouchbaseClientFactory); Mono result = reactiveTransactionsWrapper.run(ctx -> { // get the ctx return rxCBTmpl.removeById(Person.class).inCollection(cName).oneEntity(person) .then(rxCBTmpl.removeById(Person.class).inCollection(cName).oneEntity(person)).then(); @@ -498,10 +492,7 @@ public void deletePersonCBTransactionsRxTmplFail() { public void deletePersonCBTransactionsRxRepo() { Person person = new Person(1, "Walter", "White"); remove(cbTmpl, sName, cName, person.getId().toString()); - rxRepo.withCollection(cName).save(person).block(); - - ReactiveTransactionsWrapper reactiveTransactionsWrapper = new ReactiveTransactionsWrapper( - reactiveCouchbaseClientFactory); + repo.withCollection(cName).save(person); Mono result = reactiveTransactionsWrapper.run(ctx -> { // get the ctx return rxRepo.withCollection(cName).delete(person).then(); }); @@ -514,10 +505,7 @@ public void deletePersonCBTransactionsRxRepo() { public void deletePersonCBTransactionsRxRepoFail() { Person person = new Person(1, "Walter", "White"); remove(cbTmpl, sName, cName, person.getId().toString()); - rxRepo.withCollection(cName).save(person).block(); - - ReactiveTransactionsWrapper reactiveTransactionsWrapper = new ReactiveTransactionsWrapper( - reactiveCouchbaseClientFactory); + repo.withCollection(cName).save(person); Mono result = reactiveTransactionsWrapper.run(ctx -> { // get the ctx return rxRepo.withCollection(cName).findById(person.getId().toString()) .flatMap(pp -> rxRepo.withCollection(cName).delete(pp).then(rxRepo.withCollection(cName).delete(pp))).then(); @@ -534,8 +522,6 @@ public void findPersonCBTransactions() { cbTmpl.insertById(Person.class).inScope(sName).inCollection(cName).one(person); List docs = new LinkedList<>(); Query q = Query.query(QueryCriteria.where("meta().id").eq(person.getId())); - ReactiveTransactionsWrapper reactiveTransactionsWrapper = new ReactiveTransactionsWrapper( - reactiveCouchbaseClientFactory); Mono result = reactiveTransactionsWrapper.run(ctx -> rxCBTmpl.findByQuery(Person.class) .inScope(sName).inCollection(cName).matching(q).withConsistency(REQUEST_PLUS).one().doOnSuccess(doc -> { System.err.println("doc: " + doc); @@ -552,13 +538,9 @@ public void findPersonCBTransactions() { public void insertPersonRbCBTransactions() { Person person = new Person(1, "Walter", "White"); remove(cbTmpl, sName, cName, person.getId().toString()); - - ReactiveTransactionsWrapper reactiveTransactionsWrapper = new ReactiveTransactionsWrapper( - reactiveCouchbaseClientFactory); Mono result = reactiveTransactionsWrapper .run(ctx -> rxCBTmpl.insertById(Person.class).inScope(sName).inCollection(cName).one(person) . flatMap(it -> Mono.error(new SimulateFailureException()))); - assertThrowsWithCause(() -> result.block(), TransactionFailedException.class, SimulateFailureException.class); Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()); assertNull(pFound, "Should not have found " + pFound); @@ -569,15 +551,12 @@ public void replacePersonRbCBTransactions() { Person person = new Person(1, "Walter", "White"); remove(cbTmpl, sName, cName, person.getId().toString()); cbTmpl.insertById(Person.class).inScope(sName).inCollection(cName).one(person); - ReactiveTransactionsWrapper reactiveTransactionsWrapper = new ReactiveTransactionsWrapper( - reactiveCouchbaseClientFactory); Mono result = reactiveTransactionsWrapper.run(ctx -> // rxCBTmpl.findById(Person.class).inScope(sName).inCollection(cName).one(person.getId().toString()) // .flatMap(pFound -> rxCBTmpl.replaceById(Person.class).inScope(sName).inCollection(cName) .one(pFound.withFirstName("Walt"))) . flatMap(it -> Mono.error(new SimulateFailureException()))); assertThrowsWithCause(() -> result.block(), TransactionFailedException.class, SimulateFailureException.class); - Person pFound = cbTmpl.findById(Person.class).inScope(sName).inCollection(cName).one(person.getId().toString()); assertEquals(person, pFound, "Should have found " + person + " instead of " + pFound); } @@ -589,8 +568,6 @@ public void findPersonSpringTransactions() { cbTmpl.insertById(Person.class).inScope(sName).inCollection(cName).one(person); List docs = new LinkedList<>(); Query q = Query.query(QueryCriteria.where("meta().id").eq(person.getId())); - ReactiveTransactionsWrapper reactiveTransactionsWrapper = new ReactiveTransactionsWrapper( - reactiveCouchbaseClientFactory); Mono result = reactiveTransactionsWrapper.run(ctx -> rxCBTmpl.findByQuery(Person.class) .inScope(sName).inCollection(cName).matching(q).one().doOnSuccess(r -> docs.add(r))); result.block(); @@ -652,7 +629,6 @@ void remove(CouchbaseTemplate template, String scope, String collection, String void remove(ReactiveCouchbaseTemplate template, String scope, String collection, String id) { try { template.removeById(Person.class).inScope(scope).inCollection(collection).one(id).block(); - System.out.println("removed " + id); List ps = template.findByQuery(Person.class).inScope(scope).inCollection(collection) .withConsistency(REQUEST_PLUS).all().collectList().block(); } catch (DocumentNotFoundException | DataRetrievalFailureException nfe) { @@ -880,4 +856,37 @@ public Mono declarativeSavePersonErrorsReactive(Person person) { } + + @Configuration + @EnableCouchbaseRepositories("org.springframework.data.couchbase") + @EnableReactiveCouchbaseRepositories("org.springframework.data.couchbase") + static class Config extends AbstractCouchbaseConfiguration { + + @Override + public String getConnectionString() { + return connectionString(); + } + + @Override + public String getUserName() { + return config().adminUsername(); + } + + @Override + public String getPassword() { + return config().adminPassword(); + } + + @Override + public String getBucketName() { + return bucketName(); + } + + @Bean + public Cluster couchbaseCluster() { + return Cluster.connect("10.144.220.101", "Administrator", "password"); + } + + } + } diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionReactiveIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionReactiveIntegrationTests.java index 1b1076599..d75637faf 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionReactiveIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionReactiveIntegrationTests.java @@ -81,29 +81,22 @@ * @author Michael Reiche */ @IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED) -@SpringJUnitConfig(CouchbasePersonTransactionReactiveIntegrationTests.Config.class) -//@Transactional(transactionManager = BeanNames.COUCHBASE_TRANSACTION_MANAGER) +@SpringJUnitConfig(classes = { CouchbasePersonTransactionReactiveIntegrationTests.Config.class, CouchbasePersonTransactionReactiveIntegrationTests.PersonService.class } ) public class CouchbasePersonTransactionReactiveIntegrationTests extends JavaIntegrationTests { - + // intellij flags "Could not autowire" when config classes are specified with classes={...}. But they are populated. @Autowired CouchbaseClientFactory couchbaseClientFactory; @Autowired ReactiveCouchbaseTransactionManager reactiveCouchbaseTransactionManager; @Autowired CouchbaseSimpleCallbackTransactionManager couchbaseTransactionManager; @Autowired ReactivePersonRepository rxRepo; @Autowired PersonRepository repo; @Autowired ReactiveCouchbaseTemplate rxCBTmpl; - @Autowired Cluster myCluster; - - /* DO NOT @Autowired */ PersonService personService; - - static GenericApplicationContext context; + @Autowired PersonService personService; @Autowired ReactiveCouchbaseTemplate operations; @BeforeAll public static void beforeAll() { callSuperBeforeAll(new Object() {}); - context = new AnnotationConfigApplicationContext(CouchbasePersonTransactionReactiveIntegrationTests.Config.class, - CouchbasePersonTransactionReactiveIntegrationTests.PersonService.class); } @AfterAll @@ -113,28 +106,24 @@ public static void afterAll() { @BeforeEach public void beforeEachTest() { - personService = context.getBean(CouchbasePersonTransactionReactiveIntegrationTests.PersonService.class); // getting it via autowired results in no @Transactional operations.removeByQuery(Person.class).withConsistency(REQUEST_PLUS).all().collectList().block(); operations.removeByQuery(EventLog.class).withConsistency(REQUEST_PLUS).all().collectList().block(); operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).all().collectList().block(); operations.findByQuery(EventLog.class).withConsistency(REQUEST_PLUS).all().collectList().block(); } - - @Test // DATAMONGO-2265 + @Test public void shouldRollbackAfterException() { personService.savePersonErrors(new Person(null, "Walter", "White")) // .as(StepVerifier::create) // .verifyError(RuntimeException.class); - // operations.findByQuery(Person.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).count().block(); - // sleepMs(5000); operations.count(new Query(), Person.class) // .as(StepVerifier::create) // .expectNext(0L) // .verifyComplete(); } - @Test // DATAMONGO-2265 + @Test // @Rollback(false) public void shouldRollbackAfterExceptionOfTxAnnotatedMethod() { Person p = new Person(null, "Walter", "White"); @@ -153,7 +142,7 @@ public void shouldRollbackAfterExceptionOfTxAnnotatedMethod() { } - @Test // DATAMONGO-2265 + @Test public void commitShouldPersistTxEntries() { personService.savePerson(new Person(null, "Walter", "White")) // @@ -169,7 +158,7 @@ public void commitShouldPersistTxEntries() { .verifyComplete(); } - @Test // DATAMONGO-2265 + @Test public void commitShouldPersistTxEntriesOfTxAnnotatedMethod() { personService.declarativeSavePerson(new Person(null, "Walter", "White")).as(StepVerifier::create) // @@ -183,7 +172,7 @@ public void commitShouldPersistTxEntriesOfTxAnnotatedMethod() { } - @Test // DATAMONGO-2265 + @Test public void commitShouldPersistTxEntriesAcrossCollections() { personService.saveWithLogs(new Person(null, "Walter", "White")) // @@ -202,7 +191,7 @@ public void commitShouldPersistTxEntriesAcrossCollections() { .verifyComplete(); } - @Test // DATAMONGO-2265 + @Test public void rollbackShouldAbortAcrossCollections() { personService.saveWithErrorLogs(new Person(null, "Walter", "White")) // @@ -221,16 +210,15 @@ public void rollbackShouldAbortAcrossCollections() { .verifyComplete(); } - @Test // DATAMONGO-2265 + @Test public void countShouldWorkInsideTransaction() { - personService.countDuringTx(new Person(null, "Walter", "White")) // .as(StepVerifier::create) // .expectNext(1L) // .verifyComplete(); } - @Test // DATAMONGO-2265 + @Test public void emitMultipleElementsDuringTransaction() { try { @@ -244,21 +232,14 @@ public void emitMultipleElementsDuringTransaction() { } } - @Test // DATAMONGO-2265 + @Test public void errorAfterTxShouldNotAffectPreviousStep() { Person p = new Person(1, "Walter", "White"); - //remove(couchbaseTemplate, "_default", p.getId().toString()); personService.savePerson(p) // - //.delayElement(Duration.ofMillis(100)) // .then(Mono.error(new RuntimeException("my big bad evil error"))).as(StepVerifier::create) // .expectError() .verify(); - //.expectError() // - //.as(StepVerifier::create) - //.expectNext(p) - //.verifyComplete(); - operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count() // .as(StepVerifier::create) // .expectNext(1L) // diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseReactiveTransactionNativeTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseReactiveTransactionNativeTests.java index 1dd8e795d..9b872815c 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseReactiveTransactionNativeTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseReactiveTransactionNativeTests.java @@ -63,7 +63,6 @@ */ @IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED) @SpringJUnitConfig(CouchbaseReactiveTransactionNativeTests.Config.class) -//@Disabled // Now using TransactionSyncronizationManager for the session public class CouchbaseReactiveTransactionNativeTests extends JavaIntegrationTests { @Autowired CouchbaseClientFactory couchbaseClientFactory; @@ -74,7 +73,6 @@ public class CouchbaseReactiveTransactionNativeTests extends JavaIntegrationTest static String cName; // short name - static GenericApplicationContext context; ReactiveCouchbaseTemplate operations; @BeforeAll @@ -86,15 +84,11 @@ public static void beforeAll() { @AfterAll public static void afterAll() { callSuperAfterAll(new Object() {}); - if(context != null){ - context.close(); - } } @BeforeEach public void beforeEachTest() { operations = rxCBTmpl; - } @Test @@ -102,27 +96,13 @@ public void replacePersonTemplate() { Person person = new Person(1, "Walter", "White"); remove(rxCBTmpl, cName, person.getId().toString()); rxCBTmpl.insertById(Person.class).inCollection(cName).one(person).block(); - CouchbaseTransactionalOperator txOperator = new CouchbaseTransactionalOperator(reactiveCouchbaseTransactionManager); Mono result = txOperator .reactive((ctx) -> ctx.template(rxCBTmpl).findById(Person.class).one(person.getId().toString()) - .flatMap(p -> ctx.template(rxCBTmpl).replaceById(Person.class).one(p.withFirstName("Walt"))) - // .flatMap(it -> Mono.error(new PoofException())) - .then()); - - try { - result.block(); - } catch (TransactionFailedException e) { - if (e.getCause() instanceof SimulateFailureException) { - Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); - assertEquals(person, pFound, "Should have found " + person); - return; - } - e.printStackTrace(); - } + .flatMap(p -> ctx.template(rxCBTmpl).replaceById(Person.class).one(p.withFirstName("Walt"))).then()); + result.block(); Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt"); - // throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); } @Test @@ -130,77 +110,40 @@ public void replacePersonRbTemplate() { Person person = new Person(1, "Walter", "White"); remove(rxCBTmpl, cName, person.getId().toString()); rxCBTmpl.insertById(Person.class).inCollection(cName).one(person).block(); - sleepMs(1000); CouchbaseTransactionalOperator txOperator = new CouchbaseTransactionalOperator(reactiveCouchbaseTransactionManager); Mono result = txOperator .reactive((ctx) -> ctx.template(rxCBTmpl).findById(Person.class).one(person.getId().toString()) .flatMap(p -> ctx.template(rxCBTmpl).replaceById(Person.class).one(p.withFirstName("Walt"))) .flatMap(it -> Mono.error(new SimulateFailureException())).then()); - - try { - result.block(); - } catch (TransactionFailedException e) { - if (e.getCause() instanceof SimulateFailureException) { - Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); - assertEquals(person, pFound, "Should have found " + person); - return; - } - e.printStackTrace(); - } - // Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); - // assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt"); - throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); + assertThrowsWithCause(result::block, TransactionFailedException.class, SimulateFailureException.class); + Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); + assertEquals(person, pFound, "Should have found " + person); } @Test public void insertPersonTemplate() { Person person = new Person(1, "Walter", "White"); remove(rxCBTmpl, cName, person.getId().toString()); - CouchbaseTransactionalOperator txOperator = new CouchbaseTransactionalOperator(reactiveCouchbaseTransactionManager); - Mono result = txOperator.reactive((ctx) -> ctx.template(rxCBTmpl).insertById(Person.class) - .one(person).flatMap(p -> ctx.template(rxCBTmpl).replaceById(Person.class).one(p.withFirstName("Walt"))) - // .flatMap(it -> Mono.error(new PoofException())) - .then()); - - try { - result.block(); - } catch (TransactionFailedException e) { - if (e.getCause() instanceof SimulateFailureException) { - Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); - assertNull(pFound, "Should NOT have found " + pFound); - return; - } - e.printStackTrace(); - } + Mono result = txOperator + .reactive((ctx) -> ctx.template(rxCBTmpl).insertById(Person.class).one(person) + .flatMap(p -> ctx.template(rxCBTmpl).replaceById(Person.class).one(p.withFirstName("Walt"))).then()); + result.block(); Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt"); - // throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); } @Test public void insertPersonRbTemplate() { Person person = new Person(1, "Walter", "White"); remove(rxCBTmpl, cName, person.getId().toString()); - CouchbaseTransactionalOperator txOperator = new CouchbaseTransactionalOperator(reactiveCouchbaseTransactionManager); Mono result = txOperator.reactive((ctx) -> ctx.template(rxCBTmpl).insertById(Person.class) .one(person).flatMap(p -> ctx.template(rxCBTmpl).replaceById(Person.class).one(p.withFirstName("Walt"))) .flatMap(it -> Mono.error(new SimulateFailureException())).then()); - - try { - result.block(); - } catch (TransactionFailedException e) { - if (e.getCause() instanceof SimulateFailureException) { - Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); - assertNull(pFound, "Should NOT have found " + pFound); - return; - } - e.printStackTrace(); - } - // Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); - // assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt"); - throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of "+SimulateFailureException.class); + assertThrowsWithCause(result::block, TransactionFailedException.class, SimulateFailureException.class); + Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); + assertNull(pFound, "Should NOT have found " + pFound); } @Test @@ -208,133 +151,81 @@ public void replacePersonRbRepo() { Person person = new Person(1, "Walter", "White"); remove(rxCBTmpl, cName, person.getId().toString()); rxRepo.withCollection(cName).save(person).block(); - CouchbaseTransactionalOperator txOperator = new CouchbaseTransactionalOperator(reactiveCouchbaseTransactionManager); Mono result = txOperator .reactive((ctx) -> ctx.repository(rxRepo).withCollection(cName).findById(person.getId().toString()) .flatMap(p -> ctx.repository(rxRepo).withCollection(cName).save(p.withFirstName("Walt"))) .flatMap(it -> Mono.error(new SimulateFailureException())).then()); - - try { - result.block(); - } catch (TransactionFailedException e) { - if (e.getCause() instanceof SimulateFailureException) { - Person pFound = rxRepo.withCollection(cName).findById(person.getId().toString()).block(); - assertEquals(person, pFound, "Should have found " + person); - return; - } - e.printStackTrace(); - } - // Person pFound = rxRepo.withCollection(cName).findById(person.getId().toString()).block(); - // assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt"); - throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); + assertThrowsWithCause(result::block, TransactionFailedException.class, SimulateFailureException.class); + Person pFound = rxRepo.withCollection(cName).findById(person.getId().toString()).block(); + assertEquals(person, pFound, "Should have found " + person); } @Test public void insertPersonRbRepo() { Person person = new Person(1, "Walter", "White"); remove(rxCBTmpl, cName, person.getId().toString()); - CouchbaseTransactionalOperator txOperator = new CouchbaseTransactionalOperator(reactiveCouchbaseTransactionManager); Mono result = txOperator .reactive((ctx) -> ctx.repository(rxRepo).withTransaction(txOperator).withCollection(cName).save(person) // insert - //.flatMap(p -> ctx.repository(rxRepo).withCollection(cName).save(p.withFirstName("Walt"))) // replace .flatMap(it -> Mono.error(new SimulateFailureException())).then()); - - try { - result.block(); - } catch (TransactionFailedException e) { - if (e.getCause() instanceof SimulateFailureException) { - Person pFound = rxRepo.withCollection(cName).findById(person.getId().toString()).block(); - assertNull(pFound, "Should NOT have found " + pFound); - return; - } - e.printStackTrace(); - } - // Person pFound = rxRepo.withCollection(cName).findById(person.getId().toString()).block(); - // assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt"); - throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); + assertThrowsWithCause(result::block, TransactionFailedException.class, SimulateFailureException.class); + Person pFound = rxRepo.withCollection(cName).findById(person.getId().toString()).block(); + assertNull(pFound, "Should NOT have found " + pFound); } @Test public void insertPersonRepo() { Person person = new Person(1, "Walter", "White"); remove(rxCBTmpl, cName, person.getId().toString()); - CouchbaseTransactionalOperator txOperator = new CouchbaseTransactionalOperator(reactiveCouchbaseTransactionManager); Mono result = txOperator .reactive((ctx) -> ctx.repository(rxRepo).withCollection(cName).save(person) // insert .flatMap(p -> ctx.repository(rxRepo).withCollection(cName).save(p.withFirstName("Walt"))) // replace - // .flatMap(it -> Mono.error(new PoofException())) .then()); - - try { - result.block(); - } catch (TransactionFailedException e) { - if (e.getCause() instanceof SimulateFailureException) { - Person pFound = rxRepo.withCollection(cName).findById(person.getId().toString()).block(); - assertNull(pFound, "Should NOT have found " + pFound); - return; - } - e.printStackTrace(); - } + result.block(); Person pFound = rxRepo.withCollection(cName).findById(person.getId().toString()).block(); assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt"); - // throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); } - /* @Test - public void replacePersonRbSpringTransactional() { + public void replacePersonSpringTransactional() { Person person = new Person(1, "Walter", "White"); remove(rxCBTmpl, cName, person.getId().toString()); rxCBTmpl.insertById(Person.class).inCollection(cName).one(person).block(); + TransactionalOperator txOperator = TransactionalOperator.create(reactiveCouchbaseTransactionManager); + Mono result = rxCBTmpl.findById(Person.class).one(person.getId().toString()) + .flatMap(p -> rxCBTmpl.replaceById(Person.class).one(p.withFirstName("Walt"))).as(txOperator::transactional); + result.block(); + Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); + assertEquals(person.withFirstName("Walt"), pFound, "Should have found " + person); + } - - TransactionalOperator txOperator = TransactionalOperator.create(reactiveCouchbaseTransactionManager); - Mono result = txOperator.reactive((ctx) -> { - ctx.transactionResultHolder(123); - return ctx.template(rxCBTmpl).findById(Person.class).one(person.getId().toString()) - .flatMap(p -> ctx.template(rxCBTmpl).replaceById(Person.class).one(p.withFirstName("Walt"))) - .as(txOperator::transactional).then(); - }, false); - - //TransactionalOperator txOperator = TransactionalOperator.create(reactiveCouchbaseTransactionManager, new CouchbaseTransactionDefinition()); - //Mono result = txOperator.reactive((ctx) -> { - // ctx.transactionResultHolder(123); - // return ctx.template(rxCBTmpl).findById(Person.class).one(person.getId().toString()) - // .flatMap(p -> ctx.template(rxCBTmpl).replaceById(Person.class).one(p.withFirstName("Walt"))) - // .as(txOperator::transactional).then(); - //}, false); - - try { - result.block(); - } catch (TransactionFailedException e) { - if (e.getCause() instanceof PoofException) { - Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); - assertEquals(person, pFound, "Should have found " + person); - return; - } - e.printStackTrace(); - } + @Test + public void replacePersonRbSpringTransactional() { + Person person = new Person(1, "Walter", "White"); + remove(rxCBTmpl, cName, person.getId().toString()); + rxCBTmpl.insertById(Person.class).inCollection(cName).one(person).block(); + TransactionalOperator txOperator = TransactionalOperator.create(reactiveCouchbaseTransactionManager); + Mono result = rxCBTmpl.findById(Person.class).one(person.getId().toString()) + .flatMap(p -> rxCBTmpl.replaceById(Person.class).one(p.withFirstName("Walt"))) + .flatMap(it -> Mono.error(new SimulateFailureException())).as(txOperator::transactional); + assertThrowsWithCause(result::block, SimulateFailureException.class); Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); - assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt"); - // throw new RuntimeException("Should have been a TransactionFailedException exception with a cause of PoofException"); + assertEquals(person, pFound, "Should have found " + person); + assertEquals(person.getFirstname(), pFound.getFirstname(), "firstname should be "+person.getFirstname()); } -*/ + @Test public void findReplacePersonCBTransactionsRxTmpl() { Person person = new Person(1, "Walter", "White"); remove(rxCBTmpl, cName, person.getId().toString()); rxCBTmpl.insertById(Person.class).inCollection(cName).one(person).block(); CouchbaseTransactionalOperator txOperator = new CouchbaseTransactionalOperator(reactiveCouchbaseTransactionManager); - Mono result = txOperator.reactive(ctx -> { - rxCBTmpl.support().getTxResultHolder(person); - return rxCBTmpl.findById(Person.class).inCollection(cName).transaction(txOperator).one(person.getId().toString()) - .flatMap(pGet -> rxCBTmpl.replaceById(Person.class).inCollection(cName).transaction(txOperator) - .one(pGet.withFirstName("Walt"))) - .then(); - }); + Mono result = txOperator.reactive(ctx -> rxCBTmpl.findById(Person.class).inCollection(cName) + .transaction(txOperator).one(person.getId().toString()).flatMap(pGet -> rxCBTmpl.replaceById(Person.class) + .inCollection(cName).transaction(txOperator).one(pGet.withFirstName("Walt"))) + .then()); result.block(); Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); assertEquals(person.withFirstName("Walt"), pFound, "Should have found Walt"); @@ -344,52 +235,31 @@ public void findReplacePersonCBTransactionsRxTmpl() { public void insertReplacePersonsCBTransactionsRxTmpl() { Person person = new Person(1, "Walter", "White"); remove(rxCBTmpl, cName, person.getId().toString()); - CouchbaseTransactionalOperator txOperator = new CouchbaseTransactionalOperator(reactiveCouchbaseTransactionManager); - Mono result = txOperator.reactive((ctx) -> { - return rxCBTmpl - .insertById(Person.class).inCollection(cName).transaction(txOperator).one(person).flatMap(pInsert -> rxCBTmpl - .replaceById(Person.class).inCollection(cName).transaction(txOperator).one(pInsert.withFirstName("Walt"))) - .then(); - }); - + Mono result = txOperator.reactive((ctx) -> rxCBTmpl + .insertById(Person.class).inCollection(cName).transaction(txOperator).one(person).flatMap(pInsert -> rxCBTmpl + .replaceById(Person.class).inCollection(cName).transaction(txOperator).one(pInsert.withFirstName("Walt"))) + .then()); result.block(); Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); assertEquals(person.withFirstName("Walt"), pFound, "Should have found Walt"); } - @Test void transactionalSavePerson(){ + @Test + void transactionalSavePerson() { Person person = new Person(1, "Walter", "White"); remove(rxCBTmpl, cName, person.getId().toString()); savePerson(person).block(); Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString()).block(); - assertEquals(person, pFound, "Should have found "+person); + assertEquals(person, pFound, "Should have found " + person); } public Mono savePerson(Person person) { - TransactionalOperator transactionalOperator = TransactionalOperator.create(reactiveCouchbaseTransactionManager); return operations.save(person) // - .flatMap(Mono::just) // .as(transactionalOperator::transactional); } - void remove(Collection col, String id) { - remove(col.reactive(), id); - } - - void remove(ReactiveCollection col, String id) { - try { - col.remove(id, RemoveOptions.removeOptions().timeout(Duration.ofSeconds(10))).block(); - } catch (DocumentNotFoundException nfe) { - System.out.println(id + " : " + "DocumentNotFound when deleting"); - } - } - - void remove(CouchbaseTemplate template, String collection, String id) { - remove(template.reactive(), collection, id); - } - void remove(ReactiveCouchbaseTemplate template, String collection, String id) { try { template.removeById(Person.class).inCollection(collection).one(id).block(); diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTemplateTransactionIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTemplateTransactionIntegrationTests.java index 72d91e07c..97c5f109d 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTemplateTransactionIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTemplateTransactionIntegrationTests.java @@ -62,15 +62,12 @@ import org.springframework.transaction.annotation.Transactional; import com.couchbase.client.core.cnc.Event; -//import com.example.demo.CouchbaseTransactionManager; -//import com.example.demo.CouchbaseTransactionalTemplate; /** - * @author Christoph Strobl - * @currentRead Shadow's Edge - Brent Weeks + * @author Michael Reiche */ -//@ContextConfiguration + @ExtendWith({ SpringExtension.class }) @IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED) @Transactional(transactionManager = BeanNames.COUCHBASE_SIMPLE_CALLBACK_TRANSACTION_MANAGER) @@ -111,11 +108,6 @@ public CouchbaseTransactionManager transactionManager(@Autowired CouchbaseClient return new CouchbaseTransactionManager(template, null); } - //@Bean - //public CouchbaseTransactionalTemplate transactionalTemplate(CouchbaseTransactionManager manager) { - // return manager.template(); - //} - } @Autowired CouchbaseTemplate template; diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalRepositoryIntegrationTests.java index 8de0f821f..0aee620f7 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalRepositoryIntegrationTests.java @@ -16,22 +16,35 @@ package org.springframework.data.couchbase.transactions; -import com.couchbase.client.java.transactions.error.TransactionFailedException; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; +import static org.springframework.data.couchbase.transactions.util.TransactionTestUtil.assertInTransaction; +import static org.springframework.data.couchbase.transactions.util.TransactionTestUtil.assertNotInReactiveTransaction; +import static org.springframework.data.couchbase.transactions.util.TransactionTestUtil.assertNotInTransaction; + +import java.util.List; +import java.util.UUID; +import java.util.function.Consumer; + +import com.couchbase.client.java.Cluster; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.support.GenericApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.data.couchbase.CouchbaseClientFactory; +import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; import org.springframework.data.couchbase.config.BeanNames; import org.springframework.data.couchbase.core.CouchbaseTemplate; import org.springframework.data.couchbase.domain.User; import org.springframework.data.couchbase.domain.UserRepository; +import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; +import org.springframework.data.couchbase.repository.config.EnableReactiveCouchbaseRepositories; import org.springframework.data.couchbase.util.ClusterType; import org.springframework.data.couchbase.util.IgnoreWhen; import org.springframework.data.couchbase.util.JavaIntegrationTests; @@ -41,47 +54,37 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.Transactional; -import java.util.List; -import java.util.UUID; -import java.util.function.Consumer; - -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.fail; -import static org.springframework.data.couchbase.transactions.util.TransactionTestUtil.assertInTransaction; -import static org.springframework.data.couchbase.transactions.util.TransactionTestUtil.assertNotInTransaction; +import com.couchbase.client.java.transactions.error.TransactionFailedException; /** * Tests @Transactional with repository methods. */ @IgnoreWhen(clusterTypes = ClusterType.MOCKED) -@SpringJUnitConfig(Config.class) +@SpringJUnitConfig(classes = { CouchbaseTransactionalRepositoryIntegrationTests.Config.class, CouchbaseTransactionalRepositoryIntegrationTests.PersonService.class }) public class CouchbaseTransactionalRepositoryIntegrationTests extends JavaIntegrationTests { + // intellij flags "Could not autowire" when config classes are specified with classes={...}. But they are populated. @Autowired UserRepository userRepo; @Autowired CouchbaseClientFactory couchbaseClientFactory; - PersonService personService; - @Autowired - CouchbaseTemplate operations; - static GenericApplicationContext context; + @Autowired PersonService personService; + @Autowired CouchbaseTemplate operations; @BeforeAll public static void beforeAll() { callSuperBeforeAll(new Object() {}); - context = new AnnotationConfigApplicationContext(Config.class, PersonService.class); } @BeforeEach public void beforeEachTest() { - personService = context.getBean(PersonService.class); + assertNotInTransaction(); + assertNotInReactiveTransaction(); } @AfterEach public void afterEachTest() { assertNotInTransaction(); + assertNotInReactiveTransaction(); } - @Test public void findByFirstname() { operations.insertById(User.class).one(new User(UUID.randomUUID().toString(), "Ada", "Lovelace")); @@ -126,20 +129,16 @@ public void saveRolledBack() { SimulateFailureException.throwEx("fail"); }); fail(); - } - catch (TransactionFailedException ignored) { - } + } catch (TransactionFailedException ignored) {} User user = operations.findById(User.class).one(id); assertNull(user); } - @Service @Component @EnableTransactionManagement - static - class PersonService { + static class PersonService { @Autowired UserRepository userRepo; @Transactional(transactionManager = BeanNames.COUCHBASE_SIMPLE_CALLBACK_TRANSACTION_MANAGER) @@ -153,4 +152,36 @@ public List findByFirstname(String name) { } } + + @Configuration + @EnableCouchbaseRepositories("org.springframework.data.couchbase") + @EnableReactiveCouchbaseRepositories("org.springframework.data.couchbase") + static class Config extends AbstractCouchbaseConfiguration { + + @Override + public String getConnectionString() { + return connectionString(); + } + + @Override + public String getUserName() { + return config().adminUsername(); + } + + @Override + public String getPassword() { + return config().adminPassword(); + } + + @Override + public String getBucketName() { + return bucketName(); + } + + @Bean + public Cluster couchbaseCluster() { + return Cluster.connect("10.144.220.101", "Administrator", "password"); + } + + } } diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalTemplateIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalTemplateIntegrationTests.java index 62e1272e3..202e63d3e 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalTemplateIntegrationTests.java @@ -64,19 +64,16 @@ * Tests for @Transactional, using template methods (findById etc.) */ @IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED) -@SpringJUnitConfig(Config.class) +@SpringJUnitConfig(classes = { Config.class, CouchbaseTransactionalTemplateIntegrationTests.PersonService.class} ) public class CouchbaseTransactionalTemplateIntegrationTests extends JavaIntegrationTests { - + // intellij flags "Could not autowire" when config classes are specified with classes={...}. But they are populated. @Autowired CouchbaseClientFactory couchbaseClientFactory; - /* DO NOT @Autowired - it will result in no @Transactional annotation behavior */ PersonService personService; + @Autowired PersonService personService; @Autowired CouchbaseTemplate operations; - static GenericApplicationContext context; - @BeforeAll public static void beforeAll() { callSuperBeforeAll(new Object() {}); - context = new AnnotationConfigApplicationContext(Config.class, PersonService.class); } @AfterAll @@ -86,7 +83,6 @@ public static void afterAll() { @BeforeEach public void beforeEachTest() { - personService = context.getBean(PersonService.class); // getting it via autowired results in no @Transactional // Skip this as we just one to track TransactionContext operations.removeByQuery(Person.class).withConsistency(REQUEST_PLUS).all(); // doesn't work??? List p = operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).all(); @@ -441,18 +437,16 @@ public void removeEntityWithoutVersion(String id) { public Person declarativeFindReplaceTwicePersonCallback(Person person, AtomicInteger tryCount) { assertInAnnotationTransaction(true); System.err.println("declarativeFindReplacePersonCallback try: " + tryCount.incrementAndGet()); -// System.err.println("declarativeFindReplacePersonCallback cluster : " -// + callbackTm.template().getCouchbaseClientFactory().getCluster().block()); -// System.err.println("declarativeFindReplacePersonCallback resourceHolder : " -// + org.springframework.transaction.support.TransactionSynchronizationManager -// .getResource(callbackTm.template().getCouchbaseClientFactory().getCluster().block())); Person p = personOperations.findById(Person.class).one(person.getId().toString()); Person pUpdated = personOperations.replaceById(Person.class).one(p); return personOperations.replaceById(Person.class).one(pUpdated); } - // todo gpx how do we make COUCHBASE_SIMPLE_CALLBACK_TRANSACTION_MANAGER the default so user only has to specify @Transactional, without the transactionManager? + // todo mr + // todo if there is exactly one bean of type ‘org.springframework.transaction.TransactionManager’. + // todo It’s also possible to put the @Transaction annotation on the class (instead of each method). + // todo see TransactionAspectSupport.determineTransactionManager(TransactionAttribute) @Transactional(transactionManager = BeanNames.COUCHBASE_SIMPLE_CALLBACK_TRANSACTION_MANAGER) public Person replace(Person person, AtomicInteger tryCount) { assertInAnnotationTransaction(true); diff --git a/src/test/java/org/springframework/data/couchbase/transactions/util/TransactionTestUtil.java b/src/test/java/org/springframework/data/couchbase/transactions/util/TransactionTestUtil.java index e3bfb4c18..fa8ce3a04 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/util/TransactionTestUtil.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/util/TransactionTestUtil.java @@ -16,6 +16,7 @@ package org.springframework.data.couchbase.transactions.util; import org.springframework.transaction.NoTransactionException; +import reactor.core.publisher.Mono; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -38,4 +39,16 @@ public static void assertNotInTransaction() { } } + public static Mono assertInReactiveTransaction(T o) { + return org.springframework.transaction.reactive.TransactionSynchronizationManager.forCurrentTransaction().just(o); + } + + public static void assertNotInReactiveTransaction() { + try { + org.springframework.transaction.reactive.TransactionSynchronizationManager.forCurrentTransaction().block(); + } + catch (NoTransactionException ignored) { + } + } + } From 76191cb9dcba833b142a5bca662cad361076918b Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Mon, 30 May 2022 14:13:51 +0100 Subject: [PATCH 07/15] Removing unused classes (Reducing the cognitive burden) --- .../AttemptContextReactiveAccessor.java | 66 +- .../transactions/TransactionsReactive.java | 753 ------------------ .../demo/CouchbaseTransactionManager.pre-core | 201 ----- .../demo/CouchbaseTransactionalTemplate.java | 67 -- .../demo/SpringTransactionGetResult.java | 31 - .../couchbase/repository/TransactionMeta.java | 40 - .../CouchbaseCallbackTransactionManager.java | 296 ------- .../transaction/CouchbaseResourceHolderx.java | 120 --- .../CouchbaseSimpleTransactionManager.java | 49 -- .../ReactiveCouchbaseClientUtils.java | 39 - ...seTemplateTransactionIntegrationTests.java | 1 - 11 files changed, 1 insertion(+), 1662 deletions(-) delete mode 100644 src/main/java/com/couchbase/transactions/TransactionsReactive.java delete mode 100644 src/main/java/com/example/demo/CouchbaseTransactionManager.pre-core delete mode 100644 src/main/java/com/example/demo/CouchbaseTransactionalTemplate.java delete mode 100644 src/main/java/com/example/demo/SpringTransactionGetResult.java delete mode 100644 src/main/java/org/springframework/data/couchbase/repository/TransactionMeta.java delete mode 100644 src/main/java/org/springframework/data/couchbase/transaction/CouchbaseCallbackTransactionManager.java delete mode 100644 src/main/java/org/springframework/data/couchbase/transaction/CouchbaseResourceHolderx.java delete mode 100644 src/main/java/org/springframework/data/couchbase/transaction/CouchbaseSimpleTransactionManager.java diff --git a/src/main/java/com/couchbase/client/java/transactions/AttemptContextReactiveAccessor.java b/src/main/java/com/couchbase/client/java/transactions/AttemptContextReactiveAccessor.java index 7397759e5..a094d20ce 100644 --- a/src/main/java/com/couchbase/client/java/transactions/AttemptContextReactiveAccessor.java +++ b/src/main/java/com/couchbase/client/java/transactions/AttemptContextReactiveAccessor.java @@ -66,17 +66,6 @@ public static ReactiveTransactionAttemptContext reactive(TransactionAttemptConte return new ReactiveTransactionAttemptContext(getCore(atr), serializer); } - public static TransactionAttemptContext blocking(ReactiveTransactionAttemptContext atr) { - JsonSerializer serializer; - try { - Field field = ReactiveTransactionAttemptContext.class.getDeclaredField("serializer"); - field.setAccessible(true); - serializer = (JsonSerializer) field.get(atr); - } catch (Throwable err) { - throw new RuntimeException(err); - } - return new TransactionAttemptContext(getCore(atr), serializer); - } public static CoreTransactionLogger getLogger(ReactiveTransactionAttemptContext attemptContextReactive) { return attemptContextReactive.logger(); @@ -100,6 +89,7 @@ public static CoreTransactionAttemptContext newCoreTranactionAttemptContext(Reac throw new RuntimeException(err); } + // todo gpx options need to be loaded from Cluster and TransactionOptions (from TransactionsWrapper) CoreTransactionOptions perConfig = new CoreTransactionOptions(Optional.empty(), Optional.empty(), Optional.empty(), Optional.of(Duration.ofMinutes(10)), Optional.empty(), Optional.empty()); @@ -115,10 +105,6 @@ public static CoreTransactionAttemptContext newCoreTranactionAttemptContext(Reac return coreTransactionAttemptContext; } - private static Duration now() { - return Duration.of(System.nanoTime(), ChronoUnit.NANOS); - } - public static ReactiveTransactionAttemptContext from(CoreTransactionAttemptContext coreTransactionAttemptContext, JsonSerializer serializer) { TransactionAttemptContext tac = new TransactionAttemptContext(coreTransactionAttemptContext, serializer); @@ -147,61 +133,11 @@ public static CoreTransactionAttemptContext getCore(TransactionAttemptContext at } } - public static Mono implicitCommit(ReactiveTransactionAttemptContext atr, boolean b) { - CoreTransactionAttemptContext coreTransactionsReactive = getCore(atr); - try { - // getDeclaredMethod() does not find it (because of primitive arg?) - // CoreTransactionAttemptContext.class.getDeclaredMethod("implicitCommit", Boolean.class); - Method[] methods = CoreTransactionAttemptContext.class.getDeclaredMethods(); - Method method = null; - for (Method m : methods) { - if (m.getName().equals("implicitCommit")) { - method = m; - break; - } - } - if (method == null) { - throw new RuntimeException("did not find implicitCommit method"); - } - method.setAccessible(true); - return (Mono) method.invoke(coreTransactionsReactive, b); - } catch (Throwable err) { - throw new RuntimeException(err); - } - - } - - public static AttemptState getState(ReactiveTransactionAttemptContext atr) { - CoreTransactionAttemptContext coreTransactionsReactive = getCore(atr); - try { - Field field = CoreTransactionAttemptContext.class.getDeclaredField("state"); - field.setAccessible(true); - return (AttemptState) field.get(coreTransactionsReactive); - } catch (Throwable err) { - throw new RuntimeException(err); - } - } - public static ReactiveTransactionAttemptContext createReactiveTransactionAttemptContext( CoreTransactionAttemptContext core, JsonSerializer jsonSerializer) { return new ReactiveTransactionAttemptContext(core, jsonSerializer); } - public static CoreTransactionsReactive getCoreTransactionsReactive(ReactiveTransactions transactions) { - try { - Field field = ReactiveTransactions.class.getDeclaredField("internal"); - field.setAccessible(true); - return (CoreTransactionsReactive) field.get(transactions); - } catch (Throwable err) { - throw new RuntimeException(err); - } - } - - public static TransactionAttemptContext newTransactionAttemptContext(CoreTransactionAttemptContext ctx, - JsonSerializer jsonSerializer) { - return new TransactionAttemptContext(ctx, jsonSerializer); - } - public static TransactionResult run(Transactions transactions, Consumer transactionLogic, CoreTransactionOptions coreTransactionOptions) { return reactive(transactions).runBlocking(transactionLogic, coreTransactionOptions); } diff --git a/src/main/java/com/couchbase/transactions/TransactionsReactive.java b/src/main/java/com/couchbase/transactions/TransactionsReactive.java deleted file mode 100644 index 352135ead..000000000 --- a/src/main/java/com/couchbase/transactions/TransactionsReactive.java +++ /dev/null @@ -1,753 +0,0 @@ -///* -// * Copyright 2021 Couchbase, Inc. -// * -// * 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 -// * -// * http://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.couchbase.transactions; -// -//import com.couchbase.client.core.annotation.Stability; -//import com.couchbase.client.core.cnc.EventBus; -//import com.couchbase.client.core.retry.reactor.DefaultRetry; -//import com.couchbase.client.core.retry.reactor.Jitter; -//import com.couchbase.client.core.retry.reactor.RetryContext; -//import com.couchbase.client.java.Cluster; -//import com.couchbase.client.java.ReactiveCollection; -//import com.couchbase.client.java.ReactiveScope; -//import com.couchbase.client.java.json.JsonObject; -//import com.couchbase.client.java.query.ReactiveQueryResult; -//import com.couchbase.transactions.cleanup.ClusterData; -//import com.couchbase.transactions.cleanup.TransactionsCleanup; -//import com.couchbase.transactions.components.ATR; -//import com.couchbase.transactions.components.ActiveTransactionRecord; -//import com.couchbase.transactions.config.MergedTransactionConfig; -//import com.couchbase.transactions.config.PerTransactionConfig; -//import com.couchbase.transactions.config.PerTransactionConfigBuilder; -//import com.couchbase.transactions.config.SingleQueryTransactionConfig; -//import com.couchbase.transactions.config.SingleQueryTransactionConfigBuilder; -//import com.couchbase.transactions.config.TransactionConfig; -//import com.couchbase.transactions.deferred.TransactionSerializedContext; -//import com.couchbase.transactions.error.TransactionCommitAmbiguous; -//import com.couchbase.transactions.error.TransactionExpired; -//import com.couchbase.transactions.error.TransactionFailedException; -//import com.couchbase.transactions.error.internal.ErrorClasses; -//import com.couchbase.transactions.error.external.TransactionOperationFailed; -//import com.couchbase.transactions.forwards.Supported; -//import com.couchbase.transactions.log.EventBusPersistedLogger; -//import com.couchbase.transactions.log.PersistedLogWriter; -//import com.couchbase.transactions.log.TransactionLogEvent; -//import com.couchbase.transactions.support.AttemptContextFactory; -//import com.couchbase.transactions.support.AttemptState; -//import com.couchbase.transactions.support.OptionsWrapperUtil; -//import com.couchbase.transactions.util.DebugUtil; -//import reactor.core.publisher.Mono; -//import reactor.core.scheduler.Schedulers; -// -//import java.time.Duration; -//import java.time.temporal.ChronoUnit; -//import java.util.Objects; -//import java.util.Optional; -//import java.util.UUID; -//import java.util.concurrent.TimeUnit; -//import java.util.concurrent.atomic.AtomicReference; -//import java.util.function.Consumer; -//import java.util.function.Function; -//import java.util.function.Predicate; -// -//import static com.couchbase.transactions.error.internal.TransactionOperationFailedBuilder.createError; -//import static com.couchbase.transactions.log.PersistedLogWriter.MAX_LOG_ENTRIES_DEFAULT; -//import static com.couchbase.transactions.support.SpanWrapperUtil.DB_COUCHBASE_TRANSACTIONS; -// -///** -// * An asynchronous version of {@link Transactions}, allowing transactions to be created and run in an asynchronous -// * manner. -// *

-// * The main method to run transactions is {@link TransactionsReactive#run}. -// */ -//public class TransactionsReactive { -// static final int MAX_ATTEMPTS = 1000; -// private final TransactionsCleanup cleanup; -// private final TransactionConfig config; -// private AttemptContextFactory attemptContextFactory; -// private EventBusPersistedLogger persistedLogger; -// -// /** -// * This is package-private. Applications should create a {@link Transactions} object instead, and then call {@link -// * Transactions#reactive}. -// */ -// static TransactionsReactive create(Cluster cluster, TransactionConfig config) { -// return new TransactionsReactive(cluster, config); -// } -// -// private TransactionsReactive(Cluster cluster, TransactionConfig config) { -// Objects.requireNonNull(cluster); -// Objects.requireNonNull(config); -// -// ClusterData clusterData = new ClusterData(cluster); -// this.config = config; -// this.attemptContextFactory = config.attemptContextFactory(); -// MergedTransactionConfig merged = new MergedTransactionConfig(config, Optional.empty()); -// cleanup = new TransactionsCleanup(merged, clusterData); -// -// config.persistentLoggingCollection().ifPresent(collection -> { -// PersistedLogWriter persistedLogWriter = new PersistedLogWriter(collection, MAX_LOG_ENTRIES_DEFAULT); -// persistedLogger = new EventBusPersistedLogger(cluster.environment().eventBus(), persistedLogWriter, merged); -// }); -// } -// -// -// /** -// * The main transactions 'engine', responsible for attempting the transaction logic as many times as required, -// * until the transaction commits, is explicitly rolled back, or expires. -// */ -// // TODO: changed from private to public. package-protected plus an accessor would be ok to -// public Mono executeTransaction(MergedTransactionConfig config, -// TransactionContext overall, -// Mono transactionLogic) { -// AtomicReference startTime = new AtomicReference<>(); -// -// return Mono.just(overall) -// -// .subscribeOn(reactor.core.scheduler.Schedulers.elastic()) -// -// .doOnSubscribe(v -> { -// if (startTime.get() == null) startTime.set(System.nanoTime()); -// }) -// -// // Where the magic happens: execute the app's transaction logic -// // A ReactiveTransactionAttemptContext gets created in here. Rollback requires one of these (so it knows what -// // to rollback), so only errors thrown inside this block can trigger rollback. -// // So, expiry checks only get done inside this block. -// .then(transactionLogic) -// -// .flatMap(this::executeImplicitCommit) -// -// // Track an attempt if non-error, and request that the attempt be cleaned up. Similar logic is also -// // done in executeHandleErrorsPreRetry. -// .doOnNext(ctx -> executeAddAttemptAndCleanupRequest(config, overall, ctx)) -// -// // Track an attempt if error, and perform rollback if needed. -// // All errors reaching here must be a `TransactionOperationFailed`. -// .onErrorResume(err -> executeHandleErrorsPreRetry(config, overall, err)) -// -// // This is the main place to retry txns. Feed all errors up to this centralised point. -// // All errors reaching here must be a `TransactionOperationFailed`. -// .retryWhen(executeCreateRetryWhen(overall)) -// -// // If we're here, then we've hit an error that we don't want to retry. -// // Either raise some derivative of TransactionFailedException to the app, or return an ReactiveTransactionAttemptContext -// // to return success (some errors result in success, e.g. TRANSACTION_FAILED_POST_COMMIT) -// // All errors reaching here must be an `ErrorWrapper`. -// .onErrorResume(err -> executeHandleErrorsPostRetry(overall, err)) -// -// .doOnError(err -> { -// if (config.logOnFailure() && !config.logDirectly()) { -// EventBus eventBus = cleanup.clusterData().cluster().environment().eventBus(); -// overall.LOGGER.logs().forEach(log -> { -// eventBus.publish(new TransactionLogEvent(config.logOnFailureLevel(), -// TransactionLogEvent.DEFAULT_CATEGORY, log.toString())); -// }); -// } -// }) -// -// // If we get here, success -// .doOnSuccess(v -> -// overall.LOGGER.info("finished txn in %dus", -// TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - startTime.get())) -// ) -// -// // Safe to do single() as there will only ever be 1 result -// .single() -// .map(v -> createResultFromContext(overall)); -// } -// -// private reactor.util.retry.Retry executeCreateRetryWhen(TransactionContext overall) { -// Predicate> predicate = context -> { -// Throwable exception = context.exception(); -// -// if (!(exception instanceof TransactionOperationFailed)) { -// // A bug. Only TransactionOperationFailed is allowed to reach here. -// throw new IllegalStateException("Non-TransactionOperationFailed '" + DebugUtil.dbg(exception) + "' received during retry, this is a bug", exception); -// } -// -// TransactionOperationFailed e = (TransactionOperationFailed) exception; -// -// overall.LOGGER.info("TransactionOperationFailed retryTransaction=%s", e.retryTransaction()); -// -// return e.retryTransaction(); -// }; -// -// return DefaultRetry.create(predicate) -// -// .exponentialBackoff(Duration.of(1, ChronoUnit.MILLIS), -// Duration.of(2, ChronoUnit.MILLIS)) -// -// .doOnRetry(v -> overall.LOGGER.info("<>", "retrying transaction after backoff %dmillis", v.backoff().toMillis())) -// -// // Add some jitter so two txns don't livelock each other -// .jitter(Jitter.random()) -// -// // Really, this is a safety-guard. The txn will be aborted when it expires. -// .retryMax(MAX_ATTEMPTS) -// -// .toReactorRetry(); -// } -// -// private Mono executeHandleErrorsPreRetry(MergedTransactionConfig config, -// TransactionContext overall, Throwable err) { -// if (!(err instanceof TransactionOperationFailed)) { -// // A bug. Only TransactionOperationFailed is allowed to reach here. -// overall.LOGGER.warn("<>", "received non-TransactionOperationFailed error %s, unable to rollback as don't have " + -// "context", DebugUtil.dbg(err)); -// return Mono.error(new IllegalStateException("received non-TransactionOperationFailed error " + err.getClass().getName() + " in pre-retry", err)); -// } -// -// Mono autoRollback = Mono.empty(); -// Mono cleanupReq = Mono.empty(); -// -// TransactionOperationFailed e = (TransactionOperationFailed) err; -// ReactiveTransactionAttemptContext ctx = e.context(); -// -// overall.LOGGER.info("<>", "finishing attempt off after error '%s'", e); -// -// if (e.autoRollbackAttempt()) { -// // In queryMode we always ROLLBACK, as there is possibly delta table state to cleanup, and there may be an -// // ATR - we don't know -// if (ctx.state() == AttemptState.NOT_STARTED && !ctx.queryMode()) { -// // This is a better way of doing [RETRY-ERR-NOATR] and likely means that the older logic for -// // handling that won't trigger now -// ctx.LOGGER.info(ctx.attemptId(), "told to auto-rollback but in NOT_STARTED state, so nothing to do - skipping rollback"); -// } -// else { -// ctx.LOGGER.info(ctx.attemptId(), "auto-rolling-back on error"); -// -// autoRollback = ctx.rollbackInternal(false); -// } -// } else { -// ctx.LOGGER.info(ctx.attemptId(), "has been told to skip auto-rollback"); -// } -// -// if (!config.runRegularAttemptsCleanupThread()) { -// // Don't add a request to a queue that no-one will be processing -// ctx.LOGGER.trace(ctx.attemptId(), "skipping addition of cleanup request on failure as regular cleanup disabled"); -// } -// else { -// cleanupReq = Mono.fromRunnable(() -> addCleanupRequestForContext(ctx)); -// } -// -// Mono addAttempt = Mono.fromRunnable(() -> { -// TransactionAttempt ta = TransactionAttempt.createFromContext(ctx, Optional.of(err)); -// overall.addAttempt(ta); -// ctx.LOGGER.info(ctx.attemptId(), "added attempt %s after error", ta); -// }); -// -// final Mono cleanupReqForLambda = cleanupReq; -// -// return autoRollback -// // See [Primary Operations] section in design document -// .onErrorResume(er -> { -// overall.LOGGER.info("<>", "rollback failed with %s, raising original error but with retryTransaction turned off", -// DebugUtil.dbg(er)); -// -// // Still want to add attempt and cleanup request -// return cleanupReqForLambda -// .then(addAttempt) -// .then(Mono.error(createError(e.context(), e.causingErrorClass()) -// .raiseException(e.toRaise()) -// .cause(e.getCause()) -// .build())); -// }) -// .then(cleanupReqForLambda) -// // Only want to add the attempt after doing the rollback, so the attempt has the correct state (hopefully -// // ROLLED_BACK) -// .then(addAttempt) -// .then(Mono.defer(() -> { -// if (e.retryTransaction() && overall.hasExpiredClientSide()) { -// overall.LOGGER.info("<>", "original error planned to retry transaction, but it has subsequently expired"); -// return Mono.error(createError(ctx, ErrorClasses.FAIL_EXPIRY) -// .doNotRollbackAttempt() -// .raiseException(TransactionOperationFailed.FinalErrorToRaise.TRANSACTION_EXPIRED) -// .build()); -// } -// else { -// // Raise the error up the stack so the logic later can decide whether to retry the transaction -// overall.LOGGER.info("<>", "reraising original exception %s", DebugUtil.dbg(err)); -// return Mono.error(err); -// } -// })) -// .doFinally(v -> ctx.span().failWith(e)) -// .thenReturn(ctx); -// } -// -// private Mono executeHandleErrorsPostRetry(TransactionContext overall, Throwable err) { -// if (!(err instanceof TransactionOperationFailed)) { -// // A bug. Only TransactionOperationFailed is allowed to reach here. -// return Mono.error(new IllegalStateException("Non-TransactionOperationFailed '" + DebugUtil.dbg(err) + "' received, this is a bug")); -// } -// -// TransactionResult result = createResultFromContext(overall); -// TransactionOperationFailed e = (TransactionOperationFailed) err; -// -// if (e.toRaise() == TransactionOperationFailed.FinalErrorToRaise.TRANSACTION_FAILED_POST_COMMIT) { -// e.context().LOGGER.info(e.context().attemptId(), "converted TRANSACTION_FAILED_POST_COMMIT to success, unstagingComplete() will be false"); -// -// return Mono.just(e.context()); -// } -// else { -// TransactionFailedException ret; -// -// switch (e.toRaise()) { -// case TRANSACTION_EXPIRED: { -// String msg = "Transaction has expired configured timeout of " + overall.expirationTime().toMillis() + "msecs. The transaction is not committed."; -// ret = new TransactionExpired(e.getCause(), result, msg); -// break; -// } -// case TRANSACTION_COMMIT_AMBIGUOUS: { -// String msg = "It is ambiguous whether the transaction committed"; -// ret = new TransactionCommitAmbiguous(e.getCause(), result, msg); -// break; -// } -// default: -// ret = new TransactionFailedException(e.getCause(), result); -// break; -// } -// -// e.context().LOGGER.info(e.context().attemptId(), "converted TransactionOperationFailed %s to final error %s", -// e.toRaise(), ret); -// -// return Mono.error(ret); -// } -// } -// -// private void executeAddAttemptAndCleanupRequest(MergedTransactionConfig config, TransactionContext overall, -// ReactiveTransactionAttemptContext ctx) { -// TransactionAttempt ta = TransactionAttempt.createFromContext(ctx, Optional.empty()); -// overall.addAttempt(ta); -// ctx.LOGGER.info(ctx.attemptId(), "added attempt %s after success", ta); -// -// if (config.runRegularAttemptsCleanupThread()) { -// addCleanupRequestForContext(ctx); -// } else { -// ctx.LOGGER.trace(ctx.attemptId(), "skipping addition of cleanup request on success"); -// } -// -// ctx.span().finish(); -// } -// -// private Mono executeImplicitCommit(ReactiveTransactionAttemptContext ctx) { -// return Mono.defer(() -> { -// // If app has not explicitly performed a commit, assume they want to do so anyway -// if (!ctx.isDone()) { -// if (ctx.serialized().isPresent()) { -// return Mono.just(ctx); -// } else { -// ctx.LOGGER.trace(ctx.attemptId(), "doing implicit commit"); -// -// return ctx.commit() -// .then(Mono.just(ctx)) -// .onErrorResume(err -> Mono.error(TransactionOperationFailed.convertToOperationFailedIfNeeded(err, ctx))); -// } -// } else { -// return Mono.just(ctx); -// } -// }); -// } -// -// // TODO: changed from package-protected to public (could have just used an accessor class in same package) -// public ReactiveTransactionAttemptContext createAttemptContext(TransactionContext overall, -// MergedTransactionConfig config, -// String attemptId) { -// // null only happens in testing with Mockito, harmless -// if (overall != null) { -// return attemptContextFactory.create(overall, config, attemptId, this, Optional.of(overall.span())); -// } else { -// return null; -// } -// } -// -// /** -// * Runs the supplied transactional logic until success or failure. -// *

-// * This is the asynchronous version of {@link Transactions#run}, so to cover the differences: -// *

    -// *
  • The transaction logic is supplied with a {@link ReactiveTransactionAttemptContext}, which contains asynchronous -// * methods to allow it to read, mutate, insert and delete documents, as well as commit or rollback the -// * transactions.
  • -// *
  • The transaction logic should run these methods as a Reactor chain.
  • -// *
  • The transaction logic should return a Mono{@literal <}Void{@literal >}. Any -// * Flux or Mono can be converted to a Mono{@literal <}Void{@literal >} by -// * calling .then() on it.
  • -// *
  • This method returns a Mono{@literal <}TransactionResult{@literal >}, which should be handled -// * as a normal Reactor Mono.
  • -// *
-// * -// * @param transactionLogic the application's transaction logic -// * @param perConfig the configuration to use for this transaction -// * @return there is no need to check the returned {@link TransactionResult}, as success is implied by the lack of a -// * thrown exception. It contains information useful only for debugging and logging. -// * @throws TransactionFailedException or a derived exception if the transaction fails to commit for any reason, possibly -// * after multiple retries. The exception contains further details of the error. Not -// */ -// public Mono run(Function> transactionLogic, -// PerTransactionConfig perConfig) { -// return Mono.defer(() -> { -// MergedTransactionConfig merged = new MergedTransactionConfig(config, Optional.of(perConfig)); -// -// TransactionContext overall = -// new TransactionContext(cleanup.clusterData().cluster().environment().requestTracer(), -// cleanup.clusterData().cluster().environment().eventBus(), -// UUID.randomUUID().toString(), -// now(), -// Duration.ZERO, -// merged); -// AtomicReference startTime = new AtomicReference<>(0L); -// -// Mono ob = Mono.fromCallable(() -> { -// String txnId = UUID.randomUUID().toString(); -// overall.LOGGER.info(configDebug(config, perConfig)); -// return createAttemptContext(overall, merged, txnId); -// }).flatMap(ctx -> { -// ctx.LOGGER.info("starting attempt %d/%s/%s", -// overall.numAttempts(), ctx.transactionId(), ctx.attemptId()); -// Mono result = transactionLogic.apply(ctx); -// return result -// .onErrorResume(err -> { -// ctx.LOGGER.info(ctx.attemptId(), "caught exception '%s' in async, rethrowing", err); -// logElidedStacktrace(ctx, err); -// -// return Mono.error(TransactionOperationFailed.convertToOperationFailedIfNeeded(err, ctx)); -// }) -// .thenReturn(ctx); -// }).doOnSubscribe(v -> startTime.set(System.nanoTime())) -// .doOnNext(v -> v.LOGGER.trace(v.attemptId(), "finished attempt %d in %sms", -// overall.numAttempts(), (System.nanoTime() - startTime.get()) / 1_000_000)); -// -// return executeTransaction(merged, overall, ob) -// .doOnNext(v -> overall.span().finish()) -// .doOnError(err -> overall.span().failWith(err)); -// }); -// } -// -// // Printing the stacktrace is expensive in terms of log noise, but has been a life saver on many debugging -// // encounters. Strike a balance by eliding the more useless elements. -// // TODO: changed from private to public -// public void logElidedStacktrace(ReactiveTransactionAttemptContext ctx, Throwable err) { -// DebugUtil.fetchElidedStacktrace(err, (s) -> ctx.LOGGER.info(ctx.attemptId(), " " + s.toString())); -// } -// -// // TODO: changed from private to public -// public static String configDebug(TransactionConfig config, PerTransactionConfig perConfig) { -// StringBuilder sb = new StringBuilder(); -// sb.append("library version: "); -// sb.append(TransactionsReactive.class.getPackage().getImplementationVersion()); -// sb.append(" config: "); -// sb.append("atrs="); -// sb.append(config.numAtrs()); -// sb.append(", metadataCollection="); -// sb.append(config.metadataCollection()); -// sb.append(", expiry="); -// sb.append(perConfig.expirationTime().orElse(config.transactionExpirationTime()).toMillis()); -// sb.append("msecs durability="); -// sb.append(config.durabilityLevel()); -// sb.append(" per-txn config="); -// sb.append(" durability="); -// sb.append(perConfig.durabilityLevel()); -// sb.append(", supported="); -// sb.append(Supported.SUPPORTED); -// return sb.toString(); -// } -// -// /** -// * Convenience overload that runs {@link TransactionsReactive#run} with a default PerTransactionConfig. -// */ -// public Mono run(Function> transactionLogic) { -// return run(transactionLogic, PerTransactionConfigBuilder.create().build()); -// } -// -// @Stability.Volatile -// public Mono commit(TransactionSerializedContext serialized, PerTransactionConfig perConfig) { -// return deferred(serialized, -// perConfig, -// // Nothing to actually do, just want the implicit commit -// (ctx) -> Mono.empty()); -// } -// -// @Stability.Volatile -// public Mono rollback(TransactionSerializedContext serialized, PerTransactionConfig perConfig) { -// return deferred(serialized, -// perConfig, -// (ctx) -> ctx.rollback()); -// } -// -// @Stability.Volatile -// private Mono deferred(TransactionSerializedContext serialized, -// PerTransactionConfig perConfig, -// Function> initial) { -// MergedTransactionConfig merged = new MergedTransactionConfig(config, Optional.of(perConfig)); -// JsonObject hydrated = JsonObject.fromJson(serialized.encodeAsString()); -// -// String atrBucket = hydrated.getString("atrBucket"); -// String atrScope = hydrated.getString("atrScope"); -// String atrCollectionName = hydrated.getString("atrCollection"); -// String atrId = hydrated.getString("atrId"); -// ReactiveCollection atrCollection = cleanup.clusterData() -// .getBucketFromName(atrBucket) -// .scope(atrScope) -// .collection(atrCollectionName); -// -// return ActiveTransactionRecord.getAtr(atrCollection, -// atrId, -// OptionsWrapperUtil.kvTimeoutNonMutating(merged, atrCollection.core()), -// null) -// -// .flatMap(atrOpt -> { -// if (!atrOpt.isPresent()) { -// return Mono.error(new IllegalStateException(String.format("ATR %s/%s could not be found", -// atrBucket, atrId))); -// } -// else { -// ATR atr = atrOpt.get(); -// -// // Note startTimeServerMillis is written with ${Mutation.CAS} while currentTimeServer -// // could have come from $vbucket.HLC and is hence one-second granularity. So, this is a -// // somewhat imperfect comparison. -// Duration currentTimeServer = Duration.ofNanos(atr.cas()); -// Duration startTimeServer = Duration.ofMillis(hydrated.getLong("startTimeServerMillis")); -// -// // This includes the time elapsed during the first part of the transaction, plus any time -// // elapsed during the period the transaction was expired. Total time since the transaction -// // began, basically. -// Duration timeElapsed = currentTimeServer.minus(startTimeServer); -// -// TransactionContext overall = -// new TransactionContext(cleanup.clusterData().cluster().environment().requestTracer(), -// cleanup.clusterData().cluster().environment().eventBus(), -// UUID.randomUUID().toString(), -// Duration.ofNanos(System.nanoTime()), -// timeElapsed, -// merged); -// AtomicReference startTime = new AtomicReference<>(0L); -// -// overall.LOGGER.info("elapsed time = %dmsecs (ATR start time %dmsecs, current ATR time %dmsecs)", -// timeElapsed.toMillis(), startTimeServer.toMillis(), currentTimeServer.toMillis()); -// -// Mono ob = Mono.defer(() -> { -// ReactiveTransactionAttemptContext ctx = attemptContextFactory.createFrom(hydrated, overall, merged, this); -// ctx.LOGGER.info("starting attempt %d/%s/%s", -// overall.numAttempts(), ctx.transactionId(), ctx.attemptId()); -// ctx.LOGGER.info(configDebug(config, perConfig)); -// -// return initial.apply(ctx) -// -// // TXNJ-50: Make sure we run user's blocking logic on a scheduler that can take it -// .subscribeOn(Schedulers.elastic()) -// -// .onErrorResume(err -> { -// ctx.LOGGER.info(ctx.attemptId(), "caught exception '%s' in deferred, rethrowing", -// err); -// -// logElidedStacktrace(ctx, err); -// -// return Mono.error(TransactionOperationFailed.convertToOperationFailedIfNeeded(err, ctx)); -// }) -// -// .doOnSubscribe(v -> startTime.set(System.nanoTime())) -// -// .doOnNext(v -> { -// ctx.LOGGER.trace(ctx.attemptId(), "finished attempt %d in %sms", -// overall.numAttempts(), (System.nanoTime() - startTime.get()) / 1_000_000); -// }) -// -// .thenReturn(ctx); -// }); -// -// return executeTransaction(merged, overall, ob) -// .doOnNext(v -> overall.span().attribute(DB_COUCHBASE_TRANSACTIONS + "retries", overall.numAttempts()).finish()) -// .doOnError(err -> overall.span().attribute(DB_COUCHBASE_TRANSACTIONS + "retries", overall.numAttempts()).failWith(err)); -// } -// }); -// } -// -// Mono runBlocking(Consumer txnLogic, PerTransactionConfig perConfig) { -// return Mono.defer(() -> { -// MergedTransactionConfig merged = new MergedTransactionConfig(config, Optional.of(perConfig)); -// TransactionContext overall = -// new TransactionContext(cleanup.clusterData().cluster().environment().requestTracer(), -// cleanup.clusterData().cluster().environment().eventBus(), -// UUID.randomUUID().toString(), -// now(), -// Duration.ZERO, -// merged); -// AtomicReference startTime = new AtomicReference<>(0L); -// overall.LOGGER.info(configDebug(config, perConfig)); -// -// Mono ob = Mono.defer(() -> { -// String txnId = UUID.randomUUID().toString(); -// ReactiveTransactionAttemptContext ctx = createAttemptContext(overall, merged, txnId); -// TransactionAttemptContext ctxBlocking = new TransactionAttemptContext(ctx); -// ctx.LOGGER.info("starting attempt %d/%s/%s", -// overall.numAttempts(), ctx.transactionId(), ctx.attemptId()); -// -// return Mono.fromRunnable(() -> txnLogic.accept(ctxBlocking)) -// -// // TXNJ-50: Make sure we run user's blocking logic on a scheduler that can take it -// .subscribeOn(Schedulers.elastic()) -// -// .onErrorResume(err -> { -// ctx.LOGGER.info(ctx.attemptId(), "caught exception '%s' in runBlocking, rethrowing", err); -// -// logElidedStacktrace(ctx, err); -// -// return Mono.error(TransactionOperationFailed.convertToOperationFailedIfNeeded(err, ctx)); -// }) -// -// .doOnSubscribe(v -> startTime.set(System.nanoTime())) -// -// .doOnNext(v -> { -// ctx.LOGGER.trace(ctx.attemptId(), "finished attempt %d in %sms", -// overall.numAttempts(), (System.nanoTime() - startTime.get()) / 1_000_000); -// }) -// -// .thenReturn(ctx); -// }); -// -// return executeTransaction(merged, overall, ob) -// .doOnNext(v -> overall.span().attribute(DB_COUCHBASE_TRANSACTIONS + "retries", overall.numAttempts()).finish()) -// .doOnError(err -> overall.span().attribute(DB_COUCHBASE_TRANSACTIONS + "retries", overall.numAttempts()).failWith(err)); -// }); -// } -// -// public TransactionConfig config() { -// return config; -// } -// -// private static Duration now() { -// return Duration.of(System.nanoTime(), ChronoUnit.NANOS); -// } -// -// TransactionsCleanup cleanup() { -// return cleanup; -// } -// -// private void addCleanupRequestForContext(ReactiveTransactionAttemptContext ctx) { -// // Whether the txn was successful or not, still want to clean it up -// if (ctx.queryMode()) { -// ctx.LOGGER.info(ctx.attemptId(), "Skipping cleanup request as in query mode"); -// } -// else if (ctx.serialized().isPresent()) { -// ctx.LOGGER.info(ctx.attemptId(), "Skipping cleanup request as deferred transaction"); -// } -// else if (ctx.atrId().isPresent() && ctx.atrCollection().isPresent()) { -// switch (ctx.state()) { -// case NOT_STARTED: -// case COMPLETED: -// case ROLLED_BACK: -// ctx.LOGGER.trace(ctx.attemptId(), "Skipping addition of cleanup request in state %s", ctx.state()); -// break; -// default: -// ctx.LOGGER.trace(ctx.attemptId(), "Adding cleanup request for %s/%s", -// ctx.atrCollection().get().name(), ctx.atrId().get()); -// -// cleanup.add(ctx.createCleanupRequest()); -// } -// } else { -// // No ATR entry to remove -// ctx.LOGGER.trace(ctx.attemptId(), "Skipping cleanup request as no ATR entry to remove (due to no " + -// "mutations)"); -// } -// } -// -// private static TransactionResult createResultFromContext(TransactionContext overall) { -// return new TransactionResult(overall.attempts(), -// overall.LOGGER, -// Duration.of(System.nanoTime() - overall.startTimeClient().toNanos(), ChronoUnit.NANOS), -// overall.transactionId(), -// overall.serialized()); -// } -// -// /** -// * Performs a single query transaction, with default configuration. -// * -// * @param statement the statement to execute. -// * @return a ReactiveSingleQueryTransactionResult -// */ -// @Stability.Uncommitted -// public Mono query(String statement) { -// return query(null, statement, SingleQueryTransactionConfigBuilder.create().build()); -// } -// -// /** -// * Performs a single query transaction, with a custom configuration. -// * -// * @param statement the statement to execute. -// * @param queryOptions configuration options. -// * @return a ReactiveSingleQueryTransactionResult -// */ -// @Stability.Uncommitted -// public Mono query(String statement, SingleQueryTransactionConfig queryOptions) { -// return query(null, statement, queryOptions); -// } -// -// /** -// * Performs a single query transaction, with a scope context and default configuration. -// * -// * @param statement the statement to execute. -// * @param scope the query will be executed in the context of this scope, so it can refer to a collection on this scope -// * rather than needed to provide the full keyspace. -// * @return a ReactiveSingleQueryTransactionResult -// */ -// @Stability.Uncommitted -// public Mono query(ReactiveScope scope, String statement) { -// return query(scope, statement, SingleQueryTransactionConfigBuilder.create().build()); -// } -// -// /** -// * Performs a single query transaction, with a scope context and custom configuration. -// * -// * @param statement the statement to execute. -// * @param scope the query will be executed in the context of this scope, so it can refer to a collection on this scope -// * rather than needed to provide the full keyspace. -// * @param queryOptions configuration options. -// * @return a ReactiveSingleQueryTransactionResult -// */ -// @Stability.Uncommitted -// public Mono query(ReactiveScope scope, String statement, SingleQueryTransactionConfig queryOptions) { -// return Mono.defer(() -> { -// AtomicReference queryResult = new AtomicReference<>(); -// return run((ctx) -> ctx.query(scope, statement, queryOptions.queryOptions(), true) -// .doOnNext(qr -> queryResult.set(qr)) -// .then(), queryOptions.convert()) -// .map(result -> new ReactiveSingleQueryTransactionResult(result.log(), queryResult.get())); -// }); -// } -// -// @Stability.Internal -// @Deprecated // Prefer setting TransactionConfigBuilder#testFactories now -// public void setAttemptContextFactory(AttemptContextFactory factory) { -// this.attemptContextFactory = factory; -// } -// public ReactiveTransactionAttemptContext newAttemptContextReactive(){ -// PerTransactionConfig perConfig = PerTransactionConfigBuilder.create().build(); -// MergedTransactionConfig merged = new MergedTransactionConfig(config, Optional.of(perConfig)); -// -// TransactionContext overall = new TransactionContext( -// cleanup().clusterData().cluster().environment().requestTracer(), -// cleanup().clusterData().cluster().environment().eventBus(), -// UUID.randomUUID().toString(), now(), Duration.ZERO, merged); -// -// String txnId = UUID.randomUUID().toString(); -// overall.LOGGER.info(configDebug(config, perConfig)); -// return createAttemptContext(overall, merged, txnId); -// } -// -//} diff --git a/src/main/java/com/example/demo/CouchbaseTransactionManager.pre-core b/src/main/java/com/example/demo/CouchbaseTransactionManager.pre-core deleted file mode 100644 index ab9d84087..000000000 --- a/src/main/java/com/example/demo/CouchbaseTransactionManager.pre-core +++ /dev/null @@ -1,201 +0,0 @@ -package com.example.demo; - -import java.util.concurrent.atomic.AtomicReference; - -import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; -import com.couchbase.client.java.transactions.TransactionAttemptContext; -import com.couchbase.client.java.transactions.AttemptContextReactiveAccessor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.data.couchbase.core.CouchbaseTemplate; -import org.springframework.data.couchbase.transaction.ClientSession; -import org.springframework.data.couchbase.transaction.ClientSessionImpl; -import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.TransactionException; -import org.springframework.transaction.support.AbstractPlatformTransactionManager; -import org.springframework.transaction.support.CallbackPreferringPlatformTransactionManager; -import org.springframework.transaction.support.DefaultTransactionStatus; -import org.springframework.transaction.support.ResourceHolderSupport; -import org.springframework.transaction.support.ResourceTransactionManager; -import org.springframework.transaction.support.SmartTransactionObject; -import org.springframework.transaction.support.TransactionCallback; -import org.springframework.transaction.support.TransactionSynchronizationManager; -import org.springframework.transaction.support.TransactionSynchronizationUtils; -import org.springframework.util.Assert; - -// todo gp why is there separate CouchbaseCallbackTransactionManager if this class also extends CallbackPreferringPlatformTransactionManager? -// todo gp there is another CouchbaseTransactionManager in another package, which is valid? -public class CouchbaseTransactionManager extends AbstractPlatformTransactionManager - implements DisposableBean, ResourceTransactionManager, CallbackPreferringPlatformTransactionManager { - - private static final Logger LOGGER = LoggerFactory.getLogger(CouchbaseTransactionManager.class); - - private final CouchbaseTemplate template; - - public CouchbaseTransactionManager(CouchbaseTemplate template) { - this.template = template; - } - - public CouchbaseTransactionalTemplate template() { - return new CouchbaseTransactionalTemplate(template); - } - - @Override - public T execute(TransactionDefinition definition, TransactionCallback callback) throws TransactionException { - final AtomicReference result = new AtomicReference<>(); - // todo gp like CouchbaseCallbackTransactionManager, it needs access to CouchbaseClientFactory here (Cluster) -// TransactionResult txnResult = transactions.run(attemptContext -> { -// -// if (TransactionSynchronizationManager.hasResource(template.getCouchbaseClientFactory())) { -// ((CouchbaseResourceHolder) TransactionSynchronizationManager -// .getResource(template.reactive().getCouchbaseClientFactory())) -// .setAttemptContext(attemptContext); -// } else { -// TransactionSynchronizationManager.bindResource( -// template.reactive().getCouchbaseClientFactory(), -// new CouchbaseResourceHolder(attemptContext) -// ); -// } -// -// try { -// // Since we are on a different thread now transparently, at least make sure -// // that the original method invocation is synchronized. -// synchronized (this) { -// result.set(callback.doInTransaction(null)); -// } -// } catch (RuntimeException e) { -// System.err.println("RuntimeException: "+e+" instanceof RuntimeException "+(e instanceof RuntimeException)); -// throw e; -// } catch (Throwable e) { -// System.err.println("RuntimeException: "+e+" instanceof "+(e instanceof Throwable)); -// throw new RuntimeException(e); -// } -// }); - -// LOGGER.debug("Completed Couchbase Transaction with Result: " + txnResult); - return result.get(); - } - - @Override - protected CouchbaseTransactionObject doGetTransaction() throws TransactionException { - CouchbaseResourceHolder resourceHolder = (CouchbaseResourceHolder) TransactionSynchronizationManager - .getResource(template.getCouchbaseClientFactory()); - return new CouchbaseTransactionObject(resourceHolder); - } - - @Override - protected boolean isExistingTransaction(Object transaction) throws TransactionException { - return extractTransaction(transaction).hasResourceHolder(); - } - - @Override - protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException { - LOGGER.debug("Beginning Couchbase Transaction with Definition {}", definition); - } - - @Override - protected void doCommit(DefaultTransactionStatus status) throws TransactionException { - LOGGER.debug("Committing Couchbase Transaction with status {}", status); - } - - @Override - protected void doRollback(DefaultTransactionStatus status) throws TransactionException { - LOGGER.warn("Rolling back Couchbase Transaction with status {}", status); - } - - @Override - protected void doCleanupAfterCompletion(Object transaction) { - LOGGER.trace("Performing cleanup of Couchbase Transaction {}", transaction); - } - - @Override - public void destroy() { - } - - @Override - public Object getResourceFactory() { - return template.getCouchbaseClientFactory(); - } - - private static CouchbaseTransactionObject extractTransaction(Object transaction) { - Assert.isInstanceOf(CouchbaseTransactionObject.class, transaction, - () -> String.format("Expected to find a %s but it turned out to be %s.", CouchbaseTransactionObject.class, - transaction.getClass())); - - return (CouchbaseTransactionObject) transaction; - } - - public static class CouchbaseResourceHolder extends ResourceHolderSupport { - - private volatile TransactionAttemptContext attemptContext; - private volatile ReactiveTransactionAttemptContext attemptContextReactive; - private volatile ClientSession session = new ClientSessionImpl(); - - public CouchbaseResourceHolder(TransactionAttemptContext attemptContext) { - this.attemptContext = attemptContext; - } - - public TransactionAttemptContext getAttemptContext() { - return attemptContext; - } - - public void setAttemptContext(TransactionAttemptContext attemptContext) { - this.attemptContext = attemptContext; - } - - public ReactiveTransactionAttemptContext getAttemptContextReactive() { - return attemptContext!= null ? AttemptContextReactiveAccessor.getACR(attemptContext) : attemptContextReactive; - } - public void setAttemptContextReactive(ReactiveTransactionAttemptContext attemptContextReactive) { - this.attemptContextReactive = attemptContextReactive; - } - - public ClientSession getSession() { - return session; - } - - public void setSession(ClientSession session){ - this.session = session; - } - - @Override - public String toString() { - return "CouchbaseResourceHolder{" + - "attemptContext=" + attemptContext + - '}'; - } - - } - - protected static class CouchbaseTransactionObject implements SmartTransactionObject { - - final CouchbaseResourceHolder resourceHolder; - - CouchbaseTransactionObject(CouchbaseResourceHolder resourceHolderIn) { - resourceHolder = resourceHolderIn; - } - - @Override - public boolean isRollbackOnly() { - return resourceHolder != null && resourceHolder.isRollbackOnly(); - } - - @Override - public void flush() { - TransactionSynchronizationUtils.triggerFlush(); - } - - public boolean hasResourceHolder() { - return resourceHolder != null; - } - - @Override - public String toString() { - return "CouchbaseTransactionObject{" + - "resourceHolder=" + resourceHolder + - '}'; - } - } - -} diff --git a/src/main/java/com/example/demo/CouchbaseTransactionalTemplate.java b/src/main/java/com/example/demo/CouchbaseTransactionalTemplate.java deleted file mode 100644 index c0098a9d8..000000000 --- a/src/main/java/com/example/demo/CouchbaseTransactionalTemplate.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.example.demo; - -import com.couchbase.client.core.transaction.CoreTransactionAttemptContext; -import com.couchbase.client.core.transaction.CoreTransactionGetResult; -import com.couchbase.client.java.codec.Transcoder; -import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; -import com.couchbase.client.java.transactions.TransactionAttemptContext; -import com.couchbase.client.java.transactions.TransactionGetResult; -import org.springframework.data.couchbase.core.CouchbaseTemplate; -import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate; -import org.springframework.data.couchbase.transaction.ReactiveCouchbaseResourceHolder; -import org.springframework.transaction.support.TransactionSynchronizationManager; - -import static com.couchbase.client.java.transactions.internal.ConverterUtil.makeCollectionIdentifier; - -public class CouchbaseTransactionalTemplate { - - private final CouchbaseTemplate template; - - public CouchbaseTransactionalTemplate(CouchbaseTemplate template) { - this.template = template; - } - - public SpringTransactionGetResult findById(String id, Class domainType) { - try { - CoreTransactionAttemptContext ctx = getContext(); - CoreTransactionGetResult getResult = ctx.get( makeCollectionIdentifier(template.getCouchbaseClientFactory().getDefaultCollection().async()) , id).block(); - - T t = template.support().decodeEntity(id, new String(getResult.contentAsBytes()), getResult.cas(), domainType, - null, null, null); - return new SpringTransactionGetResult<>(t, getResult); - } catch (Exception e) { - e.printStackTrace(); - throw e; - } - - } - - public void replaceById(CoreTransactionGetResult getResult, T entity) { - CoreTransactionAttemptContext ctx = getContext(); - Transcoder transCoder = template.getCouchbaseClientFactory().getCluster().environment().transcoder(); - Transcoder.EncodedValue encoded = transCoder.encode(template.support().encodeEntity(entity).export()); - ctx.replace(getResult, encoded.encoded()); - } - - private CoreTransactionAttemptContext getContext() { - ReactiveCouchbaseResourceHolder resource = (ReactiveCouchbaseResourceHolder) TransactionSynchronizationManager - .getResource(template.getCouchbaseClientFactory()); - CoreTransactionAttemptContext atr; - if (resource != null) { - atr = resource.getCore(); - } else { - ReactiveCouchbaseResourceHolder holder = (ReactiveCouchbaseResourceHolder) TransactionSynchronizationManager - .getResource(template.getCouchbaseClientFactory().getCluster()); - atr = holder.getCore(); - } - return atr; - } - - - public static ReactiveCouchbaseResourceHolder getSession(ReactiveCouchbaseTemplate template) { - ReactiveCouchbaseResourceHolder resource = (ReactiveCouchbaseResourceHolder) TransactionSynchronizationManager - .getResource(template.getCouchbaseClientFactory()); - return resource; - } - -} diff --git a/src/main/java/com/example/demo/SpringTransactionGetResult.java b/src/main/java/com/example/demo/SpringTransactionGetResult.java deleted file mode 100644 index 27ede4aaf..000000000 --- a/src/main/java/com/example/demo/SpringTransactionGetResult.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.example.demo; - -import com.couchbase.client.core.transaction.CoreTransactionGetResult; -import com.couchbase.client.java.transactions.TransactionGetResult; - -public class SpringTransactionGetResult { - - private final T value; - private final CoreTransactionGetResult inner; - - public SpringTransactionGetResult(T value, CoreTransactionGetResult inner) { - this.value = value; - this.inner = inner; - } - - public T getValue() { - return value; - } - - public CoreTransactionGetResult getInner() { - return inner; - } - - @Override - public String toString() { - return "SpringTransactionGetResult{" + - "value=" + value + - ", inner=" + inner + - '}'; - } -} diff --git a/src/main/java/org/springframework/data/couchbase/repository/TransactionMeta.java b/src/main/java/org/springframework/data/couchbase/repository/TransactionMeta.java deleted file mode 100644 index ae33ddefa..000000000 --- a/src/main/java/org/springframework/data/couchbase/repository/TransactionMeta.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2021 the original author or authors - * - * 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 org.springframework.data.couchbase.repository; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.data.annotation.QueryAnnotation; - -/** - * Indicates the field should hold key to lookup the TransactionGetResult and should NOT be considered part of the - * document. - * - * @author Michael Reiche - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.FIELD }) -@Documented -@QueryAnnotation -public @interface TransactionMeta { - - String value() default ""; - -} diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseCallbackTransactionManager.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseCallbackTransactionManager.java deleted file mode 100644 index 19ae0a921..000000000 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseCallbackTransactionManager.java +++ /dev/null @@ -1,296 +0,0 @@ -///* -// * Copyright 2021 the original author or authors -// * -// * 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 org.springframework.data.couchbase.transaction; -// -//import com.couchbase.client.core.error.transaction.TransactionOperationFailedException; -//import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; -//import com.couchbase.client.java.transactions.TransactionResult; -//import reactor.core.publisher.Mono; -// -//import java.time.Duration; -//import java.time.temporal.ChronoUnit; -//import java.util.concurrent.atomic.AtomicReference; -// -//import org.slf4j.Logger; -//import org.slf4j.LoggerFactory; -//import org.springframework.beans.factory.DisposableBean; -//import org.springframework.data.couchbase.CouchbaseClientFactory; -//import org.springframework.data.couchbase.ReactiveCouchbaseClientFactory; -//import org.springframework.data.couchbase.core.CouchbaseTemplate; -//import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate; -//import org.springframework.transaction.TransactionDefinition; -//import org.springframework.transaction.TransactionException; -//import org.springframework.transaction.reactive.TransactionContextManager; -//import org.springframework.transaction.reactive.TransactionSynchronizationManager; -//import org.springframework.transaction.support.AbstractPlatformTransactionManager; -//import org.springframework.transaction.support.CallbackPreferringPlatformTransactionManager; -//import org.springframework.transaction.support.DefaultTransactionStatus; -//import org.springframework.transaction.support.ResourceTransactionManager; -//import org.springframework.transaction.support.SmartTransactionObject; -//import org.springframework.transaction.support.TransactionCallback; -//import org.springframework.transaction.support.TransactionSynchronizationUtils; -//import org.springframework.util.Assert; -// -///** -// * Blocking TransactionManager -// * -// * @author Michael Nitschinger -// * @author Michael Reiche -// */ -// -//public class CouchbaseCallbackTransactionManager extends AbstractPlatformTransactionManager -// implements DisposableBean, ResourceTransactionManager, CallbackPreferringPlatformTransactionManager { -// -// private static final Logger LOGGER = LoggerFactory.getLogger(CouchbaseTransactionManager.class); -// -// private final CouchbaseTemplate template; -// private final ReactiveCouchbaseTemplate reactiveTemplate; -// private final ReactiveCouchbaseClientFactory reactiveCouchbaseClientFactory; -// private final CouchbaseClientFactory couchbaseClientFactory; -// -// private ReactiveCouchbaseTransactionManager.ReactiveCouchbaseTransactionObject transaction; -// -// public CouchbaseCallbackTransactionManager(CouchbaseTemplate template, ReactiveCouchbaseTemplate reactiveTemplate) { -// this.template = template; -// this.reactiveTemplate = reactiveTemplate; -// this.reactiveCouchbaseClientFactory = this.reactiveTemplate.getCouchbaseClientFactory(); -// this.couchbaseClientFactory = this.template.getCouchbaseClientFactory(); -// } -// -// public ReactiveCouchbaseTemplate template() { -// return reactiveTemplate; -// } -// -// private CouchbaseResourceHolder newResourceHolder(TransactionDefinition definition, ClientSessionOptions options, -// ReactiveTransactionAttemptContext atr) { -// -// CouchbaseClientFactory databaseFactory = template.getCouchbaseClientFactory(); -// -// CouchbaseResourceHolder resourceHolder = new CouchbaseResourceHolder( -// databaseFactory.getSession(options, atr), databaseFactory); -// return resourceHolder; -// } -// -// @Override -// public T execute(TransactionDefinition definition, TransactionCallback callback) throws TransactionException { -// final AtomicReference execResult = new AtomicReference<>(); -// AtomicReference startTime = new AtomicReference<>(0L); -// -// Mono txnResult = couchbaseClientFactory.getCluster().reactive().transactions().run(ctx -> { -// /* begin spring-data-couchbase transaction 1/2 */ -// ClientSession clientSession = reactiveCouchbaseClientFactory // couchbaseClientFactory -// .getSession(ClientSessionOptions.builder().causallyConsistent(true).build()) -// .block(); -// ReactiveCouchbaseResourceHolder reactiveResourceHolder = new ReactiveCouchbaseResourceHolder(clientSession, -// reactiveCouchbaseClientFactory); -// -// CouchbaseResourceHolder resourceHolder = new CouchbaseResourceHolder(clientSession, -// template.getCouchbaseClientFactory()); -// -// Mono sync = TransactionContextManager.currentContext() -// .map(TransactionSynchronizationManager::new) -// . flatMap(synchronizationManager -> { -// System.err.println("CallbackTransactionManager: " + this); -// System.err.println("bindResource: " + reactiveCouchbaseClientFactory.getCluster().block()); -// // todo gp not sure why we bind, unbind, bind again? -// // todo msr - to avoid the NotBound exception on unbind. Should use unbindIfPossible. -// synchronizationManager.bindResource(reactiveCouchbaseClientFactory.getCluster().block(), -// reactiveResourceHolder); -// org.springframework.transaction.support.TransactionSynchronizationManager -// .unbindResourceIfPossible(reactiveCouchbaseClientFactory.getCluster().block()); -// org.springframework.transaction.support.TransactionSynchronizationManager -// .bindResource(reactiveCouchbaseClientFactory.getCluster().block(), resourceHolder); -// ReactiveCouchbaseTransactionManager.ReactiveCouchbaseTransactionObject transaction = new ReactiveCouchbaseTransactionManager.ReactiveCouchbaseTransactionObject( -// reactiveResourceHolder); -// setTransaction(transaction); -// -// // todo gp experimenting with replacing the ClientSession, the ReactiveCouchbaseTransactionObject, -// // the resource holders etc., with just storing the TransactionAttemptContext. -// synchronizationManager.bindResource(ReactiveTransactionAttemptContext.class, ctx); -// -// /* end spring-data-couchbase transaction 1/2 */ -// -// // todo gp do we need TransactionSynchronizationManager.forCurrentTransaction()? as we already have synchronizationManager -// Mono result = TransactionSynchronizationManager.forCurrentTransaction().flatMap((sm) -> { -// // todo gp not sure why re-binding again? -// sm.unbindResourceIfPossible(reactiveCouchbaseClientFactory.getCluster().block()); -// sm.bindResource(reactiveCouchbaseClientFactory.getCluster().block(), -// reactiveResourceHolder); -// CouchbaseTransactionStatus status = new CouchbaseTransactionStatus(transaction, true, false, false, true, null, sm); -// prepareSynchronization(status, new CouchbaseTransactionDefinition()); -// // System.err.println("deferContextual.ctx : " + xxx); -// //Mono cxView = Mono.deferContextual(cx -> { System.err.println("CallbackTransactionManager.cx: "+cx); return Mono.just(cx);}); -// try { -// // Since we are on a different thread now transparently, at least make sure -// // that the original method invocation is synchronized. -// synchronized (this) { -// // todo gp this will execute the lambda, and so we likely don't want that to be inside a synchronized block -// execResult.set(callback.doInTransaction(status)); -// } -// } catch (RuntimeException e) { -// throw e; -// } catch (Throwable e) { -// throw new RuntimeException(e); -// } -// return Mono.empty(); -// }).contextWrite(TransactionContextManager.getOrCreateContext()) // this doesn't create a context on the desired publisher -// .contextWrite(TransactionContextManager.getOrCreateContextHolder()).then(); -// -// // todo gp this isn't part of the chain (no `result = result.onErrorResume...`) so isn't called -// // and presumably isn't needed? -//// result.onErrorResume(err -> { -//// AttemptContextReactiveAccessor.getLogger(ctx).info(ctx.attemptId(), -//// "caught exception '%s' in async, rethrowing", err); -//// return Mono.error(ctx.TransactionOperationFailedException.convertToOperationFailedIfNeeded(err, ctx)); -//// }).thenReturn(ctx); -// -// return result.then(Mono.just(synchronizationManager)); -// }); -// /* begin spring-data-couchbase transaction 2/2 */ // this doesn't create a context on the desired publisher -// return sync.contextWrite(TransactionContextManager.getOrCreateContext()) -// .contextWrite(TransactionContextManager.getOrCreateContextHolder()).then(); -// /* end spring-data-couchbase transaction 2/2 */ -// }).doOnSubscribe(v -> startTime.set(System.nanoTime())); -// -// txnResult.block(); -// return execResult.get(); // transactions.reactive().executeTransaction(merged,overall,ob).doOnNext(v->overall.span().finish()).doOnError(err->overall.span().failWith(err));}); -// -// } -// -// private void setTransaction(ReactiveCouchbaseTransactionManager.ReactiveCouchbaseTransactionObject transaction) { -// this.transaction = transaction; -// } -// -// @Override -// protected ReactiveCouchbaseTransactionManager.ReactiveCouchbaseTransactionObject doGetTransaction() -// throws TransactionException { -// /* -// CouchbaseResourceHolder resourceHolder = (CouchbaseResourceHolder) TransactionSynchronizationManager -// .getResource(template.getCouchbaseClientFactory()); -// return new CouchbaseTransactionManager.CouchbaseTransactionObject(resourceHolder); -// */ -// return (ReactiveCouchbaseTransactionManager.ReactiveCouchbaseTransactionObject) transaction; -// } -// -// @Override -// protected boolean isExistingTransaction(Object transaction) throws TransactionException { -// return extractTransaction(transaction).hasResourceHolder(); -// } -// -// @Override -// protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException { -// LOGGER.debug("Beginning Couchbase Transaction with Definition {}", definition); -// } -// -// @Override -// protected void doCommit(DefaultTransactionStatus status) throws TransactionException { -// LOGGER.debug("Committing Couchbase Transaction with status {}", status); -// } -// -// @Override -// protected void doRollback(DefaultTransactionStatus status) throws TransactionException { -// LOGGER.warn("Rolling back Couchbase Transaction with status {}", status); -// org.springframework.transaction.support.TransactionSynchronizationManager -// .unbindResource(reactiveCouchbaseClientFactory); -// } -// -// @Override -// protected void doCleanupAfterCompletion(Object transaction) { -// LOGGER.trace("Performing cleanup of Couchbase Transaction {}", transaction); -// org.springframework.transaction.support.TransactionSynchronizationManager -// .unbindResource(reactiveCouchbaseClientFactory); -// return; -// } -// -// @Override -// public void destroy() { -// } -// -// @Override -// public Object getResourceFactory() { -// return reactiveTemplate.getCouchbaseClientFactory(); -// } -// -// private static CouchbaseTransactionObject extractTransaction(Object transaction) { -// Assert.isInstanceOf(CouchbaseTransactionObject.class, transaction, -// () -> String.format("Expected to find a %s but it turned out to be %s.", CouchbaseTransactionObject.class, -// transaction.getClass())); -// -// return (CouchbaseTransactionObject) transaction; -// } -// /* -// public class CouchbaseResourceHolder extends ResourceHolderSupport { -// -// private volatile ReactiveTransactionAttemptContext attemptContext; -// //private volatile TransactionResultMap resultMap = new TransactionResultMap(template); -// -// public CouchbaseResourceHolder(ReactiveTransactionAttemptContext attemptContext) { -// this.attemptContext = attemptContext; -// } -// -// public ReactiveTransactionAttemptContext getAttemptContext() { -// return attemptContext; -// } -// -// public void setAttemptContext(ReactiveTransactionAttemptContext attemptContext) { -// this.attemptContext = attemptContext; -// } -// -// //public TransactionResultMap getTxResultMap() { -// // return resultMap; -// //} -// -// @Override -// public String toString() { -// return "CouchbaseResourceHolder{" + "attemptContext=" + attemptContext + "}"; -// } -// } -// -// */ -// -// protected static class CouchbaseTransactionObject implements SmartTransactionObject { -// -// private final CouchbaseResourceHolder resourceHolder; -// -// CouchbaseTransactionObject(CouchbaseResourceHolder resourceHolder) { -// this.resourceHolder = resourceHolder; -// } -// -// @Override -// public boolean isRollbackOnly() { -// return this.resourceHolder != null && this.resourceHolder.isRollbackOnly(); -// } -// -// @Override -// public void flush() { -// TransactionSynchronizationUtils.triggerFlush(); -// } -// -// public boolean hasResourceHolder() { -// return resourceHolder != null; -// } -// -// @Override -// public String toString() { -// return "CouchbaseTransactionObject{" + "resourceHolder=" + resourceHolder + '}'; -// } -// } -// -// private static Duration now() { -// return Duration.of(System.nanoTime(), ChronoUnit.NANOS); -// } -// -//} diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseResourceHolderx.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseResourceHolderx.java deleted file mode 100644 index a4ba04574..000000000 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseResourceHolderx.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2019-2021 the original author or authors. - * - * 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 org.springframework.data.couchbase.transaction; - -import org.springframework.data.couchbase.CouchbaseClientFactory; -import org.springframework.data.couchbase.core.CouchbaseTemplate; -import org.springframework.lang.Nullable; -import org.springframework.transaction.support.ResourceHolderSupport; - -import com.couchbase.client.core.transaction.CoreTransactionAttemptContext; - -/** - * MongoDB specific resource holder, wrapping a {@link CoreTransactionAttemptContext}. - * {@link ReactiveCouchbaseTransactionManager} binds instances of this class to the subscriber context. - *

- * Note: Intended for internal usage only. - * - * @author Mark Paluch - * @author Christoph Strobl - * @since 2.2 - * @see CouchbaseTransactionManager - * @see CouchbaseTemplate - */ -// todo gp understand why this is needed - can we not just hold ctx in Mono context? -public class CouchbaseResourceHolderx extends ResourceHolderSupport { - - private @Nullable CoreTransactionAttemptContext core; // which holds the atr - private CouchbaseClientFactory databaseFactory; - - /** - * Create a new {@link org.springframework.data.couchbase.transaction.ReactiveCouchbaseResourceHolder} for a given - * {@link CoreTransactionAttemptContext session}. - * - * @param core the associated {@link CoreTransactionAttemptContext}. Can be {@literal null}. - * @param databaseFactory the associated {@link CouchbaseClientFactory}. must not be {@literal null}. - */ - public CouchbaseResourceHolderx(@Nullable CoreTransactionAttemptContext core, CouchbaseClientFactory databaseFactory) { - - this.core = core; - this.databaseFactory = databaseFactory; - } - - /** - * @return the associated {@link CoreTransactionAttemptContext}. Can be {@literal null}. - */ - @Nullable - public CoreTransactionAttemptContext getCore() { - return core; - } - - /** - * @return the required associated {@link CoreTransactionAttemptContext}. - * @throws IllegalStateException if no session is associated. - */ - CoreTransactionAttemptContext getRequiredSession() { - - CoreTransactionAttemptContext session = getCore(); - - if (session == null) { - throw new IllegalStateException("No ClientSession associated"); - } - return session; - } - - /** - * @return the associated {@link CouchbaseClientFactory}. - */ - public CouchbaseClientFactory getDatabaseFactory() { - return databaseFactory; - } - - /** - * Set the {@link CoreTransactionAttemptContext} to guard. - * - * @param core can be {@literal null}. - */ - public void setCore(@Nullable CoreTransactionAttemptContext core) { - this.core = core; - } - - /** - * @return {@literal true} if session is not {@literal null}. - */ - boolean hasCore() { - return core != null; - } - - /** - * If the {@link org.springframework.data.couchbase.transaction.ReactiveCouchbaseResourceHolder} is {@link #hasCore()} - * not already associated} with a {@link CoreTransactionAttemptContext} the given value is - * {@link #setCore(CoreTransactionAttemptContext) set} and returned, otherwise the current bound session is returned. - * - * @param core - * @return - */ - @Nullable - public CoreTransactionAttemptContext setSessionIfAbsent(@Nullable CoreTransactionAttemptContext core) { - - if (!hasCore()) { - setCore(core); - } - - return core; - } - -} diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseSimpleTransactionManager.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseSimpleTransactionManager.java deleted file mode 100644 index 1947cb7a2..000000000 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseSimpleTransactionManager.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2018-2021 the original author or authors. - * - * 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 org.springframework.data.couchbase.transaction; - -import org.springframework.data.couchbase.CouchbaseClientFactory; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.TransactionException; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.TransactionSystemException; - -// todo gp experimenting with the simplest possible class, extending PlatformTransactionManager not AbstractPlatformTransactionManager -public class CouchbaseSimpleTransactionManager implements PlatformTransactionManager { - - private final CouchbaseClientFactory clientFactory; - - public CouchbaseSimpleTransactionManager(CouchbaseClientFactory clientFactory) { - this.clientFactory = clientFactory; - } - - @Override - public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException { - return null; - } - - @Override - public void commit(TransactionStatus status) throws TransactionException { - // todo gp what here - do we need to re-allow explicit commit? how to handle retries of this part? - } - - @Override - public void rollback(TransactionStatus status) throws TransactionException { - // todo gp same as commit() - } -} diff --git a/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseClientUtils.java b/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseClientUtils.java index 7f7de3736..1f429c6a0 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseClientUtils.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseClientUtils.java @@ -17,45 +17,6 @@ public class ReactiveCouchbaseClientUtils { - /** - * Check if the {@link ReactiveMongoDatabaseFactory} is actually bound to a - * {@link com.mongodb.reactivestreams.client.ClientSession} that has an active transaction, or if a - * {@link org.springframework.transaction.reactive.TransactionSynchronization} has been registered for the - * {@link ReactiveMongoDatabaseFactory resource} and if the associated - * {@link com.mongodb.reactivestreams.client.ClientSession} has an - * {@link com.mongodb.reactivestreams.client.ClientSession#hasActiveTransaction() active transaction}. - * - * @param databaseFactory the resource to check transactions for. Must not be {@literal null}. - * @return a {@link Mono} emitting {@literal true} if the factory has an ongoing transaction. - */ - public static Mono isTransactionActive(ReactiveCouchbaseClientFactory databaseFactory) { - - if (databaseFactory.isTransactionActive()) { - return Mono.just(true); - } - - return TransactionSynchronizationManager.forCurrentTransaction() // - .map(it -> { - - ReactiveCouchbaseResourceHolder holder = (ReactiveCouchbaseResourceHolder) it.getResource(databaseFactory); - return holder != null && holder.hasActiveTransaction(); - }) // - .onErrorResume(NoTransactionException.class, e -> Mono.just(false)); - } - - /** - * Obtain the default {@link MongoDatabase database} form the given {@link ReactiveMongoDatabaseFactory factory} using - * {@link SessionSynchronization#ON_ACTUAL_TRANSACTION native session synchronization}.
- * Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the subscriber - * {@link Context} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}. - * - * @param factory the {@link ReactiveMongoDatabaseFactory} to get the {@link MongoDatabase} from. - * @return the {@link MongoDatabase} that is potentially associated with a transactional {@link ClientSession}. - */ - public static Mono getDatabase(ReactiveCouchbaseClientFactory factory) { - return doGetCouchbaseCluster(null, factory, SessionSynchronization.ON_ACTUAL_TRANSACTION); - } - /** * Obtain the default {@link MongoDatabase database} form the given {@link ReactiveMongoDatabaseFactory factory}. *
diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTemplateTransactionIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTemplateTransactionIntegrationTests.java index 97c5f109d..4e6bb1ad9 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTemplateTransactionIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTemplateTransactionIntegrationTests.java @@ -20,7 +20,6 @@ import static org.springframework.data.couchbase.util.Util.assertInAnnotationTransaction; import com.couchbase.client.core.error.DocumentNotFoundException; -import com.example.demo.CouchbaseTransactionalTemplate; import lombok.AllArgsConstructor; import lombok.Data; From 7709c0b87da4d713267799afcdd51ef8a582e692 Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Mon, 30 May 2022 15:56:59 +0100 Subject: [PATCH 08/15] Removing version from CouchbaseDocument This change was done previously - it must have slipped back in a merge. --- .../data/couchbase/core/mapping/CouchbaseDocument.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseDocument.java b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseDocument.java index 02566f767..ead8146ed 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseDocument.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseDocument.java @@ -56,9 +56,6 @@ public class CouchbaseDocument implements CouchbaseStorable { */ private int expiration; - // todo gp - public long version; - /** * Creates a completely empty {@link CouchbaseDocument}. */ From ee9f837a48280c9e475a74b6d7a2c612763b6ac3 Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Mon, 30 May 2022 15:59:49 +0100 Subject: [PATCH 09/15] Adding and removing TODOs --- .../couchbase/core/ReactiveInsertByIdOperationSupport.java | 2 -- .../couchbase/core/ReactiveRemoveByIdOperationSupport.java | 2 -- .../data/couchbase/core/query/OptionsBuilder.java | 1 + .../transaction/ReactiveCouchbaseResourceHolder.java | 1 - .../couchbase/transaction/ReactiveTransactionsWrapper.java | 4 +--- .../data/couchbase/transaction/TransactionsWrapper.java | 2 +- .../CouchbaseTransactionalPropagationIntegrationTests.java | 2 -- .../CouchbaseTransactionalRepositoryIntegrationTests.java | 2 +- 8 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java index 8429c359f..7887d0b35 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java @@ -190,8 +190,6 @@ public InsertByIdInScope withDurability(final DurabilityLevel durabilityLevel durabilityLevel, expiry, txCtx, support); } - // todo gpx need to figure out how to handle options re transactions. E.g. many non-transactional insert options, - // like this, aren't supported @Override public InsertByIdInScope withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) { Assert.notNull(persistTo, "PersistTo must not be null."); diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java index 5d71a5799..b7e1cb8e6 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java @@ -94,9 +94,7 @@ public Mono one(final String id) { ReactiveCollection rc = clientFactory.withScope(pArgs.getScope()).getCollection(pArgs.getCollection()) .reactive(); Mono tmpl = template.doGetTemplate(); - final Mono removeResult; - // todo gpx convert to TransactionalSupport Mono allResult = tmpl.flatMap(tp -> tp.getCouchbaseClientFactory().getResourceHolderMono().flatMap(s -> { if (s.getCore() == null) { System.err.println("non-tx remove"); diff --git a/src/main/java/org/springframework/data/couchbase/core/query/OptionsBuilder.java b/src/main/java/org/springframework/data/couchbase/core/query/OptionsBuilder.java index aacc2a4f0..748d429eb 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/OptionsBuilder.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/OptionsBuilder.java @@ -98,6 +98,7 @@ public static TransactionQueryOptions buildTransactionQueryOptions(QueryOptions TransactionQueryOptions txOptions = TransactionQueryOptions.queryOptions(); JsonObject optsJson = getQueryOpts(built); + // todo gpx is this compatible with all forms of named and positional parameters? won't be compatible with JsonSerializer. maybe can put some support into SDK for (Map.Entry entry : optsJson.toMap().entrySet()) { txOptions.raw(entry.getKey(), entry.getValue()); } diff --git a/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseResourceHolder.java b/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseResourceHolder.java index 4e3d09d5c..e2c9ba353 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseResourceHolder.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseResourceHolder.java @@ -37,7 +37,6 @@ * @see ReactiveCouchbaseTransactionManager * @see ReactiveCouchbaseTemplate */ -// todo gp understand why this is needed public class ReactiveCouchbaseResourceHolder extends ResourceHolderSupport { private @Nullable CoreTransactionAttemptContext core; // which holds the atr diff --git a/src/main/java/org/springframework/data/couchbase/transaction/ReactiveTransactionsWrapper.java b/src/main/java/org/springframework/data/couchbase/transaction/ReactiveTransactionsWrapper.java index 79b89946c..9780c3f9e 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/ReactiveTransactionsWrapper.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/ReactiveTransactionsWrapper.java @@ -15,7 +15,6 @@ import com.couchbase.client.java.transactions.TransactionResult; import com.couchbase.client.java.transactions.config.TransactionOptions; -// todo gp needed now Transactions has gone? public class ReactiveTransactionsWrapper /* wraps ReactiveTransactions */ { ReactiveCouchbaseClientFactory reactiveCouchbaseClientFactory; @@ -34,11 +33,10 @@ public Mono run(Function run(Function> transactionLogic, TransactionOptions perConfig) { - // todo gp this is duplicating a lot of logic from the core loop, and is hopefully not needed. - // todo ^^^ I think I removed all the duplicate logic. Function> newTransactionLogic = (ctx) -> { ReactiveCouchbaseResourceHolder resourceHolder = reactiveCouchbaseClientFactory.getResourceHolder( TransactionOptions.transactionOptions(), AttemptContextReactiveAccessor.getCore(ctx)); + // todo gp let's DRY any TransactionSynchronizationManager code Mono sync = TransactionContextManager.currentContext() .map(TransactionSynchronizationManager::new).flatMap(synchronizationManager -> { synchronizationManager.bindResource(reactiveCouchbaseClientFactory.getCluster(), resourceHolder); diff --git a/src/main/java/org/springframework/data/couchbase/transaction/TransactionsWrapper.java b/src/main/java/org/springframework/data/couchbase/transaction/TransactionsWrapper.java index 68243b731..26e446429 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/TransactionsWrapper.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/TransactionsWrapper.java @@ -21,7 +21,6 @@ import com.couchbase.client.java.transactions.config.TransactionOptions; import com.couchbase.client.java.transactions.error.TransactionFailedException; -// todo gp needed now Transactions has gone? public class TransactionsWrapper /* wraps Transactions */ { CouchbaseClientFactory couchbaseClientFactory; @@ -77,6 +76,7 @@ public TransactionResult run(Consumer transactionLogi logger.debug(String.format("Started transaction for session %s.", debugString(resourceHolder.getCore()))); + // todo gp let's DRY any TransactionSynchronizationManager code TransactionSynchronizationManager.setActualTransactionActive(true); resourceHolder.setSynchronizedWithTransaction(true); TransactionSynchronizationManager.unbindResourceIfPossible(couchbaseClientFactory.getCluster()); diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalPropagationIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalPropagationIntegrationTests.java index 63cdc42a3..f6f9626ea 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalPropagationIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalPropagationIntegrationTests.java @@ -54,10 +54,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; -// todo gpx test repository methods in @Transactional // todo gpx test queries in @Transactional // todo gpx chekc what happens when try to do reactive @Transcational (unsupported by CallbackPreferring) -// todo gpx handle synchronization /** * Tests for the various propagation values allowed on @Transactional methods. diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalRepositoryIntegrationTests.java index 0aee620f7..b058eb294 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalRepositoryIntegrationTests.java @@ -106,7 +106,7 @@ public void save() { assertInTransaction(); // read your own write - // todo gpx this is failing because it's being executed non-transactionally, due to a bug somewhere + // todo gpx now understand why this was failing, but have concerns (see Slack) about UX User user = operations.findById(User.class).one(id); assertNotNull(user); From 8b200185daa07b6b2c83d2f98d6c398b29320774 Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Mon, 30 May 2022 15:59:49 +0100 Subject: [PATCH 10/15] Adding and removing TODOs --- .../SimpleCouchbaseClientFactory.java | 5 +-- .../AbstractCouchbaseConfiguration.java | 3 -- .../repository/TransactionResult.java | 2 +- .../CouchbaseTransactionalOperator.java | 2 - ...basePersonTransactionIntegrationTests.java | 41 +------------------ ...TransactionalTemplateIntegrationTests.java | 22 ++++------ .../util/ClusterAwareIntegrationTests.java | 2 +- 7 files changed, 11 insertions(+), 66 deletions(-) diff --git a/src/main/java/org/springframework/data/couchbase/SimpleCouchbaseClientFactory.java b/src/main/java/org/springframework/data/couchbase/SimpleCouchbaseClientFactory.java index f6d702447..4f10af8b9 100644 --- a/src/main/java/org/springframework/data/couchbase/SimpleCouchbaseClientFactory.java +++ b/src/main/java/org/springframework/data/couchbase/SimpleCouchbaseClientFactory.java @@ -59,10 +59,7 @@ public SimpleCouchbaseClientFactory(final String connectionString, final Authent public SimpleCouchbaseClientFactory(final String connectionString, final Authenticator authenticator, final String bucketName, final String scopeName) { - this(new OwnedSupplier<>(Cluster.connect(connectionString, ClusterOptions.clusterOptions(authenticator) - // todo gp disabling cleanupLostAttempts to simplify output during development - .environment(env -> env.transactionsConfig( - TransactionsConfig.cleanupConfig(TransactionsCleanupConfig.cleanupLostAttempts(false)))))), + this(new OwnedSupplier<>(Cluster.connect(connectionString, ClusterOptions.clusterOptions(authenticator))), bucketName, scopeName); } diff --git a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java index 80f4e72b9..d031483c4 100644 --- a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java +++ b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java @@ -168,9 +168,6 @@ public ClusterEnvironment couchbaseClusterEnvironment() { throw new CouchbaseException("non-shadowed Jackson not present"); } builder.jsonSerializer(JacksonJsonSerializer.create(couchbaseObjectMapper())); - // todo gp only suitable for tests - TransactionsConfig.cleanupConfig(TransactionsCleanupConfig.cleanupLostAttempts(false)); - builder.transactionsConfig(transactionsConfig()); configureEnvironment(builder); return builder.build(); } diff --git a/src/main/java/org/springframework/data/couchbase/repository/TransactionResult.java b/src/main/java/org/springframework/data/couchbase/repository/TransactionResult.java index d2236d520..5413ac5e9 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/TransactionResult.java +++ b/src/main/java/org/springframework/data/couchbase/repository/TransactionResult.java @@ -28,7 +28,7 @@ * * @author Michael Reiche */ -// todo gp can we give this a different name since there is an existing TransactionResult +// todo gp I think this is no longer used - can we remove? @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD }) @Documented diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionalOperator.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionalOperator.java index c64cc47a8..b02215ab6 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionalOperator.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionalOperator.java @@ -89,8 +89,6 @@ public Mono apply(CouchbaseTransactionalOperator couchbaseTransactionalOpe */ public Mono reactive(Function> transactionLogic, boolean commit) { -// // todo gp this needs access to a Cluster -// return Mono.empty(); return ((ReactiveCouchbaseTransactionManager) transactionManager).getDatabaseFactory().getCluster().reactive().transactions().run(ctx -> { setAttemptContextReactive(ctx); // for getTxOp().getCtx() in Reactive*OperationSupport // for transactional(), transactionDefinition.setAtr(ctx) is called at the beginning of that method diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionIntegrationTests.java index 2748ac525..5947d1592 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionIntegrationTests.java @@ -255,7 +255,7 @@ public void emitMultipleElementsDuringTransaction() { @Test public void errorAfterTxShouldNotAffectPreviousStep() { Person p = personService.savePerson(new Person(null, "Walter", "White")); - // todo gp user shouldn't be getting exposed to TransactionOperationFailedException + // todo gp user shouldn't be getting exposed to TransactionOperationFailedException. This is happening as TransactionOperator does not support the automatic retries and error handling we depend on. As argued on Slack, we cannot support it because of this. // todo mr /* TransactionOperationFailedException {cause:com.couchbase.client.core.error.DocumentExistsException, retry:false, autoRollback:true, raise:TRANSACTION_FAILED} @@ -662,45 +662,6 @@ public String toString() { } } - // todo gp disabled while trying to get alternative method of CouchbaseCallbackTransactionManager working - // @Configuration(proxyBeanMethods = false) - // @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - // static class TransactionInterception { - // - // @Bean - // @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - // public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource, - // CouchbaseTransactionManager txManager) { - // TransactionInterceptor interceptor = new CouchbaseTransactionInterceptor(); - // interceptor.setTransactionAttributeSource(transactionAttributeSource); - // if (txManager != null) { - // interceptor.setTransactionManager(txManager); - // } - // return interceptor; - // } - // - // @Bean - // @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - // public TransactionAttributeSource transactionAttributeSource() { - // return new AnnotationTransactionAttributeSource(); - // } - // - // @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME) - // @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - // public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor( - // TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) { - // - // BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor(); - // advisor.setTransactionAttributeSource(transactionAttributeSource); - // advisor.setAdvice(transactionInterceptor); - // // if (this.enableTx != null) { - // // advisor.setOrder(this.enableTx.getNumber("order")); - // // } - // return advisor; - // } - // - // } - @Service @Component @EnableTransactionManagement diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalTemplateIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalTemplateIntegrationTests.java index 202e63d3e..ec58e30e6 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalTemplateIntegrationTests.java @@ -270,28 +270,25 @@ public void concurrentTxns() { } // todo gpx investigate how @Transactional @Rollback/@Commit interacts with us - // todo gpx how to provide per-transaction options? - @Disabled("taking too long - must fix") @DisplayName("Create a Person outside a @Transactional block, modify it, and then replace that person in the @Transactional. The transaction will retry until timeout.") @Test public void replacePerson() { Person person = new Person(1, "Walter", "White"); operations.insertById(Person.class).one(person); - System.out.printf("insert CAS: %s%n", person.getVersion()); - Person refetched = operations.findById(Person.class).one(person.getId().toString()); operations.replaceById(Person.class).one(refetched); - System.out.printf("replace CAS: %s%n", refetched.getVersion()); - assertNotEquals(person.getVersion(), refetched.getVersion()); AtomicInteger tryCount = new AtomicInteger(0); - // todo gpx this is raising incorrect error: - // com.couchbase.client.core.retry.reactor.RetryExhaustedException: com.couchbase.client.core.error.transaction.RetryTransactionException: User request to retry transaction - personService.replace(person, tryCount); + try { + personService.replace(person, tryCount); + fail(); + } + catch (TransactionFailedException ignored) { + } } @@ -442,12 +439,7 @@ public Person declarativeFindReplaceTwicePersonCallback(Person person, AtomicInt return personOperations.replaceById(Person.class).one(pUpdated); } - // todo gpx how do we make COUCHBASE_SIMPLE_CALLBACK_TRANSACTION_MANAGER the default so user only has to specify @Transactional, without the transactionManager? - // todo mr - // todo if there is exactly one bean of type ‘org.springframework.transaction.TransactionManager’. - // todo It’s also possible to put the @Transaction annotation on the class (instead of each method). - // todo see TransactionAspectSupport.determineTransactionManager(TransactionAttribute) - @Transactional(transactionManager = BeanNames.COUCHBASE_SIMPLE_CALLBACK_TRANSACTION_MANAGER) + @Transactional(transactionManager = BeanNames.COUCHBASE_SIMPLE_CALLBACK_TRANSACTION_MANAGER, timeout = 2) public Person replace(Person person, AtomicInteger tryCount) { assertInAnnotationTransaction(true); tryCount.incrementAndGet(); diff --git a/src/test/java/org/springframework/data/couchbase/util/ClusterAwareIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/util/ClusterAwareIntegrationTests.java index 5deab4ba9..842cae6cd 100644 --- a/src/test/java/org/springframework/data/couchbase/util/ClusterAwareIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/util/ClusterAwareIntegrationTests.java @@ -64,7 +64,7 @@ public abstract class ClusterAwareIntegrationTests { @BeforeAll static void setup(TestClusterConfig config) { testClusterConfig = config; - // todo gp disabling cleanupLostAttempts to simplify output during development + // Disabling cleanupLostAttempts to simplify output during development ClusterEnvironment env = ClusterEnvironment.builder() .transactionsConfig(TransactionsConfig.cleanupConfig(TransactionsCleanupConfig.cleanupLostAttempts(false))) .build(); From 68dcca3997b2c845ed8b58070fcd3cfdad280d5d Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Mon, 30 May 2022 16:13:56 +0100 Subject: [PATCH 11/15] DRYing CouchbaseTransactionalPropagationIntegrationTests --- ...seTransactionalPropagationIntegrationTests.java | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalPropagationIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalPropagationIntegrationTests.java index f6f9626ea..acf439649 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalPropagationIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalPropagationIntegrationTests.java @@ -53,6 +53,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.springframework.data.couchbase.transactions.util.TransactionTestUtil.assertInTransaction; +import static org.springframework.data.couchbase.transactions.util.TransactionTestUtil.assertNotInTransaction; // todo gpx test queries in @Transactional // todo gpx chekc what happens when try to do reactive @Transcational (unsupported by CallbackPreferring) @@ -323,18 +325,6 @@ public void callDefaultThatCallsNested() { assertNull(operations.findById(Person.class).one(id1.toString())); } - void assertInTransaction() { - assertTrue(org.springframework.transaction.support.TransactionSynchronizationManager.isActualTransactionActive()); - } - - void assertNotInTransaction() { - try { - assertFalse(org.springframework.transaction.support.TransactionSynchronizationManager.isActualTransactionActive()); - } - catch (NoTransactionException ignored) { - } - } - @Service @Component @EnableTransactionManagement From e8ab027e1603736a7f43b918181e8dbe069c9c92 Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Mon, 30 May 2022 16:19:47 +0100 Subject: [PATCH 12/15] Check propagation tests retry as expected --- ...nsactionalPropagationIntegrationTests.java | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalPropagationIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalPropagationIntegrationTests.java index acf439649..08fd96c4e 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalPropagationIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalPropagationIntegrationTests.java @@ -16,6 +16,7 @@ package org.springframework.data.couchbase.transactions; +import com.couchbase.client.core.error.transaction.RetryTransactionException; import com.couchbase.client.java.transactions.error.TransactionFailedException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -46,8 +47,10 @@ import org.springframework.transaction.annotation.Transactional; import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; @@ -168,8 +171,6 @@ public void callNested() { }); } - // todo gp check retries - @DisplayName("Call @Transactional that calls @Transactional(propagation = DEFAULT) - succeeds, continues existing") @Test public void callDefaultThatCallsDefault() { @@ -186,7 +187,7 @@ public void callDefaultThatCallsDefault() { }); }); - // Validate everyting committed + // Validate everything committed assertNotNull(operations.findById(Person.class).one(id1.toString())); assertNotNull(operations.findById(Person.class).one(id2.toString())); } @@ -207,7 +208,7 @@ public void callDefaultThatCallsRequired() { }); }); - // Validate everyting committed + // Validate everything committed assertNotNull(operations.findById(Person.class).one(id1.toString())); assertNotNull(operations.findById(Person.class).one(id2.toString())); } @@ -228,7 +229,7 @@ public void callDefaultThatCallsMandatory() { }); }); - // Validate everyting committed + // Validate everything committed assertNotNull(operations.findById(Person.class).one(id1.toString())); assertNotNull(operations.findById(Person.class).one(id2.toString())); } @@ -325,6 +326,32 @@ public void callDefaultThatCallsNested() { assertNull(operations.findById(Person.class).one(id1.toString())); } + @DisplayName("Call @Transactional that calls @Transactional(propagation = DEFAULT) - check retries act correct") + @Test + public void callDefaultThatCallsDefaultRetries() { + UUID id1 = UUID.randomUUID(); + UUID id2 = UUID.randomUUID(); + AtomicInteger attempts = new AtomicInteger(); + + personService.propagationDefault(ops -> { + ops.insertById(Person.class).one(new Person(id1, "Ada", "Lovelace")); + + personService.propagationDefault(ops2 -> { + ops2.insertById(Person.class).one(new Person(id2, "Grace", "Hopper")); + assertInTransaction(); + + if (attempts.incrementAndGet() < 3) { + throw new RetryTransactionException(); + } + }); + }); + + // Validate everything committed + assertNotNull(operations.findById(Person.class).one(id1.toString())); + assertNotNull(operations.findById(Person.class).one(id2.toString())); + assertEquals(3, attempts.get()); + } + @Service @Component @EnableTransactionManagement From ebb1cc7b053bfc55a77de64c2f79cdaf92cb3846 Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Mon, 30 May 2022 16:22:17 +0100 Subject: [PATCH 13/15] Tidy up PersonWithoutVersion to the minimum required --- .../domain/PersonWithoutVersion.java | 172 ++---------------- ...TransactionalTemplateIntegrationTests.java | 5 +- 2 files changed, 14 insertions(+), 163 deletions(-) diff --git a/src/test/java/org/springframework/data/couchbase/domain/PersonWithoutVersion.java b/src/test/java/org/springframework/data/couchbase/domain/PersonWithoutVersion.java index 4aa46ed8e..d60c75621 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/PersonWithoutVersion.java +++ b/src/test/java/org/springframework/data/couchbase/domain/PersonWithoutVersion.java @@ -15,176 +15,26 @@ */ package org.springframework.data.couchbase.domain; -import org.springframework.data.annotation.CreatedBy; -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.annotation.LastModifiedBy; -import org.springframework.data.annotation.LastModifiedDate; -import org.springframework.data.annotation.Transient; -import org.springframework.data.annotation.Version; -import org.springframework.data.couchbase.core.mapping.Document; -import org.springframework.data.couchbase.core.mapping.Field; -import org.springframework.data.couchbase.repository.TransactionResult; -import org.springframework.data.domain.Persistable; -import org.springframework.lang.Nullable; - import java.util.Optional; import java.util.UUID; -// todo gpx: lame to C&P the entire Person, but struggling to get a simpler entity working +import org.springframework.data.couchbase.core.mapping.Document; +import org.springframework.lang.Nullable; + @Document -public class PersonWithoutVersion extends AbstractEntity implements Persistable { +public class PersonWithoutVersion extends AbstractEntity +{ Optional firstname; - @Nullable Optional lastname; - - @CreatedBy private String creator; - - @LastModifiedBy private String lastModifiedBy; - - @LastModifiedDate private long lastModification; - - @CreatedDate private long creationDate; - - @Nullable @Field("nickname") private String middlename; - @Nullable @Field(name = "prefix") private String salutation; - - private Address address; - - // Required for use in transactions - @TransactionResult private Integer txResultHolder; - @Transient private boolean isNew; - + Optional lastname; - public PersonWithoutVersion() {} - - public PersonWithoutVersion(String firstname, String lastname) { - this(); - setFirstname(firstname); - setLastname(lastname); - setMiddlename("Nick"); - isNew(true); - } - - public PersonWithoutVersion(int id, String firstname, String lastname) { - this(firstname, lastname); - setId(new UUID(id, id)); + public PersonWithoutVersion() { + firstname = Optional.empty(); + lastname = Optional.empty(); } public PersonWithoutVersion(UUID id, String firstname, String lastname) { - this(firstname, lastname); + this.firstname = Optional.of(firstname); + this.lastname = Optional.of(lastname); setId(id); } - - static String optional(String name, Optional obj) { - if (obj != null) { - if (obj.isPresent()) { - return (" " + name + ": '" + obj.get() + "'"); - } else { - return " " + name + ": null"; - } - } - return ""; - } - - public String getFirstname() { - return firstname.get(); - } - - public void setFirstname(String firstname) { - this.firstname = firstname == null ? null : (Optional.ofNullable(firstname.equals("") ? null : firstname)); - } - - public void setFirstname(Optional firstname) { - this.firstname = firstname; - } - - public String getLastname() { - return lastname.get(); - } - - public void setLastname(String lastname) { - this.lastname = lastname == null ? null : (Optional.ofNullable(lastname.equals("") ? null : lastname)); - } - - public void setLastname(Optional lastname) { - this.lastname = lastname; - } - - public String getMiddlename() { - return middlename; - } - - public String getSalutation() { - return salutation; - } - - public void setMiddlename(String middlename) { - this.middlename = middlename; - } - - public void setSalutation(String salutation) { - this.salutation = salutation; - } - - public Address getAddress() { - return address; - } - - public void setAddress(Address address) { - this.address = address; - } - - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("Person : {\n"); - sb.append(" id : " + getId()); - sb.append(optional(", firstname", firstname)); - sb.append(optional(", lastname", lastname)); - if (middlename != null) - sb.append(", middlename : '" + middlename + "'"); - if (creator != null) { - sb.append(", creator : " + creator); - } - if (creationDate != 0) { - sb.append(", creationDate : " + creationDate); - } - if (lastModifiedBy != null) { - sb.append(", lastModifiedBy : " + lastModifiedBy); - } - if (lastModification != 0) { - sb.append(", lastModification : " + lastModification); - } - if (getAddress() != null) { - sb.append(", address : " + getAddress().toString()); - } - sb.append("\n}"); - return sb.toString(); - } - - public PersonWithoutVersion withFirstName(String firstName) { - PersonWithoutVersion p = new PersonWithoutVersion(this.getId(), firstName, this.getLastname()); - p.txResultHolder = this.txResultHolder; - return p; - } - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!super.equals(obj)) { - return false; - } - - PersonWithoutVersion that = (PersonWithoutVersion) obj; - return this.getId().equals(that.getId()) && this.getFirstname().equals(that.getFirstname()) - && this.getLastname().equals(that.getLastname()) && this.getMiddlename().equals(that.getMiddlename()); - } - - @Override - public boolean isNew() { - return isNew; - } - - public void isNew(boolean isNew){ - this.isNew = isNew; - } } diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalTemplateIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalTemplateIntegrationTests.java index ec58e30e6..bc176c7ce 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalTemplateIntegrationTests.java @@ -47,6 +47,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; @@ -295,7 +296,7 @@ public void replacePerson() { @DisplayName("Entity must have CAS field during replace") @Test public void replaceEntityWithoutCas() { - PersonWithoutVersion person = new PersonWithoutVersion(1, "Walter", "White"); + PersonWithoutVersion person = new PersonWithoutVersion(UUID.randomUUID(), "Walter", "White"); operations.insertById(PersonWithoutVersion.class).one(person); try { personService.replaceEntityWithoutVersion(person.getId().toString()); @@ -326,7 +327,7 @@ public void replaceEntityWithCasZero() { @DisplayName("Entity must have CAS field during remove") @Test public void removeEntityWithoutCas() { - PersonWithoutVersion person = new PersonWithoutVersion(1, "Walter", "White"); + PersonWithoutVersion person = new PersonWithoutVersion(UUID.randomUUID(), "Walter", "White"); operations.insertById(PersonWithoutVersion.class).one(person); try { personService.removeEntityWithoutVersion(person.getId().toString()); From 6ed3bad06c6832514db7a48a932d8f7ea11aff22 Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Mon, 30 May 2022 18:05:37 +0100 Subject: [PATCH 14/15] Removing unused code This should all be non-destructive. Just removing code IntelliJ declares unused. Intent is to make it easier to figure out how the CoreTransactionAttemptContext TLS is working. --- .../AttemptContextReactiveAccessor.java | 11 --- .../couchbase/CouchbaseClientFactory.java | 4 - .../ReactiveCouchbaseClientFactory.java | 6 -- .../SimpleReactiveCouchbaseClientFactory.java | 14 ---- .../AbstractCouchbaseConfiguration.java | 6 -- .../data/couchbase/config/BeanNames.java | 2 - .../core/AbstractTemplateSupport.java | 10 --- .../couchbase/core/CouchbaseTemplate.java | 27 ------ .../core/NonReactiveSupportWrapper.java | 5 -- .../core/ReactiveCouchbaseTemplate.java | 83 ------------------- .../ReactiveCouchbaseTemplateSupport.java | 4 - .../ReactiveFindByQueryOperationSupport.java | 1 + .../core/ReactiveSessionCallback.java | 45 ---------- .../core/ReactiveTemplateSupport.java | 4 - .../CouchbaseTransactionDefinition.java | 19 ----- .../CouchbaseTransactionManager.java | 5 -- .../CouchbaseTransactionStatus.java | 2 - .../CouchbaseTransactionalOperator.java | 22 ----- .../ReactiveCouchbaseClientUtils.java | 69 +-------------- .../ReactiveCouchbaseResourceHolder.java | 43 ---------- .../data/couchbase/transactions/Config.java | 38 --------- 21 files changed, 2 insertions(+), 418 deletions(-) delete mode 100644 src/main/java/org/springframework/data/couchbase/core/ReactiveSessionCallback.java diff --git a/src/main/java/com/couchbase/client/java/transactions/AttemptContextReactiveAccessor.java b/src/main/java/com/couchbase/client/java/transactions/AttemptContextReactiveAccessor.java index a094d20ce..7c631eeca 100644 --- a/src/main/java/com/couchbase/client/java/transactions/AttemptContextReactiveAccessor.java +++ b/src/main/java/com/couchbase/client/java/transactions/AttemptContextReactiveAccessor.java @@ -66,11 +66,6 @@ public static ReactiveTransactionAttemptContext reactive(TransactionAttemptConte return new ReactiveTransactionAttemptContext(getCore(atr), serializer); } - - public static CoreTransactionLogger getLogger(ReactiveTransactionAttemptContext attemptContextReactive) { - return attemptContextReactive.logger(); - } - public static CoreTransactionLogger getLogger(TransactionAttemptContext attemptContextReactive) { return attemptContextReactive.logger(); } @@ -105,12 +100,6 @@ public static CoreTransactionAttemptContext newCoreTranactionAttemptContext(Reac return coreTransactionAttemptContext; } - public static ReactiveTransactionAttemptContext from(CoreTransactionAttemptContext coreTransactionAttemptContext, - JsonSerializer serializer) { - TransactionAttemptContext tac = new TransactionAttemptContext(coreTransactionAttemptContext, serializer); - return reactive(tac); - } - public static CoreTransactionAttemptContext getCore(ReactiveTransactionAttemptContext atr) { CoreTransactionAttemptContext coreTransactionsReactive; try { diff --git a/src/main/java/org/springframework/data/couchbase/CouchbaseClientFactory.java b/src/main/java/org/springframework/data/couchbase/CouchbaseClientFactory.java index 70a6c9227..4f82a74af 100644 --- a/src/main/java/org/springframework/data/couchbase/CouchbaseClientFactory.java +++ b/src/main/java/org/springframework/data/couchbase/CouchbaseClientFactory.java @@ -78,8 +78,4 @@ public interface CouchbaseClientFactory extends Closeable { CoreTransactionAttemptContext getCore(TransactionOptions options, CoreTransactionAttemptContext atr); - - //CouchbaseClientFactory with(CouchbaseTransactionalOperator txOp); - - //CouchbaseTransactionalOperator getTransactionalOperator(); } diff --git a/src/main/java/org/springframework/data/couchbase/ReactiveCouchbaseClientFactory.java b/src/main/java/org/springframework/data/couchbase/ReactiveCouchbaseClientFactory.java index ca2be1c05..d89ebf675 100644 --- a/src/main/java/org/springframework/data/couchbase/ReactiveCouchbaseClientFactory.java +++ b/src/main/java/org/springframework/data/couchbase/ReactiveCouchbaseClientFactory.java @@ -103,11 +103,5 @@ public interface ReactiveCouchbaseClientFactory /*extends CodecRegistryProvider* */ ReactiveCouchbaseClientFactory with(CouchbaseTransactionalOperator txOp); - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.ReactiveMongoDatabaseFactory#isTransactionActive() - */ - boolean isTransactionActive(); - CouchbaseTransactionalOperator getTransactionalOperator(); } diff --git a/src/main/java/org/springframework/data/couchbase/SimpleReactiveCouchbaseClientFactory.java b/src/main/java/org/springframework/data/couchbase/SimpleReactiveCouchbaseClientFactory.java index 7a3402a10..9c0efae74 100644 --- a/src/main/java/org/springframework/data/couchbase/SimpleReactiveCouchbaseClientFactory.java +++ b/src/main/java/org/springframework/data/couchbase/SimpleReactiveCouchbaseClientFactory.java @@ -145,11 +145,6 @@ public ReactiveCouchbaseClientFactory withCore(ReactiveCouchbaseResourceHolder h return new CoreTransactionAttemptContextBoundCouchbaseClientFactory(holder, this, transactions); } - @Override - public boolean isTransactionActive() { - return false; - } - @Override public CouchbaseTransactionalOperator getTransactionalOperator() { return transactionalOperator; @@ -281,15 +276,6 @@ public ReactiveCouchbaseClientFactory withCore(ReactiveCouchbaseResourceHolder c return delegate.withCore(core); } - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.ReactiveMongoDatabaseFactory#isTransactionActive() - */ - @Override - public boolean isTransactionActive() { - return transactionResources != null && transactionResources.hasActiveTransaction(); - } - @Override public CouchbaseTransactionalOperator getTransactionalOperator() { return delegate.getTransactionalOperator(); diff --git a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java index d031483c4..b98dc2d46 100644 --- a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java +++ b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java @@ -18,7 +18,6 @@ import static com.couchbase.client.java.ClusterOptions.clusterOptions; import static org.springframework.data.couchbase.config.BeanNames.COUCHBASE_MAPPING_CONTEXT; -import static org.springframework.data.couchbase.config.BeanNames.COUCHBASE_TRANSACTIONS; import java.time.Duration; import java.util.Collections; @@ -375,11 +374,6 @@ public TransactionOptions transactionsOptions(){ return TransactionOptions.transactionOptions(); } - // todo gpx transactions config is now done in standard ClusterConfig - so I think we don't want a separate bean? - public TransactionsConfig.Builder transactionsConfig(){ - return TransactionsConfig.builder().durabilityLevel(DurabilityLevel.NONE).timeout(Duration.ofMinutes(20));// for testing - } - /** * Blocking Transaction Manager * diff --git a/src/main/java/org/springframework/data/couchbase/config/BeanNames.java b/src/main/java/org/springframework/data/couchbase/config/BeanNames.java index 100c841e5..41bccb95a 100644 --- a/src/main/java/org/springframework/data/couchbase/config/BeanNames.java +++ b/src/main/java/org/springframework/data/couchbase/config/BeanNames.java @@ -34,8 +34,6 @@ public class BeanNames { public static final String COUCHBASE_CUSTOM_CONVERSIONS = "couchbaseCustomConversions"; - public static final String COUCHBASE_TRANSACTIONS = "couchbaseTransactions"; - /** * The name for the bean that stores custom mapping between repositories and their backing couchbaseOperations. */ diff --git a/src/main/java/org/springframework/data/couchbase/core/AbstractTemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/AbstractTemplateSupport.java index 8164f2cd2..685d7a8a0 100644 --- a/src/main/java/org/springframework/data/couchbase/core/AbstractTemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/AbstractTemplateSupport.java @@ -192,16 +192,6 @@ ConvertingPropertyAccessor getPropertyAccessor(final T source) { return new ConvertingPropertyAccessor<>(accessor, converter.getConversionService()); } - public Integer getTxResultKey(T source) { - final CouchbasePersistentEntity persistentEntity = mappingContext.getRequiredPersistentEntity(source.getClass()); - final CouchbasePersistentProperty transactionResultProperty = persistentEntity.transactionResultProperty(); - if (transactionResultProperty == null) { - throw new CouchbaseException("the entity class " + source.getClass() - + " does not have a property required for transactions:\n\t@TransactionResult TransactionResultHolder txResultHolder"); - } - return getPropertyAccessor(source).getProperty(transactionResultProperty, Integer.class); - } - public void maybeEmitEvent(CouchbaseMappingEvent event) { if (canPublishEvent()) { try { diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java index 322bf4f73..ac2c1c675 100644 --- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java @@ -56,7 +56,6 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationContex private final ReactiveCouchbaseTemplate reactiveCouchbaseTemplate; private final QueryScanConsistency scanConsistency; private @Nullable CouchbasePersistentEntityIndexCreator indexCreator; - private CouchbaseTransactionalOperator couchbaseTransactionalOperator; public CouchbaseTemplate(final CouchbaseClientFactory clientFactory, final ReactiveCouchbaseClientFactory reactiveCouchbaseClientFactory, final CouchbaseConverter converter) { @@ -233,30 +232,4 @@ private void prepareIndexCreator(final ApplicationContext context) { public TemplateSupport support() { return templateSupport; } - - public CouchbaseTemplate with(CouchbaseTransactionalOperator couchbaseTransactionalOperator) { - this.couchbaseTransactionalOperator = couchbaseTransactionalOperator; - return this; - } - - /** - * Get the TransactionalOperator from
- * 1. The template.clientFactory
- * 2. The template.threadLocal
- * 3. otherwise null
- * This can be overriden in the operation method by
- * 1. repository.withCollection() - *//* - private CouchbaseStuffHandle getTransactionalOperator() { - if (this.getCouchbaseClientFactory().getTransactionalOperator() != null) { - return this.getCouchbaseClientFactory().getTransactionalOperator(); - } - ReactiveCouchbaseTemplate t = this.reactive(); - PseudoArgs pArgs = t.getPseudoArgs(); - if (pArgs != null && pArgs.getTxOp() != null) { - return pArgs.getTxOp(); - } - return null; - } - */ } diff --git a/src/main/java/org/springframework/data/couchbase/core/NonReactiveSupportWrapper.java b/src/main/java/org/springframework/data/couchbase/core/NonReactiveSupportWrapper.java index 2020a0d43..7b2c9dd4e 100644 --- a/src/main/java/org/springframework/data/couchbase/core/NonReactiveSupportWrapper.java +++ b/src/main/java/org/springframework/data/couchbase/core/NonReactiveSupportWrapper.java @@ -81,11 +81,6 @@ public String getJavaNameForEntity(Class clazz) { return support.getJavaNameForEntity(clazz); } - @Override - public Integer getTxResultHolder(T source) { - return support.getTxResultHolder(source); - } - @Override public TranslationService getTranslationService() { return support.getTranslationService(); diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplate.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplate.java index 98a5c6546..c9750db4a 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplate.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplate.java @@ -43,8 +43,6 @@ import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; -import com.couchbase.client.core.transaction.CoreTransactionAttemptContext; -import com.couchbase.client.java.ClusterInterface; import com.couchbase.client.java.Collection; import com.couchbase.client.java.query.QueryScanConsistency; @@ -254,72 +252,12 @@ public QueryScanConsistency getConsistency() { return scanConsistency; } - protected Mono doGetDatabase() { - return ReactiveCouchbaseClientUtils.getDatabase(clientFactory, SessionSynchronization.ON_ACTUAL_TRANSACTION); - } protected Mono doGetTemplate() { return ReactiveCouchbaseClientUtils.getTemplate(clientFactory, SessionSynchronization.ON_ACTUAL_TRANSACTION, this.getConverter()); } - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#withSession(com.mongodb.session.ClientSession) - */ - public ReactiveCouchbaseOperations withResources(ReactiveCouchbaseResourceHolder resources) { - return new ReactiveResourcesBoundCouchbaseTemplate(resources, ReactiveCouchbaseTemplate.this); - } - - /** - * {@link CouchbaseTemplate} extension bound to a specific {@link CoreTransactionAttemptContext} that is applied when - * interacting with the server through the driver API.
- * The prepare steps for {} and {} proxy the target and invoke the desired target method matching the actual arguments - * plus a {@link CoreTransactionAttemptContext}. - * - * @author Christoph Strobl - * @since 2.1 - */ - static class ReactiveResourcesBoundCouchbaseTemplate extends ReactiveCouchbaseTemplate { - - private final ReactiveCouchbaseTemplate delegate; - private final ReactiveCouchbaseResourceHolder holder; - - /** - * @param holder must not be {@literal null}. - * @param that must not be {@literal null}. - */ - ReactiveResourcesBoundCouchbaseTemplate(ReactiveCouchbaseResourceHolder holder, ReactiveCouchbaseTemplate that) { - - super(that.clientFactory.withCore(holder), that.getConverter()); - - this.delegate = that; - this.holder = holder; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.ReactiveMongoTemplate#getCollection(java.lang.String) - */ - @Override - public Collection getCollection(String collectionName) { - - // native MongoDB objects that offer methods with ClientSession must not be proxied. - return delegate.getCollection(collectionName); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.ReactiveMongoTemplate#getMongoDatabase() - */ - @Override - public ReactiveCouchbaseClientFactory getCouchbaseClientFactory() { - - // native MongoDB objects that offer methods with ClientSession must not be proxied. - return delegate.getCouchbaseClientFactory(); - } - } - class IndexCreatorEventListener implements ApplicationListener> { final Consumer subscriptionExceptionHandler; @@ -344,27 +282,6 @@ public void onApplicationEvent(MappingContextEvent event) { } } - /** - * Get the TransactionalOperator from
- * 1. The template.clientFactory
- * 2. The template.threadLocal
- * 3. otherwise null
- * This can be overriden in the operation method by
- * 1. repository.withCollection() - */ - /* - private CouchbaseStuffHandle getTransactionalOperator() { - if (this.getCouchbaseClientFactory().getTransactionalOperator() != null) { - return this.getCouchbaseClientFactory().getTransactionalOperator(); - } - ReactiveCouchbaseTemplate t = this; - PseudoArgs pArgs = t.getPseudoArgs(); - if (pArgs != null && pArgs.getTxOp() != null) { - return pArgs.getTxOp(); - } - return null; - } - */ /** * Value object chaining together a given source document with its mapped representation and the collection to persist * it to. diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java index 7d73839c6..cb372aaf8 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java @@ -93,10 +93,6 @@ public Mono applyResult(T entity, CouchbaseDocument converted, Object id, return Mono.fromSupplier(() -> applyResultBase(entity, converted, id, cas, txResultHolder, holder)); } - @Override - public Integer getTxResultHolder(T source) { - return null; - } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java index b717b589a..4e0c62aa0 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java @@ -200,6 +200,7 @@ public Flux all() { return pArgs.getScope() == null ? clientFactory.getCluster().reactive().query(statement, opts) : rs.query(statement, opts); } else { + // todo gpx handle unsupported options TransactionQueryOptions opts = buildTransactionOptions(pArgs.getOptions()); return (AttemptContextReactiveAccessor.createReactiveTransactionAttemptContext(s.getCore(), clientFactory.getCluster().environment().jsonSerializer())).query(statement, opts); diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveSessionCallback.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveSessionCallback.java deleted file mode 100644 index c91caa162..000000000 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveSessionCallback.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.springframework.data.couchbase.core; -/* - * Copyright 2018-2021 the original author or authors. - * - * 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. - */ - -import org.reactivestreams.Publisher; - -/** - * Callback interface for executing operations within a {@link com.mongodb.reactivestreams.client.ClientSession} using - * reactive infrastructure. - * - * @author Christoph Strobl - * @since 2.1 - * @see com.mongodb.reactivestreams.client.ClientSession - */ -@FunctionalInterface -public interface ReactiveSessionCallback { - - /** - * Execute operations against a MongoDB instance via session bound {@link ReactiveMongoOperations}. The session is - * inferred directly into the operation so that no further interaction is necessary.
- * Please note that only Spring Data-specific abstractions like {@link ReactiveMongoOperations#find(Query, Class)} and - * others are enhanced with the {@link com.mongodb.session.ClientSession}. When obtaining plain MongoDB gateway - * objects like {@link com.mongodb.reactivestreams.client.MongoCollection} or - * {@link com.mongodb.reactivestreams.client.MongoDatabase} via eg. - * {@link ReactiveMongoOperations#getCollection(String)} we leave responsibility for - * {@link com.mongodb.session.ClientSession} again up to the caller. - * - * @param operations will never be {@literal null}. - * @return never {@literal null}. - */ - Publisher doInSession(ReactiveCouchbaseOperations operations); -} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveTemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveTemplateSupport.java index 1313a646d..afde8fd6a 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveTemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveTemplateSupport.java @@ -47,9 +47,5 @@ Mono applyResult(T entity, CouchbaseDocument converted, Object id, Long c String getJavaNameForEntity(Class clazz); - Integer getTxResultHolder(T source); - - // Integer setTxResultHolder(T source); - TranslationService getTranslationService(); } diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionDefinition.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionDefinition.java index d46d73bc7..d879ac645 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionDefinition.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionDefinition.java @@ -1,37 +1,18 @@ package org.springframework.data.couchbase.transaction; import com.couchbase.client.java.transactions.ReactiveTransactionAttemptContext; -import com.couchbase.client.java.transactions.TransactionAttemptContext; -import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.support.DefaultTransactionDefinition; public class CouchbaseTransactionDefinition extends DefaultTransactionDefinition { ReactiveTransactionAttemptContext atr; - TransactionAttemptContext at; public CouchbaseTransactionDefinition(){ super(); setIsolationLevel(ISOLATION_READ_COMMITTED); } - public CouchbaseTransactionDefinition(TransactionDefinition that) { - super(that); - } - - public CouchbaseTransactionDefinition(int propagationBehavior) { - super(propagationBehavior); - } - public void setAttemptContextReactive(ReactiveTransactionAttemptContext atr){ this.atr = atr; } - - public ReactiveTransactionAttemptContext getAttemptContextReactive(){ - return atr; - } - - public void setAttemptContext(TransactionAttemptContext attemptContext) { - at = attemptContext; - } } diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionManager.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionManager.java index 3f5d7a393..f161049c3 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionManager.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionManager.java @@ -474,11 +474,6 @@ public CoreTransactionAttemptContext getCore() { return resourceHolder != null ? resourceHolder.getCore() : null; } - private ReactiveCouchbaseResourceHolder getRequiredResourceHolder() { - Assert.state(resourceHolder != null, "CouchbaseResourceHolder is required but not present. o_O"); - return resourceHolder; - } - private CoreTransactionAttemptContext getRequiredCore() { CoreTransactionAttemptContext core = getCore(); Assert.state(core != null, "A Core is required but it turned out to be null."); diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionStatus.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionStatus.java index 5331cea53..23635bd32 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionStatus.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionStatus.java @@ -5,7 +5,6 @@ public class CouchbaseTransactionStatus extends DefaultTransactionStatus { - final TransactionSynchronizationManager transactionSynchronizationManager; /** * Create a new {@code DefaultTransactionStatus} instance. * @@ -28,6 +27,5 @@ public CouchbaseTransactionStatus(Object transaction, boolean newTransaction, bo readOnly, debug, suspendedResources); - transactionSynchronizationManager = sm; } } diff --git a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionalOperator.java b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionalOperator.java index b02215ab6..b31748259 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionalOperator.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/CouchbaseTransactionalOperator.java @@ -97,16 +97,6 @@ public Mono reactive(Function> R repository(R repo) { if (!(repo.getOperations() instanceof ReactiveCouchbaseOperations)) { throw new CouchbaseException("Repository must be a Reactive Couchbase repository" + repo); diff --git a/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseClientUtils.java b/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseClientUtils.java index 1f429c6a0..8e648c1c7 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseClientUtils.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseClientUtils.java @@ -17,76 +17,11 @@ public class ReactiveCouchbaseClientUtils { - /** - * Obtain the default {@link MongoDatabase database} form the given {@link ReactiveMongoDatabaseFactory factory}. - *
- * Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the subscriber - * {@link Context} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}. - * - * @param factory the {@link ReactiveMongoDatabaseFactory} to get the {@link MongoDatabase} from. - * @param sessionSynchronization the synchronization to use. Must not be {@literal null}. - * @return the {@link MongoDatabase} that is potentially associated with a transactional {@link ClientSession}. - */ - public static Mono getDatabase(ReactiveCouchbaseClientFactory factory, - SessionSynchronization sessionSynchronization) { - return doGetCouchbaseCluster(null, factory, sessionSynchronization); - } - public static Mono getTemplate(ReactiveCouchbaseClientFactory factory, SessionSynchronization sessionSynchronization, CouchbaseConverter converter) { return doGetCouchbaseTemplate(null, factory, sessionSynchronization, converter); } - /** - * Obtain the {@link MongoDatabase database} with given name form the given {@link ReactiveMongoDatabaseFactory - * factory} using {@link SessionSynchronization#ON_ACTUAL_TRANSACTION native session synchronization}.
- * Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the subscriber - * {@link Context} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}. - * - * @param dbName the name of the {@link MongoDatabase} to get. - * @param factory the {@link ReactiveMongoDatabaseFactory} to get the {@link MongoDatabase} from. - * @return the {@link MongoDatabase} that is potentially associated with a transactional {@link ClientSession}. - */ - public static Mono getDatabase(String dbName, ReactiveCouchbaseClientFactory factory) { - return doGetCouchbaseCluster(dbName, factory, SessionSynchronization.ON_ACTUAL_TRANSACTION); - } - - /** - * Obtain the {@link MongoDatabase database} with given name form the given {@link ReactiveMongoDatabaseFactory - * factory}.
- * Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the subscriber - * {@link Context} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}. - * - * @param dbName the name of the {@link MongoDatabase} to get. - * @param factory the {@link ReactiveMongoDatabaseFactory} to get the {@link MongoDatabase} from. - * @param sessionSynchronization the synchronization to use. Must not be {@literal null}. - * @return the {@link MongoDatabase} that is potentially associated with a transactional {@link ClientSession}. - */ - public static Mono getCluster(String dbName, ReactiveCouchbaseClientFactory factory, - SessionSynchronization sessionSynchronization) { - return doGetCouchbaseCluster(dbName, factory, sessionSynchronization); - } - - private static Mono doGetCouchbaseCluster(@Nullable String dbName, - ReactiveCouchbaseClientFactory factory, SessionSynchronization sessionSynchronization) { - - Assert.notNull(factory, "DatabaseFactory must not be null!"); - - if (sessionSynchronization == SessionSynchronization.NEVER) { - return getCouchbaseClusterOrDefault(dbName, factory); - } - - return TransactionSynchronizationManager.forCurrentTransaction() - .filter(TransactionSynchronizationManager::isSynchronizationActive) // - .flatMap(synchronizationManager -> { - - return doGetSession(synchronizationManager, factory, sessionSynchronization) // - .flatMap(it -> getCouchbaseClusterOrDefault(dbName, factory.withCore(it))); - }) // - .onErrorResume(NoTransactionException.class, e -> getCouchbaseClusterOrDefault(dbName, factory)) // hitting this - .switchIfEmpty(getCouchbaseClusterOrDefault(dbName, factory)); - } - private static Mono doGetCouchbaseTemplate(@Nullable String dbName, ReactiveCouchbaseClientFactory factory, SessionSynchronization sessionSynchronization, CouchbaseConverter converter) { @@ -97,9 +32,6 @@ private static Mono doGetCouchbaseTemplate(@Nullable return getCouchbaseTemplateOrDefault(dbName, factory, converter); } - //CouchbaseResourceHolder h = (CouchbaseResourceHolder) org.springframework.transaction.support.TransactionSynchronizationManager - // .getResource(factory); - return TransactionSynchronizationManager.forCurrentTransaction() .filter(TransactionSynchronizationManager::isSynchronizationActive) // .flatMap(synchronizationManager -> { @@ -158,6 +90,7 @@ private static Mono doGetSession(TransactionSyn System.err.println("doGetSession: createClientSession()"); // init a non native MongoDB transaction by registering a MongoSessionSynchronization + // todo gp but this always returns null - does this code get executed anywhere? return createClientSession(dbFactory).map(session -> { ReactiveCouchbaseResourceHolder newHolder = new ReactiveCouchbaseResourceHolder(session); diff --git a/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseResourceHolder.java b/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseResourceHolder.java index e2c9ba353..00e6ded54 100644 --- a/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseResourceHolder.java +++ b/src/main/java/org/springframework/data/couchbase/transaction/ReactiveCouchbaseResourceHolder.java @@ -75,23 +75,6 @@ CoreTransactionAttemptContext getRequiredCore() { return core; } - /* - * @return the associated {@link CouchbaseClientFactory}. - ReactiveCouchbaseClientFactory getDatabaseFactory() { - return databaseFactory; - } - */ - - /** - * Set the {@link CoreTransactionAttemptContext} to guard. - * - * @param core can be {@literal null}. - */ - CoreTransactionAttemptContext setCore(@Nullable CoreTransactionAttemptContext core) { - System.err.println("setCore: " + core); - return this.core = core; - } - /** * @return {@literal true} if session is not {@literal null}. */ @@ -99,37 +82,11 @@ boolean hasCore() { return core != null; } - /** - * If the {@link ReactiveCouchbaseResourceHolder} is {@link #hasCore() not already associated} with a - * {@link CoreTransactionAttemptContext} the given value is {@link #setCore(CoreTransactionAttemptContext)} set} and - * returned, otherwise the current bound session is returned. - * - * @param core - * @return - */ - @Nullable - CoreTransactionAttemptContext setSessionIfAbsent(@Nullable CoreTransactionAttemptContext core) { - - if (!hasCore()) { - setCore(core); - } - - return this.core; - } - public boolean hasActiveTransaction() { return getCore() != null; } - public TransactionResultHolder transactionResultHolder(Integer key) { - TransactionResultHolder holder = getResultMap.get(key); - if(holder == null){ - throw new RuntimeException("did not find transactionResultHolder for key="+key+" in session"); - } - return holder; - } - public TransactionResultHolder transactionResultHolder(TransactionResultHolder holder, Object o) { System.err.println("PUT: "+System.identityHashCode(o)+" "+o); getResultMap.put(System.identityHashCode(o), holder); diff --git a/src/test/java/org/springframework/data/couchbase/transactions/Config.java b/src/test/java/org/springframework/data/couchbase/transactions/Config.java index e9193f294..8cd7c8bd5 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/Config.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/Config.java @@ -1,10 +1,5 @@ package org.springframework.data.couchbase.transactions; -import java.time.Duration; - -import com.couchbase.client.core.msg.kv.DurabilityLevel; -import com.couchbase.client.core.transaction.config.CoreTransactionsConfig; -import com.couchbase.client.java.transactions.config.TransactionsConfig; import org.springframework.context.annotation.Configuration; import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; @@ -12,7 +7,6 @@ import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests; import org.springframework.transaction.annotation.EnableTransactionManagement; -import com.couchbase.client.java.transactions.config.TransactionOptions; @Configuration @EnableCouchbaseRepositories("org.springframework.data.couchbase") @@ -39,36 +33,4 @@ public String getPassword() { public String getBucketName() { return ClusterAwareIntegrationTests.bucketName(); } - - @Override - public TransactionsConfig.Builder transactionsConfig() { - return TransactionsConfig.builder().durabilityLevel(DurabilityLevel.NONE).timeout(Duration.ofMinutes(20));// for testing - } - - /* - @Override - public TransactionsConfig transactionConfig() { - // expirationTime 20 minutes for stepping with the debugger - return TransactionsConfig.create() - .logDirectly(Event.Severity.INFO) - .logOnFailure(true, - Event.Severity.ERROR) - .expirationTime(Duration.ofMinutes(20)) - .durabilityLevel(TransactionDurabilityLevel.MAJORITY) - .build(); - } - */ - /* - beforeAll creates a PersonService bean in the applicationContext - - context = new AnnotationConfigApplicationContext(CouchbasePersonTransactionIntegrationTests.Config.class, - PersonService.class); - - @Bean("personService") - PersonService getPersonService(CouchbaseOperations ops, CouchbaseTransactionManager mgr, - ReactiveCouchbaseOperations opsRx, ReactiveCouchbaseTransactionManager mgrRx) { - return new PersonService(ops, mgr, opsRx, mgrRx); - } - */ - } From 4344bb0a2562f8ad04e69a3b666f0a58e114c86a Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Mon, 30 May 2022 18:07:19 +0100 Subject: [PATCH 15/15] Adding tests for @Transactional removeByQuery and findByQuery Failing as they aren't being executed transactionally - investigating why. --- ...TransactionalTemplateIntegrationTests.java | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalTemplateIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalTemplateIntegrationTests.java index bc176c7ce..f77ace22b 100644 --- a/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalTemplateIntegrationTests.java @@ -32,6 +32,8 @@ import org.springframework.data.couchbase.core.CouchbaseOperations; import org.springframework.data.couchbase.core.CouchbaseTemplate; import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations; +import org.springframework.data.couchbase.core.RemoveResult; +import org.springframework.data.couchbase.core.query.QueryCriteria; import org.springframework.data.couchbase.domain.Person; import org.springframework.data.couchbase.domain.PersonWithoutVersion; import org.springframework.data.couchbase.transaction.CouchbaseSimpleCallbackTransactionManager; @@ -45,6 +47,8 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.Transactional; +import javax.management.Query; +import javax.management.ValueExp; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -148,6 +152,37 @@ public void committedRemove() { assertEquals(1, tryCount.get()); } + @DisplayName("A basic golden path removeByQuery should succeed") + @Test + public void committedRemoveByQuery() { + AtomicInteger tryCount = new AtomicInteger(0); + Person person = new Person(1, "Walter", "White"); + operations.insertById(Person.class).one(person); + + List removed = personService.doInTransaction(tryCount, ops -> { + return ops.removeByQuery(Person.class).matching(QueryCriteria.where("firstname").eq("Walter")).all(); + }); + + Person fetched = operations.findById(Person.class).one(person.getId().toString()); + assertNull(fetched); + assertEquals(1, tryCount.get()); + assertEquals(1, removed.size()); + } + + @DisplayName("A basic golden path findByQuery should succeed (though we don't know for sure it executed transactionally)") + @Test + public void committedFindByQuery() { + AtomicInteger tryCount = new AtomicInteger(0); + Person person = new Person(1, "Walter", "White"); + operations.insertById(Person.class).one(person); + + List found = personService.doInTransaction(tryCount, ops -> { + return ops.findByQuery(Person.class).matching(QueryCriteria.where("firstname").eq("Walter")).all(); + }); + + assertEquals(1, found.size()); + } + @DisplayName("Basic test of doing an insert then rolling back") @Test public void rollbackInsert() { @@ -221,6 +256,49 @@ public void rollbackRemove() { assertEquals(1, tryCount.get()); } + @DisplayName("Basic test of doing a removeByQuery then rolling back") + @Test + public void rollbackRemoveByQuery() { + AtomicInteger tryCount = new AtomicInteger(0); + Person person = new Person(1, "Walter", "White"); + operations.insertById(Person.class).one(person); + + try { + personService.doInTransaction(tryCount, ops -> { + // todo gpx this isn't executed transactionally + ops.removeByQuery(Person.class).matching(QueryCriteria.where("firstname").eq("Walter")).all(); + throw new SimulateFailureException(); + }); + fail(); + } catch (TransactionFailedException err) { + assertTrue(err.getCause() instanceof SimulateFailureException); + } + + Person fetched = operations.findById(Person.class).one(person.getId().toString()); + assertNotNull(fetched); + assertEquals(1, tryCount.get()); + } + + @DisplayName("Basic test of doing a findByQuery then rolling back") + @Test + public void rollbackFindByQuery() { + AtomicInteger tryCount = new AtomicInteger(0); + Person person = new Person(1, "Walter", "White"); + operations.insertById(Person.class).one(person); + + try { + personService.doInTransaction(tryCount, ops -> { + ops.findByQuery(Person.class).matching(QueryCriteria.where("firstname").eq("Walter")).all(); + throw new SimulateFailureException(); + }); + fail(); + } catch (TransactionFailedException err) { + assertTrue(err.getCause() instanceof SimulateFailureException); + } + + assertEquals(1, tryCount.get()); + } + @Test public void shouldRollbackAfterException() { try {