Suporte a transações foi adicionado recentemente aos SDKs do Couchbase. A partir de Spring Data Couchbase 5.0.0-M5O suporte a transações foi adicionado ao Dados do Spring Couchbase. Neste blog, descreveremos como usar o Spring Data @Transactional para aproveitar o Couchbase Transactions.
O base de dados de mola contém um repositório aplicativo de teste de dados de mola que realiza transações. Ele se baseia na amostra de viagem em um servidor Couchbase local. (qualquer bucket com um índice primário será suficiente).
|
1 2 |
git clone git@github.com:spring-projects/spring-data-examples.git cd spring-data-examples/couchbase/transactions |
Os beans que oferecem suporte ao gerenciamento de transações foram adicionados ao AbstractCouchbaseConfiguration. Para permitir que a classe de interceptador de transações integrada seja substituída pelo CouchbaseTransactionInterceptor, verifique se a seguinte linha está no arquivo application.properties file:
application.properties
|
1 |
spring.main.allow-bean-definition-overriding=true |
A configuração da transação pode ser modificada substituindo o configureEnvironment() em sua classe que estende AbstractCouchbaseConfiguration. Aqui definimos o DurabilityLevel (nível de durabilidade) para NENHUM para que possamos trabalhar em nosso cluster de nó único.
Config.java
|
1 2 3 4 5 6 7 8 9 |
@Configuration @EnableCouchbaseRepositories({"com.example.demo"}) @EnableTransactionManagement public class Config extends AbstractCouchbaseConfiguration { … @Override public void configureEnvironment(ClusterEnvironment.Builder builder){ builder.transactionsConfig(TransactionsConfig.durabilityLevel(DurabilityLevel.NONE)); } |
Na classe que os usará, precisamos definir o modelo como de costume e uma referência ao objeto de serviço. É importante observar que o objeto @Autowired preenche uma variável com um objeto proxy. Esse é um detalhe importante, pois os objetos proxy executam o processamento antes e depois de chamar o método real. A criação direta do objeto de serviço a partir de seu construtor fará com que a anotação @Transactional não tenha efeito.
CmdRunner.java
|
1 2 |
@Autowired CouchbaseTemplate template; @Autowired AirportGatesService airportGatesService; |
O serviço é definido com um @Serviço anotação. O construtor recebe argumentos que são @Bean de modo que a infraestrutura possa criar um objeto proxy para @Autowired.
AirlineGateService.java
|
1 2 3 4 5 6 |
@Service public class AirlineGatesService { CouchbaseTemplate template; public AirlineGatesService(CouchbaseTemplate template) { this.template = template; } |
AirlineGateService.java (continuação)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// The @Transactional annotation results in the method of the proxy for the service executing this in a transaction @Transactional public void transferGates(String fromId, String toId, int gatesToTransfer, RuntimeException exceptionToThrow) { // Not necessary, but may wish to include this check to confirm this is actually in a transaction. TransactionalSupport.checkForTransactionInThreadLocalStorage().map((h) -> { if (!h.isPresent()) throw new RuntimeException("not in transaction!"); return h; }); AirlineGates fromAirlineGates = template.findById(AirlineGates.class).one(fromId); AirlineGates toAirlineGates = template.findById(AirlineGates.class).one(toId); toAirlineGates.gates += gatesToTransfer; fromAirlineGates.gates -= gatesToTransfer; template.save(fromAirlineGates); // maybe simulate an error occurring after the fromAirlineGates doc has been saved if(exceptionToThrow != null){ throw exceptionToThrow; } template.save(toAirlineGates); } |
Agora use tudo o que construímos:
- salvar dois Portões de companhias aéreas documentos, cada um dos quais indica 200 portões
- verificar se eles foram salvos.
- executar o transferGates() método do serviço para transferir 50 portões de uma companhia aérea para outra em uma transação.
- verificar se a transferência foi realizada.
- tentativa de execução transferGates() novamente, desta vez com uma exceção que ocorre após o primeiro documento ter sido salvo.
CmdRunner.java
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
AirlineGates airlineGates1 = new AirlineGates("1", "JFK", "American Airlines", Long.valueOf(200)); //1 AirlineGates airlineGates2 = new AirlineGates("2", "JFK", "Lufthansa", Long.valueOf(200)); AirlineGates saved1 = airlineGatesService.save(airlineGates1); AirlineGates saved2 = airlineGatesService.save(airlineGates2); AirlineGates found1 = airlineGatesService.findById(saved1.getId()); //2 AirlineGates found2 = airlineGatesService.findById(saved2.getId()); System.err.println("found before transferGates: " + found1); System.err.println("found before transferGates: " + found2); // move 50 gates from airline1 to airline2 int gatesToTransfer=50; airlineGatesService.transferGates(airlineGates1.getId(), airlineGates2.getId(), gatesToTransfer, null); //3 found1 = airlineGatesService.findById(saved1.getId()); found2 = airlineGatesService.findById(saved2.getId()); System.err.println("found after transferGates: " + found1); //4 System.err.println("found after transferGates: " + found2); Assert.isTrue(found1.getGates().equals(airlineGates1.getGates()-gatesToTransfer), "should have transferred"); Assert.isTrue(found2.getGates().equals(airlineGates1.getGates()+gatesToTransfer), "should have transferred"); // attempt to move 44 gates from airline1 to airline2, but it fails. try { // 5 airlineGatesService.transferGates(airlineGates1.getId(), airlineGates2.getId(), 44, new SimulateErrorException()); } catch (RuntimeException rte) { if (!(rte instanceof TransactionSystemUnambiguousException) && rte != null && rte.getCause() instanceof SimulateErrorException) { throw rte; } } System.err.println("found after transferGates: " + airlineGatesService.findById(airlineGates1.getId())); System.err.println("found after transferGates: " + airlineGatesService.findById(airlineGates2.getId())); Assert.isTrue(found1.getGates().equals(airlineGates1.getGates()-gatesToTransfer), "should be same as previous"); Assert.isTrue(found2.getGates().equals(airlineGates1.getGates()+gatesToTransfer), "should be same as previous"); |
Como funciona:
- O Interceptador de transações @Bean in AbstractCouchbaseConfiguration substitui o Interceptador de transações para métodos anotados @Transactional.
- A anotação @Transactional faz com que o proxy para AirportGatesService para usar o Interceptador de transações para chamar o método anotado usando o CouchbaseCallbackTransactionManager.
- O CouchbaseCallbackTransactionManager inicializa uma transação e, em seguida, chama o método de serviço real no contexto da transação.
- Se a chamada do método for bem-sucedida, o CouchbaseCallbackTransactionManager confirma a transação.
- Se o commit for bem-sucedido, a chamada será retornada.
- Se o commit falhar com uma exceção que pode ser tentada novamente, o CouchbaseCallbackTransactionManger tentará novamente o processo completo de inicialização de uma nova transação, executando o método de serviço real e, em seguida, confirmando a transação.
O CouchbaseCallbackTransactionManager repetirá isso até que o commit seja bem-sucedido ou o número máximo de tentativas seja atingido.
Opções para as próximas etapas
- As transações do Couchbase podem ser aproveitadas em Dados do Spring Couchbase usando o Operador transacional - veja o Documentos do Spring Data aqui.
- As transações podem também pode ser usado diretamente pelo Couchbase Java SDK - consulte a seção Documentos sobre transações Java ACID aqui.
Olá, pessoal - o projeto couchbase/transactions ainda não foi incorporado ao spring-data-examples. Ele estará disponível em breve.
- Mike
O projeto couchbase/transactions agora está disponível na ramificação boot-3
https://github.com/spring-projects/spring-data-examples/tree/boot-3/couchbase/transactions
O exemplo de transação precisa do spring-boot-starter-data-couchbase 3.0.0-RC2 para ser executado devido a uma incompatibilidade com o reactor em outras versões 3.0.0*.
Olá, Michael, obrigado por implementar o suporte integrado a transações do couchbase. Funciona muito bem quando uso "couchbase transaction" usando um único bucket, mas tenho um erro em transações usando vários buckets.
Implementei este link https://github.com/spring-projects/spring-data-couchbase/issues/878. Essa implementação não está funcionando com transações. Você tem alguma ideia?
As transações do Spring Data Couchbase não funcionarão com vários buckets, pois vários buckets no Spring Data Couchbase exigem várias conexões com o Couchbase e a transação deve estar em uma única conexão.
- Mike