Trensações distribuídas envolvem operações que abrangem múltiplos sistemas ou bancos de dados. Elas garantem a atomicidade e a consistência dos dados em diferentes nós de uma rede distribuída, como transferências entre contas bancárias em instituições distintas.
O uso de JTA (Java Transaction API) em projetos geralmante surge da necessidade de gerenciar operações em múltiplos bancos de dados de forma transacional, assegurando que todas as modificações sejam concluídas com sucesso ou nenhuma delas seja aplicada. Este artigo demonstra como configurar o JTA com Spring para obter um gerenciamento unificado de transações entre diferentes fontes de dados.
A configuração básica pode ser implementada utilizando Spring e JTA. Abaixo, apresentamos um exemplo de arquivo de contexto do Spring (applicationContext-jta.xml):
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
4 xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
5 xsi:schemaLocation="http://www.springframework.org/schema/beans
6 http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
7 http://www.springframework.org/schema/context
8 http://www.springframework.org/schema/context/spring-context-2.5.xsd
9 http://www.springframework.org/schema/aop
10 http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
11 http://www.springframework.org/schema/tx
12 http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
13
14 <!-- Instância local do JOTM (Java Open Transaction Manager) -->
15 <bean id="transactionManagerJotm" class="org.springframework.transaction.jta.JotmFactoryBean" />
16
17 <!-- Gerenciador de transações JTA do Spring -->
18 <bean id="springTransactionManager"
19 class="org.springframework.transaction.jta.JtaTransactionManager">
20 <property name="userTransaction" ref="transactionManagerJotm"></property>
21 </bean>
22
23 <!-- Configuração do pool XAPool para o banco de dados 'dbSource1' -->
24 <bean id="dbPool1" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource"
25 destroy-method="shutdown">
26 <property name="dataSource">
27 <!-- DataSource XA interno -->
28 <bean class="org.enhydra.jdbc.standard.StandardXADataSource"
29 destroy-method="shutdown">
30 <property name="transactionManager" ref="transactionManagerJotm" />
31 <property name="driverName" value="com.mysql.cj.jdbc.Driver" />
32 <property name="url"
33 value="jdbc:mysql://localhost:3306/database1?useUnicode=true&characterEncoding=UTF-8" />
34 </bean>
35 </property>
36 <property name="user" value="dbuser1" />
37 <property name="password" value="passworddb1" />
38 </bean>
39
40 <!-- Configuração do pool XAPool para o banco de dados 'dbSource2' -->
41 <bean id="dbPool2" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource"
42 destroy-method="shutdown">
43 <property name="dataSource">
44 <bean class="org.enhydra.jdbc.standard.StandardXADataSource"
45 destroy-method="shutdown">
46 <property name="transactionManager" ref="transactionManagerJotm" />
47 <property name="driverName" value="com.mysql.cj.jdbc.Driver" />
48 <property name="url"
49 value="jdbc:mysql://localhost:3306/database2?useUnicode=true&characterEncoding=UTF-8" />
50 </bean>
51 </property>
52 <property name="user" value="dbuser2" />
53 <property name="password" value="passworddb2" />
54 </bean>
55
56 <!-- Spring JdbcTemplate para acessar o banco de dados 'dbSource1' -->
57 <bean id="dataSource1Template" class="org.springframework.jdbc.core.JdbcTemplate">
58 <property name="dataSource" ref="dbPool1"></property>
59 </bean>
60
61 <!-- Spring JdbcTemplate para acessar o banco de dados 'dbSource2' -->
62 <bean id="dataSource2Template" class="org.springframework.jdbc.core.JdbcTemplate">
63 <property name="dataSource" ref="dbPool2"></property>
64 </bean>
65 </beans>
Observação: A classe org.springframework.transaction.jta.JotmFactoryBean pode não estar disponível em versões mais recentes do Spring (como spring-tx-3.x ou superior). Pode ser necessário utilizar uma versão mais antiga do JAR (spring-tx-2.5.6.jar) ou implementar uma fábrica para essa classe, se necessário.
A seguir, um exemplo de código para a camada de acesso a dados (DAO) que utiliza essas configurações:
1 @Resource(name = "springTransactionManager")
2 private JtaTransactionManager transactionManager;
3
4 protected JdbcTemplate dataSource2JdbcTemplate;
5
6 /**
7 * JdbcTemplate para o DataSource 1.
8 */
9 protected JdbcTemplate dataSource1JdbcTemplate;
10
11 /**
12 * Getter para o JdbcTemplate do DataSource 2.
13 * @return O JdbcTemplate configurado.
14 */
15 public JdbcTemplate getDataSource2JdbcTemplate() {
16 return dataSource2JdbcTemplate;
17 }
18
19 /**
20 * Getter para o JdbcTemplate do DataSource 1.
21 * @return O JdbcTemplate configurado.
22 */
23 public JdbcTemplate getDataSource1JdbcTemplate() {
24 return dataSource1JdbcTemplate;
25 }
26
27 @Resource(name = "dataSource2Template")
28 public void setDataSource2JdbcTemplate(JdbcTemplate jdbcTemplate) {
29 this.dataSource2JdbcTemplate = jdbcTemplate;
30 }
31
32 @Resource(name = "dataSource1Template")
33 public void setDataSource1JdbcTemplate(JdbcTemplate jdbcTemplate) {
34 this.dataSource1JdbcTemplate = jdbcTemplate;
35 }
36
37 /**
38 * Atualiza registros em ambos os bancos de dados de forma transacional.
39 * @throws RollbackException Caso ocorra um erro e a transação precise ser revertida.
40 */
41 public void performTransactionalUpdate() {
42
43 if (this.transactionManager == null) {
44 System.err.println("Gerenciador de transação JTA não está configurado.");
45 return;
46 }
47
48 UserTransaction userTransaction = this.transactionManager.getUserTransaction();
49 if (userTransaction == null) {
50 System.err.println("UserTransaction não pôde ser obtido.");
51 return;
52 }
53
54 try {
55 userTransaction.begin();
56
57 // Executa a atualização no primeiro banco de dados
58 this.dataSource1JdbcTemplate.update("UPDATE users SET password = ? WHERE id = ?",
59 new Object[]{"newPassword1", 8});
60
61 // Executa a atualização no segundo banco de dados
62 this.dataSource2JdbcTemplate.update("UPDATE products SET name = ? WHERE code = ?",
63 new Object[]{"Updated Product Name", "a1b2c3d4-e5f6-7890-1234-567890abcdef"});
64
65 userTransaction.commit();
66
67 } catch (Exception e) {
68 System.err.println("Erro durante a transação distribuída. Realizando rollback: " + e.getMessage());
69 e.printStackTrace();
70 try {
71 userTransaction.rollback();
72 } catch (IllegalStateException | SecurityException | SystemException ex) {
73 System.err.println("Erro ao tentar reverter a transação: " + ex.getMessage());
74 }
75 }
76 }
Ao introduzir um erro proposital na segunda instrução SQL (por exemplo, um nome de tabela incorreto), o mecanismo de rollback do JTA será acionado. Isso garantirá que a operação no primeiro banco de dados seja desfeita, mantendo a consistência entre os sistemas.
Em testes anteriores com Spring, JPA e JTA, surgiram desafios ao mapear classes de entidade que, por padrão, poderiam ser associadas a todos os bancos de dados. A propriedade <property name="packagesToScan" value="nome.do.pacote"></property> no JPA pode ajudar a gerenciar quais pacotes de entidades são escaneados, mas outros problemas podem surgir e requerem investigação adicional.