Introducción
En este artículo, le mostraré la mejor manera de usar la anotación Spring Transactional.
Esta es una de las mejores prácticas que apliqué al desarrollar RevoGain , una aplicación web que le permite calcular las ganancias que obtuvo al operar con acciones, materias primas o criptomonedas usando Revolut .
Anotación transaccional de primavera
Desde la versión 1.0, Spring ofreció soporte para la gestión de transacciones basada en AOP que permitió a los desarrolladores definir los límites de las transacciones de forma declarativa.
Muy poco después, en la versión 1.2, Spring agregó soporte para la
La
-
yvalue– estos atributos se pueden usar para proporcionar unatransactionManagerreferencia que se utilizará al manejar la transacción para el bloque anotadoTransactionManager
-
– define cómo se propagan los límites de la transacción a otros métodos que serán llamados directa o indirectamente desde dentro del bloque anotado. La propagación predeterminada espropagationy significa que se inicia una transacción si ya no hay ninguna transacción disponible. De lo contrario, la transacción en curso será utilizada por el método de ejecución actual.REQUIRED
-
ytimeout– definir el número máximo de segundos que el método actual puede ejecutar antes de lanzar untimeoutStringTransactionTimedOutException
-
– define si la transacción actual es de solo lectura o de lectura y escritura.readOnly
-
yrollbackFor– definir una o másrollbackForClassNameclases para las cuales se retrotraerá la transacción actual. De forma predeterminada, una transacción se retrotrae si arroja unThrowableo unRuntimException, pero no si arroja un marcadoError.Exception
-
ynoRollbackFor– definir una o másnoRollbackForClassNameclases para las que no se revertirá la transacción actual. Normalmente, usaría estos atributos para una o másThrowableclases para las que no desea revertir una transacción determinada.RuntimException
¿A qué capa pertenece la anotación Spring Transactional?
La
No lo use en la capa web porque esto puede aumentar el tiempo de respuesta de la transacción de la base de datos y hacer que sea más difícil proporcionar el mensaje de error correcto para un error de transacción de base de datos determinado (p. ej., consistencia, interbloqueo, adquisición de bloqueo, bloqueo optimista).
La capa DAO (Objeto de acceso a datos) o Repositorio requiere una transacción a nivel de aplicación, pero esta transacción debe propagarse desde la capa de Servicio.
La mejor manera de usar la anotación Spring Transactional
En la capa de servicio, puede tener servicios relacionados con la base de datos y no relacionados con la base de datos. Si un caso de uso comercial determinado necesita mezclarlos, como cuando tiene que analizar una declaración determinada, crear un informe y guardar algunos resultados en la base de datos, es mejor si la transacción de la base de datos se inicia lo más tarde posible.
Por esta razón, podría tener un servicio de puerta de enlace no transaccional, como el siguiente
123456789101112131415dieciséis1718192021222324252627282930313233343536373839404142 |
@Service public class RevolutStatementService { @Transactional (propagation = Propagation.NEVER) public TradeGainReport processRevolutStocksStatement( MultipartFile inputFile, ReportGenerationSettings reportGenerationSettings) { return processRevolutStatement( inputFile, reportGenerationSettings, stocksStatementParser ); } private TradeGainReport processRevolutStatement( MultipartFile inputFile, ReportGenerationSettings reportGenerationSettings, StatementParser statementParser ) { ReportType reportType = reportGenerationSettings.getReportType(); String statementFileName = inputFile.getOriginalFilename(); long statementFileSize = inputFile.getSize(); StatementOperationModel stocksStatementModel = statementParser.parse( inputFile, reportGenerationSettings.getFxCurrency() ); int statementChecksum = stocksStatementModel.getStatementChecksum(); TradeGainReport report = generateReport(stocksStatementModel); if (!operationService.addStatementReportOperation( statementFileName, statementFileSize, statementChecksum, reportType.toOperationType() )) { triggerInsufficientCreditsFailure(report); } return report; } } |
El
Por lo tanto,
Solo el
1234567891011121314 |
@Service @Transactional (readOnly = true ) public class OperationService { @Transactional (isolation = Isolation.SERIALIZABLE) public boolean addStatementReportOperation( String statementFileName, long statementFileSize, int statementChecksum, OperationType reportType) { ... } } |
Tenga en cuenta que
Otra cosa que vale la pena señalar es que la clase está anotada con
Para los servicios transaccionales, es una buena práctica establecer el
Por ejemplo,
123456789101112131415 |
@Service @Transactional (readOnly = true ) public class UserService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { ... } @Transactional public void createUser(User user) { ... } } |
El
Por otro lado,
Otra gran ventaja de dividir los métodos de lectura-escritura y solo lectura es que podemos enrutarlos a diferentes nodos de la base de datos, como se explica en este artículo .
De esta forma, podemos escalar el tráfico de solo lectura aumentando la cantidad de nodos de réplica.
Impresionante, ¿verdad?
Conclusión
La anotación Spring Transactional es muy útil cuando se trata de definir los límites de transacción de los métodos comerciales.
Si bien los valores de atributos predeterminados se eligieron correctamente, es una buena práctica proporcionar configuraciones de nivel de clase y nivel de método para dividir los casos de uso entre casos de uso no transaccionales, transaccionales, de solo lectura y de lectura y escritura.