Noções básicas sobre transações XA
O Microsoft JDBC Driver para SQL Server oferece suporte a transações distribuídas opcionais para Plataforma Java, Enterprise Edition/JDBC 2.0. Conexões JDBC obtidas da classe SQLServerXADataSource podem participar de ambientes de processamento de transações distribuídas padrão como os servidores de aplicativos da Plataforma Java, Java EE (Enterprise Edition).
Neste artigo, o XA significa arquitetura estendida.
Aviso
O Microsoft JDBC Driver 4.2 (e superiores) for SQL inclui novas opções de tempo limite para o recurso existente para reversão automática de transações não preparadas. Confira Configuração das definições de tempo limite do servidor para reversão automática de transações não preparadas posteriormente neste tópico para encontrar mais detalhes.
Comentários
As classes para a implementação das transações distribuídas são as seguintes:
Classe | Implementa | Descrição |
---|---|---|
com.microsoft.sqlserver.jdbc.SQLServerXADataSource | javax.sql.XADataSource | A fábrica de classes para conexões distribuídas. |
com.microsoft.sqlserver.jdbc.SQLServerXAResource | javax.transaction.xa.XAResource | O adaptador de recursos para o gerenciador de transações. |
Observação
As conexões de transações distribuídas XA usam como padrão o nível de isolamento Leitura Confirmada.
Diretrizes e limitações ao usar transações XA
As diretrizes adicionais a seguir se aplicam a transações firmemente acopladas:
Ao usar transações XA junto com o Coordenador de Transações Distribuídas (MS DTC), talvez você observe que a versão atual do MS DTC não oferece suporte ao comportamento de ramificações XA firmemente acopladas. Por exemplo, o MS DTC tem um mapeamento um-para-um entre uma ID de transação de ramificação XA (XID) e uma ID de transação do MS DT,C e o trabalho que as ramificações XA fracamente acopladas executam é isolado um do outro.
O MS DTC também é compatível com branches XA firmemente acoplados em que vários branches XA com a mesma GTRID (ID de transação global) são mapeados para uma única ID de transação do MS DTC. Esse suporte permite que vários branches XA bem acoplados reconheçam as alterações feitas no gerenciador de recursos, como SQL Server.
Um sinalizador SSTRANSTIGHTLYCPLD permite que os aplicativos usem as transações XA firmemente acopladas com IDs de transação de branch XA (BQUAL) diferentes, mas a mesma GTRID (ID de transação global) e a mesma FormatID (ID de formato). Para usar esse recurso, você deve definir o SSTRANSTIGHTLYCPLD no parâmetro de sinalizadores do método XAResource.start:
xaRes.start(xid, SQLServerXAResource.SSTRANSTIGHTLYCPLD);
Instruções de configuração
As etapas a seguir serão necessárias se você desejar usar fontes de dados XA junto com o MS DTC para tratar de transações distribuídas. As etapas de alto nível são:
- Verifique se o serviço MS DTC está em execução e é iniciado automaticamente.
- Configure os componentes do lado do servidor.
- Configure o tempo limite do lado do servidor (opcional).
- Conceda acesso aos usuários.
Executando o serviço MS DTC
O serviço MS DTC deve ser marcado como Automático no Service Manager para garantir que ele esteja em execução quando o serviço SQL Server for iniciado. Para habilitar o MS DTC para transações XA, você deve seguir estas etapas:
No Windows Vista e versão posterior:
Clique no botão Iniciar, digite dcomcnfg na caixa Iniciar Pesquisa e pressione ENTER para abrir Serviços de Componentes. Você também pode digitar
%windir%\system32\comexp.msc
na caixa IniciarPesquisa para abrir os Serviços de Componentes.Expanda Serviços de Componentes, Computadores, Meu Computador e Coordenador de Transações Distribuídas.
Clique com o botão direito do mouse em DTC Local e selecione Propriedades.
Selecione a guia Segurança na caixa de diálogo Propriedades de controle DTC local.
Marque a caixa de seleção Habilitar transações XA e selecione OK. Essa ação causa uma reinicialização do serviço MS DTC.
Selecione OK novamente para fechar a caixa de diálogo Propriedades e feche Serviços de Componentes.
Pare e reinicie o SQL Server para assegurar que ele seja sincronizado com as alterações do MS DTC. (Esta etapa é opcional para o SQL Server 2019 e para o SQL Server 2017 CU 16 e superior.)
Configurando os componentes de transações distribuídas do JDBC
As etapas para configurar os componentes do lado do servidor diferem dependendo da versão do servidor de destino. Para verificar a versão do servidor, execute a consulta SELECT @@VERSION
no servidor e exiba a saída. Para SQL Server CU (Atualização Cumulativa) 16 e superior 2017, siga as instruções do SQL Server 2017 CU16 e superior. Para versões do SQL Server mais antigas, siga as instruções do SQL Server 2017 CU15 e inferiores.
SQL Server 2017 CU16 e superior
Para habilitar os componentes necessários para executar transações distribuídas XA usando o driver JDBC, execute o procedimento armazenado a seguir.
EXEC sp_sqljdbc_xa_install
Para desabilitar os componentes, execute o procedimento armazenado a seguir.
EXEC sp_sqljdbc_xa_uninstall
Pule para a seção Definir as configurações de tempo limite do lado do servidor para reversão automática de transações não preparadas.
SQL Server 2017 CU15 e inferiores
Observação
Isso apenas se aplica ao SQL Server 2017 CU15 e versões inferiores. As funções fornecidas por sqljdbc_xa.dll já estão incluídas no SQL Server 2017 CU16 e versões superiores.
Os componentes de transações distribuídas do JDBC são incluídos no diretório xa da instalação do driver JDBC. Esses componentes incluem os arquivos xa_install.sql e sqljdbc_xa.dll. Se você tem versões diferentes do driver JDBC em clientes diferentes, é recomendável usar o sqljdbc_xa.dll mais recente no servidor.
Você pode configurar os componentes de transações distribuídas do driver JDBC seguindo estas etapas:
Copie o novo sqljdbc_xa.dll do diretório de instalação do driver JDBC para o diretório Binn de cada computador SQL Server que participa de transações distribuídas.
Observação
Se você estiver usando transações XA com um SQL Server de 32 bits (aplicável apenas ao SQL Server 2014 ou anterior), use o arquivo sqljdbc_xa.dll na pasta x86, mesmo que o SQL Server esteja instalado em um processador x64. Se estiver usando transações XA com um SQL Server de 64 bits no processador x64, use o arquivo sqljdbc_xa.dll na pasta x64.
Execute o script do banco de dados xa_install.sql em cada Instância do SQL Server que participa de transações distribuídas. Esse script instala os procedimentos armazenados estendidos que são chamados por sqljdbc_xa.dll. Esses procedimentos armazenados estendidos implementam o suporte a transações distribuídas e XA para o Microsoft JDBC Driver para SQL Server. Você precisará executar este script como administrador da Instância do SQL Server.
Para conceder permissões para um usuário específico participar de transações distribuídas com o driver JDBC, adicione o usuário à função SqlJDBCXAUser.
Só é possível configurar uma versão do assembly sqljdbc_xa.dll de cada vez em cada instância do SQL Server. Talvez os aplicativos precisem usar versões diferentes do driver JDBC para conectarem-se à mesma instância do SQL Server usando a conexão XA. Nesse caso, o arquivo sqljdbc_xa.dll, que vem com o driver JDBC mais recente, deve ser instalado na instância do SQL Server.
Há três maneiras de verificar qual versão do sqljdbc_xa.dll está instalada no momento na instância do SQL Server:
Abra o diretório LOG do computador SQL Server que participa de transações distribuídas. Selecione e abra o arquivo "ERRORLOG" do SQL Server. Procure a frase "Using 'SQLJDBC_XA.dll' version ..." no arquivo "ERRORLOG".
Abra o diretório Binn do computador SQL Server que participa de transações distribuídas. Selecione o assembly sqljdbc_xa.dll.
- No Windows Vista ou posterior: Clique com o botão direito do mouse em sqljdbc_xa.dll e selecione Propriedades. Em seguida, selecione a guia Detalhes. O campo Versão do Arquivo mostra a versão de sqljdbc_xa.dll que está instalada no momento na Instância do SQL Server.
Defina a funcionalidade de registro em log conforme mostrado no exemplo de código na próxima seção. Procure a frase "Server XA DLL version:..." no arquivo de log de saída.
Atualizando o sqljdbc_xa.dll
Observação
Isso apenas se aplica ao SQL Server 2017 CU15 e versões inferiores. As funções fornecidas por sqljdbc_xa.dll já estão incluídas no SQL Server 2017 CU16 e versões superiores.
Ao instalar uma nova versão do driver JDBC, você também deve usar o arquivo sqljdbc_xa.dll da nova versão para atualizar sqljdbc_xa.dll no servidor.
Importante
Você deve atualizar o sqljdbc_xa.dll durante a janela de manutenção ou quando não houver nenhuma transação do MS DTC em andamento.
Descarregue sqljdbc_xa.dll usando o comando Transact-SQL:
DBCC sqljdbc_xa (FREE)
Copie o novo sqljdbc_xa.dll do diretório de instalação do driver JDBC para o diretório Binn de cada computador SQL Server que participa de transações distribuídas.
A nova DLL é carregada quando um procedimento estendido em sqljdbc_xa.dll for chamado. Você não precisa reiniciar o SQL Server para carregar as novas definições.
Configurando as definições de tempo limite do servidor para reversão automática de transações não preparadas
Há duas configurações de Registro (valores DWORD) para controlar o comportamento de tempo limite de transações distribuídas:
XADefaultTimeout
(em segundos): o valor de tempo limite padrão a ser usado quando o usuário não especifica nenhum tempo limite. O padrão é 0.XAMaxTimeout
(em segundos): O valor máximo do tempo limite que um usuário pode definir. O padrão é 0.
Essas configurações são específicas da instância do SQL Server e devem ser criadas na seguinte chave do Registro:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL<version>.<instance_name>\XATimeout
Observação
Para SQL Server de 32 bits em execução em máquinas de 64 bits (aplicável apenas ao SQL Server 2014 e anteriores), as configurações do registro devem ser criadas na seguinte chave: HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SQL Server\MSSQL<version>.<instance_name>\XATimeout
Um valor de tempo limite é definido para cada transação quando ela é iniciada e o SQL Server reverte a transação se o tempo limite expirar. O tempo limite é determinado dependendo dessas definições de Registro e daquelas que o usuário tiver especificado por meio de XAResource.setTransactionTimeout(). A seguir estão alguns exemplos de como esses valores de tempo limite são interpretados:
XADefaultTimeout = 0
,XAMaxTimeout = 0
Significa que nenhum tempo limite padrão é usado e nenhum tempo limite máximo é aplicado aos clientes. Nesse caso, as transações terá um tempo limite somente se o cliente definir um tempo limite usando XAResource.setTransactionTimeout.
XADefaultTimeout = 60
,XAMaxTimeout = 0
Significa que todas as transações têm o tempo limite de 60 segundos se o cliente não especificar nenhum tempo limite. Se o cliente especificar o tempo limite, esse valor de tempo limite é usado. Nenhum valor máximo para o tempo limite é aplicado.
XADefaultTimeout = 30
,XAMaxTimeout = 60
Significa que todas as transações têm o tempo limite de 30 segundos se o cliente não especificar nenhum tempo limite. Se o cliente especificar um tempo limite, ele é usado desde que seja inferior a 60 segundos (o valor máximo).
XADefaultTimeout = 0
,XAMaxTimeout = 30
Significa que todas as transações têm o tempo limite de 30 segundos (valor máximo) se o cliente não especificar nenhum tempo limite. Se o cliente especificar um tempo limite, ele é usado desde que seja inferior a 30 segundos (o valor máximo).
Configurando as funções definidas pelo usuário
Para conceder permissões para um usuário específico participar de transações distribuídas com o driver JDBC, adicione o usuário à função SqlJDBCXAUser. Por exemplo, use o seguinte código Transact-SQL para adicionar um usuário chamado "shelly" (usuário de logon padrão SQL chamado "shelly") à função SqlJDBCXAUser:
USE master
GO
EXEC sp_grantdbaccess 'shelly', 'shelly'
GO
EXEC sp_addrolemember [SqlJDBCXAUser], 'shelly'
As funções definidas pelo usuário do SQL são definidas por banco de dados. Para criar sua função para fins de segurança, você tem que definir a função em cada banco de dados e adicionar os usuários para cada banco de dados. A função SqlJDBCXAUser é definida estritamente no banco de dados mestre porque é usada para conceder acesso aos procedimentos armazenados estendidos do SQL JDBC que residem no mestre. Primeiro, você precisa conceder acessos de usuário individuais ao mestre e conceder a eles acesso à função SqlJDBCXAUser enquanto você estiver conectado ao banco de dados mestre.
Exemplo
import java.net.Inet4Address;
import java.sql.*;
import java.util.Random;
import javax.sql.XAConnection;
import javax.transaction.xa.*;
import com.microsoft.sqlserver.jdbc.*;
public class testXA {
public static void main(String[] args) throws Exception {
// Create variables for the connection string.
String prefix = "jdbc:sqlserver://";
String serverName = "localhost";
int portNumber = 1433;
String databaseName = "AdventureWorks";
String user = "UserName";
String password = "*****";
String connectionUrl = prefix + serverName + ":" + portNumber + ";encrypt=true;databaseName=" + databaseName + ";user="
+ user + ";password=" + password;
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
try (Connection con = DriverManager.getConnection(connectionUrl); Statement stmt = con.createStatement()) {
stmt.executeUpdate("CREATE TABLE XAMin (f1 int, f2 varchar(max))");
}
// Create the XA data source and XA ready connection.
SQLServerXADataSource ds = new SQLServerXADataSource();
ds.setUser(user);
ds.setPassword(password);
ds.setServerName(serverName);
ds.setPortNumber(portNumber);
ds.setDatabaseName(databaseName);
XAConnection xaCon = ds.getXAConnection();
try (Connection con = xaCon.getConnection()) {
// Get a unique Xid object for testing.
XAResource xaRes = null;
Xid xid = null;
xid = XidImpl.getUniqueXid(1);
// Get the XAResource object and set the timeout value.
xaRes = xaCon.getXAResource();
xaRes.setTransactionTimeout(0);
// Perform the XA transaction.
System.out.println("Write -> xid = " + xid.toString());
xaRes.start(xid, XAResource.TMNOFLAGS);
PreparedStatement pstmt = con.prepareStatement("INSERT INTO XAMin (f1,f2) VALUES (?, ?)");
pstmt.setInt(1, 1);
pstmt.setString(2, xid.toString());
pstmt.executeUpdate();
// Commit the transaction.
xaRes.end(xid, XAResource.TMSUCCESS);
xaRes.commit(xid, true);
}
xaCon.close();
// Open a new connection and read back the record to verify that it worked.
try (Connection con = DriverManager.getConnection(connectionUrl); Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM XAMin")) {
rs.next();
System.out.println("Read -> xid = " + rs.getString(2));
stmt.executeUpdate("DROP TABLE XAMin");
}
}
}
class XidImpl implements Xid {
public int formatId;
public byte[] gtrid;
public byte[] bqual;
public byte[] getGlobalTransactionId() {
return gtrid;
}
public byte[] getBranchQualifier() {
return bqual;
}
public int getFormatId() {
return formatId;
}
XidImpl(int formatId, byte[] gtrid, byte[] bqual) {
this.formatId = formatId;
this.gtrid = gtrid;
this.bqual = bqual;
}
public String toString() {
int hexVal;
StringBuffer sb = new StringBuffer(512);
sb.append("formatId=" + formatId);
sb.append(" gtrid(" + gtrid.length + ")={0x");
for (int i = 0; i < gtrid.length; i++) {
hexVal = gtrid[i] & 0xFF;
if (hexVal < 0x10)
sb.append("0" + Integer.toHexString(gtrid[i] & 0xFF));
else
sb.append(Integer.toHexString(gtrid[i] & 0xFF));
}
sb.append("} bqual(" + bqual.length + ")={0x");
for (int i = 0; i < bqual.length; i++) {
hexVal = bqual[i] & 0xFF;
if (hexVal < 0x10)
sb.append("0" + Integer.toHexString(bqual[i] & 0xFF));
else
sb.append(Integer.toHexString(bqual[i] & 0xFF));
}
sb.append("}");
return sb.toString();
}
// Returns a globally unique transaction id.
static byte[] localIP = null;
static int txnUniqueID = 0;
static Xid getUniqueXid(int tid) {
Random rnd = new Random(System.currentTimeMillis());
txnUniqueID++;
int txnUID = txnUniqueID;
int tidID = tid;
int randID = rnd.nextInt();
byte[] gtrid = new byte[64];
byte[] bqual = new byte[64];
if (null == localIP) {
try {
localIP = Inet4Address.getLocalHost().getAddress();
} catch (Exception ex) {
localIP = new byte[] {0x01, 0x02, 0x03, 0x04};
}
}
System.arraycopy(localIP, 0, gtrid, 0, 4);
System.arraycopy(localIP, 0, bqual, 0, 4);
// Bytes 4 -> 7 - unique transaction id.
// Bytes 8 ->11 - thread id.
// Bytes 12->15 - random number generated by using seed from current time in milliseconds.
for (int i = 0; i <= 3; i++) {
gtrid[i + 4] = (byte) (txnUID % 0x100);
bqual[i + 4] = (byte) (txnUID % 0x100);
txnUID >>= 8;
gtrid[i + 8] = (byte) (tidID % 0x100);
bqual[i + 8] = (byte) (tidID % 0x100);
tidID >>= 8;
gtrid[i + 12] = (byte) (randID % 0x100);
bqual[i + 12] = (byte) (randID % 0x100);
randID >>= 8;
}
return new XidImpl(0x1234, gtrid, bqual);
}
}