Suporte a transações na integração de primavera
1. Visão geral
Neste tutorial, daremos uma olhada no suporte a transações emSpring Integration framework.
2. Transações em fluxos de mensagens
O Spring fornece suporte para sincronizar recursos com transações desde as versões mais antigas. Costumamos usá-lo para sincronizar transações gerenciadas por vários gerenciadores de transações.
Por outro lado, também temos casos de uso mais complexos nos fluxos de mensagens. Eles incluem a sincronização de recursos não transacionais, bem como vários tipos de recursos transacionais.
Normalmente, os fluxos de mensagens podem ser iniciados por dois tipos diferentes de mecanismos.
2.1. Fluxos de mensagens iniciados por um processo do usuário
Alguns fluxos de mensagens dependem da iniciação de processos de terceiros, como acionar uma mensagem em algum canal de mensagem ou invocar um método de gateway de mensagem.
We configure transaction support for these flows through Spring’s standard transaction support. Os fluxos não precisam ser configurados explicitamente pelo Spring Integration para suportar transações. O fluxo de mensagens do Spring Integration naturalmente respeita a semântica transacional dos componentes do Spring.
Por exemplo, podemos anotar umServiceActivator ou seu método com@Transactional:
@Transactional
public class TxServiceActivator {
@Autowired
private JdbcTemplate jdbcTemplate;
public void storeTestResult(String testResult) {
this.jdbcTemplate.update("insert into STUDENT values(?)", testResult);
log.info("Test result is stored: {}", testResult);
}
}
Podemos executar o métodostoreTestResult de qualquer componente e o contexto transacional será aplicado normalmente. Com essa abordagem, temos controle total sobre a configuração da transação.
2.2. Fluxos de mensagens iniciados por um processo daemon
Costumamos usar esse tipo de fluxo de mensagens para automação. Por exemplo, umPoller pesquisando uma fila de mensagens para iniciar um novo fluxo de mensagens com a mensagem pesquisada ou um planejador que programa o processo criando uma nova mensagem e iniciando um fluxo de mensagens em um horário predefinido.
Em essência, esses são fluxos baseados em acionador iniciados por um processo de acionamento (um processo daemon). For these flows, we have to provide some transaction configuration to create a transaction context whenever a new message flow begins.
Por meio da configuração, delegamos os fluxos ao suporte de transação existente do Spring.
Vamos nos concentrar no suporte à transação para esse tipo de fluxo de mensagens no restante do artigo.
3. Suporte de transação de Poller
Poller é um componente comum em fluxos de integração. Recupera periodicamente os dados de várias fontes e os repassa pela cadeia de integração.
O Spring Integration fornece suporte transacional para os pollers prontos para uso. Sempre que configuramos um componentePoller, podemos fornecer a configuração transacional:
@Bean
@InboundChannelAdapter(value = "someChannel", poller = @Poller(value = "pollerMetadata"))
public MessageSource someMessageSource() {
...
}
@Bean
public PollerMetadata pollerMetadata() {
return Pollers.fixedDelay(5000)
.advice(transactionInterceptor())
.transactionSynchronizationFactory(transactionSynchronizationFactory)
.get();
}
private TransactionInterceptor transactionInterceptor() {
return new TransactionInterceptorBuilder()
.transactionManager(txManager)
.build();
}
Temos que fornecer uma referência para umTransactionManagere umTransactionSynchronizationFactory personalizado, ou podemos contar com os padrões. Internamente, a transação nativa do Spring encerra o processo. Como resultado, todos os fluxos de mensagens iniciados por esse pesquisador são transacionais.
4. Limites de transação
Quando uma transação é iniciada, o contexto da transação é sempre vinculado ao encadeamento atual. Independentemente de quantos terminais e canais possamos ter em nosso fluxo de mensagens,our transaction context will always be preserved as long as the flow lives in the same thread.
Se quebrarmos iniciando um novo encadeamento em algum serviço, quebraremos o limite deTransactional também. Essencialmente, a transação terminará nesse ponto.
Se uma transferência bem-sucedida ocorreu entre os encadeamentos, o fluxo será considerado um sucesso. Isso confirmará a transação nesse ponto, mas o fluxo continuará e ainda pode resultar emException em algum lugar no fluxo abaixo.
Consequentemente, esseException pode voltar ao iniciador do fluxo para que a transação possa terminar em um rollback. É por isso quewe have to use transactional channels at any point where a thread boundary can be broken.
Por exemplo, devemos usar JMS, JDBC ou algum outro canal transacional.
5. Sincronização de transação
Em alguns casos de uso, é benéfico sincronizar determinadas operações com uma transação que engloba todo o fluxo.
Por exemplo, demonstraremos como usar umPoller que lê um arquivo de entrada e, com base em seu conteúdo, realiza uma atualização do banco de dados. Quando a operação do banco de dados é concluída, ele também renomeia o arquivo, dependendo do sucesso da operação.
Antes de passarmos para o exemplo,it is crucial to understand that this approach synchronizes the operations on the filesystem with a transaction. It does not make the filesystem, which is not inherently transactional, actually become transactional.
A transação inicia antes da pesquisa e confirma ou retrocede quando o fluxo é concluído, seguido pela operação sincronizada no sistema de arquivos.
Primeiro, definimos umInboundChannelAdapter com umPoller simples:
@Bean
@InboundChannelAdapter(value = "inputChannel", poller = @Poller(value = "pollerMetadata"))
public MessageSource fileReadingMessageSource() {
FileReadingMessageSource sourceReader = new FileReadingMessageSource();
sourceReader.setDirectory(new File(INPUT_DIR));
sourceReader.setFilter(new SimplePatternFileListFilter(FILE_PATTERN));
return sourceReader;
}
@Bean
public PollerMetadata pollerMetadata() {
return Pollers.fixedDelay(5000)
.advice(transactionInterceptor())
.transactionSynchronizationFactory(transactionSynchronizationFactory)
.get();
}
Poller contém uma referência aTransactionManager, conforme explicado anteriormente. Além disso, ele também contém uma referência aoTransactionSynchronizationFactory. Este componente fornece o mecanismo para sincronização das operações do sistema de arquivos com a transação:
@Bean
public TransactionSynchronizationFactory transactionSynchronizationFactory() {
ExpressionEvaluatingTransactionSynchronizationProcessor processor =
new ExpressionEvaluatingTransactionSynchronizationProcessor();
SpelExpressionParser spelParser = new SpelExpressionParser();
processor.setAfterCommitExpression(
spelParser.parseExpression(
"payload.renameTo(new java.io.File(payload.absolutePath + '.PASSED'))"));
processor.setAfterRollbackExpression(
spelParser.parseExpression(
"payload.renameTo(new java.io.File(payload.absolutePath + '.FAILED'))"));
return new DefaultTransactionSynchronizationFactory(processor);
}
Se a transação for confirmada,TransactionSynchronizationFactory renomeará o arquivo acrescentando “.PASSED” ao nome do arquivo. No entanto, se retroceder, acrescentará ".FAILED".
OInputChannel transforma a carga útil usandoFileToStringTransformere delega paratoServiceChannel. Este canal está vinculado aoServiceActivator:
@Bean
public MessageChannel inputChannel() {
return new DirectChannel();
}
@Bean
@Transformer(inputChannel = "inputChannel", outputChannel = "toServiceChannel")
public FileToStringTransformer fileToStringTransformer() {
return new FileToStringTransformer();
}
ServiceActivator lê o arquivo de entrada, que contém os resultados do exame do aluno. Ele grava o resultado no banco de dados. Se um resultado contém a string “falha”, ele lançaException, o que faz com que o banco de dados seja revertido:
@ServiceActivator(inputChannel = "toServiceChannel")
public void serviceActivator(String payload) {
jdbcTemplate.update("insert into STUDENT values(?)", payload);
if (payload.toLowerCase().startsWith("fail")) {
log.error("Service failure. Test result: {} ", payload);
throw new RuntimeException("Service failure.");
}
log.info("Service success. Test result: {}", payload);
}
Depois que a operação do banco de dados é confirmada ou revertida com sucesso, oTransactionSynchronizationFactory sincroniza a operação do sistema de arquivos com seu resultado.
6. Conclusão
Neste artigo, explicamos o suporte a transações na estruturaSpring Integration. Além disso, demonstramos como sincronizar a transação com operações em um recurso não transacional, como o sistema de arquivos.
O código-fonte completo para o exemplo está disponívelover on GitHub.