Apoyo a las transacciones se ha añadido recientemente a los SDK de Couchbase. A partir de Spring Data Couchbase 5.0.0-M5se ha añadido soporte para transacciones a Spring Data Couchbase. En este blog, describiremos el uso de Spring Data @Transactional para aprovechar Couchbase Transactions.
En spring-data-couchbase contiene un spring-data-testapp que ejerce las transacciones. Se basa en la viaje-muestra en un servidor Couchbase local. (cualquier bucket con un índice primario será suficiente).
1 2 |
git clonar git@github.com:primavera-proyectos/primavera-datos-ejemplos.git cd primavera-datos-ejemplos/couchbase/transacciones |
Se han añadido a AbstractCouchbaseConfiguration. Para permitir que la clase de interceptor de transacciones incorporada sea sobrescrita con CouchbaseTransactionInterceptor, asegúrese de que la siguiente línea está en el archivo aplicación.propiedades file:
aplicación.propiedades
1 |
primavera.principal.permitir-judía-definición-anulando=verdadero |
La configuración de la transacción puede modificarse anulando la directiva configurarEntorno() en su clase que extiende AbstractCouchbaseConfiguration. Aquí fijamos el durabilityLevel a NONE para que podamos trabajar en nuestro clúster de un solo nodo.
Config.java
1 2 3 4 5 6 7 8 9 |
@Configuración @EnableCouchbaseRepositories({"com.example.demo"}) @EnableTransactionManagement público clase Configurar extiende AbstractCouchbaseConfiguration { ... @Anular público void configureEnvironment(ClusterEnvironment.Constructor constructor){ constructor.transactionsConfig(TransactionsConfig.durabilityLevel(Nivel de durabilidad.NONE)); } |
En la clase que los utilizará, tenemos que definir la plantilla como de costumbre y una referencia al objeto de servicio. Es importante tener en cuenta que el @Autowired rellena una variable con un objeto proxy. Este es un detalle importante, ya que los objetos proxy realizan el procesamiento antes y después de llamar al método real. Si se crea directamente el objeto de servicio desde su constructor, la anotación @Transactional no tendrá ningún efecto.
CmdRunner.java
1 2 |
@Autowired CouchbaseTemplate plantilla; @Autowired AirportGatesService airportGatesService; |
El servicio se define con un @Servicio anotación. El constructor toma argumentos que son @Bean de forma que la infraestructura pueda crear un objeto proxy para @Autowired.
AirlineGateService.java
1 2 3 4 5 6 |
@Servicio público clase AerolíneaPuertasServicio { CouchbaseTemplate plantilla; público AerolíneaPuertasServicio(CouchbaseTemplate plantilla) { este.plantilla = plantilla; } |
AirlineGateService.java (continuación)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// La anotación @Transactional hace que el método del proxy para el servicio ejecute esto en una transacción @Transaccional público void transferGates(Cadena fromId, Cadena toId, int gatesToTransfer, RuntimeException exceptionToThrow) { // No es necesario, pero tal vez desee incluir esta comprobación para confirmar que se trata realmente de una transacción. SoporteTransaccional.checkForTransactionInThreadLocalStorage().mapa((h) -> { si (!h.isPresent()) tirar nuevo RuntimeException("¡no en la transacción!"); devolver h; }); AirlineGates fromAirlineGates = plantilla.findById(AirlineGates.clase).un(fromId); AirlineGates toAirlineGates = plantilla.findById(AirlineGates.clase).un(toId); toAirlineGates.puertas += gatesToTransfer; fromAirlineGates.puertas -= gatesToTransfer; plantilla.guardar(fromAirlineGates); // tal vez simular un error que se produce después de que el fromAirlineGates doc se ha guardado si(exceptionToThrow != null){ tirar exceptionToThrow; } plantilla.guardar(toAirlineGates); } |
Ahora usa todo lo que hemos construido:
- salva dos AirlineGates documentos, cada uno de los cuales indica 200 puertas
- verificar que se han salvado.
- ejecutar la transferGates() método del servicio para transferir 50 puertas de embarque de una compañía aérea a otra en una transacción.
- verificar que la transferencia se ha realizado.
- intento de ejecución transferGates() de nuevo, esta vez con una excepción que se produce después de guardar el primer documento.
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 compañía aéreaPuertas1 = nuevo AirlineGates("1", "JFK", "American Airlines", Largo.valueOf(200)); //1 AirlineGates aerolíneaPuertas2 = nuevo AirlineGates("2", "JFK", "Lufthansa", Largo.valueOf(200)); AirlineGates guardado1 = airlineGatesService.guardar(compañía aéreaPuertas1); AirlineGates guardado2 = airlineGatesService.guardar(aerolíneaPuertas2); AirlineGates encontrado1 = airlineGatesService.findById(guardado1.getId()); //2 AirlineGates encontrado2 = airlineGatesService.findById(guardado2.getId()); Sistema.err.println(" encontrados antes de transferGates: " + encontrado1); Sistema.err.println(" encontrados antes de transferGates: " + encontrado2); // trasladar 50 puertas de la compañía aérea1 a la compañía aérea2 int gatesToTransfer=50; airlineGatesService.transferGates(compañía aéreaPuertas1.getId(), aerolíneaPuertas2.getId(), gatesToTransfer, null); //3 encontrado1 = airlineGatesService.findById(guardado1.getId()); encontrado2 = airlineGatesService.findById(guardado2.getId()); Sistema.err.println(" encontrados después de transferGates: " + encontrado1); //4 Sistema.err.println(" encontrados después de transferGates: " + encontrado2); Afirme.isTrue(encontrado1.getGates().es igual a(compañía aéreaPuertas1.getGates()-gatesToTransfer), "debería haber transferido"); Afirme.isTrue(encontrado2.getGates().es igual a(compañía aéreaPuertas1.getGates()+gatesToTransfer), "debería haber transferido"); // intenta mover 44 puertas de la aerolínea1 a la aerolínea2, pero falla. pruebe { // 5 airlineGatesService.transferGates(compañía aéreaPuertas1.getId(), aerolíneaPuertas2.getId(), 44, nuevo SimularErrorException()); } captura (RuntimeException rte) { si (!(rte instanceof TransactionSystemUnambiguousException) && rte != null && rte.getCause() instanceof SimularErrorException) { tirar rte; } } Sistema.err.println(" encontrados después de transferGates: " + airlineGatesService.findById(compañía aéreaPuertas1.getId())); Sistema.err.println(" encontrados después de transferGates: " + airlineGatesService.findById(aerolíneaPuertas2.getId())); Afirme.isTrue(encontrado1.getGates().es igual a(compañía aéreaPuertas1.getGates()-gatesToTransfer), "debería ser el mismo que el anterior"); Afirme.isTrue(encontrado2.getGates().es igual a(compañía aéreaPuertas1.getGates()+gatesToTransfer), "debería ser el mismo que el anterior"); |
Cómo funciona:
- En TransactionInterceptor @Bean in AbstractCouchbaseConfiguration anula la función TransactionInterceptor para métodos anotados @Transactional.
- La anotación @Transactional hace que el proxy de AirportGatesService para utilizar el TransactionInterceptor para llamar al método anotado utilizando el método CouchbaseCallbackTransactionManager.
- En CouchbaseCallbackTransactionManager inicializa una transacción y luego llama al método de servicio real en el contexto de la transacción.
- Si la llamada al método tiene éxito, el CouchbaseCallbackTransactionManager confirma la transacción.
- Si la consignación se realiza correctamente, se devuelve la llamada.
- Si la confirmación falla con una excepción reintentable, la función CouchbaseCallbackTransactionManger reintentará el proceso completo de inicializar una nueva transacción, ejecutar el método de servicio real y luego confirmar la transacción.
En CouchbaseCallbackTransactionManager repetirá esta operación hasta que la confirmación tenga éxito o se haya alcanzado el número máximo de reintentos.
Opciones para los próximos pasos
- Las transacciones de Couchbase pueden aprovecharse en Spring Data Couchbase utilizando el Operador transaccional - ver el Documentación de Spring Data.
- Las transacciones pueden también puede ser utilizado directamente por el SDK Java de Couchbase. Documentación sobre transacciones Java ACID.
Hola amigos - el proyecto couchbase/transactions aún no se ha fusionado con spring-data-examples. Estará disponible en breve.
- Mike
El proyecto couchbase/transactions ya está disponible en la rama boot-3
https://github.com/spring-projects/spring-data-examples/tree/boot-3/couchbase/transactions
El ejemplo de transacción necesita spring-boot-starter-data-couchbase 3.0.0-RC2 para ejecutarse debido a una incompatibilidad con reactor en otras versiones 3.0.0*.
Hola Michael, Gracias por implementar el soporte de transacciones couchbase incorporado. Funciona muy bien cuando uso "couchbase transacción" utilizando el cubo único, pero tengo un error en las transacciones utilizando con múltiples cubos.
He implementado este enlace https://github.com/spring-projects/spring-data-couchbase/issues/878. Esta implementación no funciona con las transacciones. ¿Tiene alguna idea?
Spring Data Couchbase Transactions no funcionará con multi-buckets ya que los multi-buckets en Spring Data Couchbase requieren múltiples conexiones a Couchbase y la transacción debe estar en una única conexión.
- Mike