Partilhar via


Noções básicas sobre transações XA

Baixar driver JDBC

O Microsoft JDBC Driver para SQL Server oferece suporte para transações distribuidas opcionais da Java Platform, Enterprise Edition/JDBC 2.0. As ligações JDBC obtidas da classe SQLServerXADataSource podem participar em ambientes de processamento de transações distribuídos padrão, como servidores de aplicações Java Platform e Enterprise Edition (Java EE).

Neste artigo, XA significa arquitetura estendida.

Advertência

O Microsoft JDBC Driver 4.2 (e superior) para SQL inclui novas opções de timeout para a funcionalidade existente de rollback automático de transações não preparadas. Consulte Configurar definições de timeout do lado do servidor para reverter automaticamente transações não preparadas mais adiante neste tópico para mais detalhes.

Observações

As classes para a implementação da transação distribuída são as seguintes:

Class Implementações Description
com.microsoft.sqlserver.jdbc.SQLServerXADataSource javax.sql.XADataSource A fábrica de classes para ligações distribuídas.
com.microsoft.sqlserver.jdbc.SQLServerXAResource javax.transaction.xa.XAResource O adaptador de recursos para o gestor de transações.

Observação

As ligações XA de transações distribuídas têm predefinido o nível de isolamento Read Committed.

Diretrizes e limitações ao utilizar transações XA

As seguintes diretrizes adicionais aplicam-se a transações fortemente acopladas:

  • Quando utiliza transações XA em conjunto com o Microsoft Distributed Transaction Coordinator (MS DTC), pode notar que a versão atual do MS DTC não suporta o comportamento de branch XA fortemente acoplado. Por exemplo, o MS DTC tem um mapeamento um-para-um entre um ID de transação de ramo XA (XID) e um ID de transação do MS DTC, e o trabalho realizado por ramos XA fracamente acoplados está isolado uns dos outros.

  • O MS DTC também suporta ramos XA fortemente acoplados, onde múltiplos ramos XA com o mesmo ID de Transação Global (GTRID) são mapeados para um único ID de transação do MS DTC. Este suporte permite que múltiplas ramificações XA fortemente acopladas vejam as alterações umas das outras no gestor de recursos, como o SQL Server.

  • Um indicador SSTRANSTIGHTLYCPLD permite que as aplicações usem transações XA fortemente acopladas, que têm diferentes IDs de ramo XA (BQUAL), mas têm o mesmo identificador global de transação (GTRID) e ID de formato (FormatID). Para utilizar essa funcionalidade, deve configurar o SSTRANSTIGHTLYCPLD no parâmetro flags do método XAResource.start:

    xaRes.start(xid, SQLServerXAResource.SSTRANSTIGHTLYCPLD);
    

Instruções de configuração

Os seguintes passos são necessários se quiser usar fontes de dados XA juntamente com o Microsoft Distributed Transaction Coordinator (MS DTC) para gerir transações distribuídas. Os passos de alto nível são:

  1. Certifique-se de que o serviço MS DTC está a funcionar e arranca automaticamente.
  2. Configurar componentes do lado do servidor.
  3. Configurar o timeout do lado do servidor (opcional).
  4. Conceder acesso aos utilizadores.

Execução do serviço MS DTC

O serviço MS DTC deve estar marcado como Automático no Service Manager para garantir que está a correr quando o serviço SQL Server é iniciado. Para ativar o MS DTC para transações XA, deve seguir estes passos:

No Windows Vista e versões posteriores:

  1. Selecione o botão Iniciar , escreva dcomcnfg na caixa de Pesquisa Iniciar e depois pressione ENTER para abrir Serviços de Componentes. Também pode escrever %windir%\system32\comexp.msc na caixa StartSearch para abrir os Serviços de Componentes.

  2. Expanda Serviços de Componentes, Computadores, O Meu Computador e depois o Coordenador de Transações Distribuídas.

  3. Clique com o botão direito no Local DTC e depois selecione Propriedades.

  4. Selecione o separador Segurança na caixa de diálogo Propriedades Locais do DTC .

  5. Seleciona a opção Ativar Transações XA e depois seleciona OK. Esta ação provoca o reinício do serviço MS DTC.

  6. Selecione OK novamente para fechar a caixa de diálogo Propriedades e depois feche Serviços de Componentes.

  7. Pare e depois reinicie o SQL Server para garantir que sincroniza com as alterações do MS DTC. (Este passo é opcional para SQL Server 2019 e SQL Server 2017 CU 16 e superiores.)

Configuração dos componentes de transações distribuídas JDBC

Os passos para configurar componentes do lado do servidor variam consoante a versão do seu servidor-alvo. Para verificar a versão do seu servidor, execute a consulta SELECT @@VERSION contra o servidor e veja o resultado. Para o SQL Server 2017 Atualização Cumulativa (CU) 16 e superiores, siga as instruções SQL Server 2017 CU16 e superiores. Para versões mais antigas do SQL Server, siga as instruções SQL Server 2017 CU15 e inferiores .

SQL Server 2017 CU16 e superiores

Para permitir que os componentes necessários realizem transações distribuídas XA usando o driver JDBC, execute o seguinte procedimento armazenado.

EXEC sp_sqljdbc_xa_install

Para desativar os componentes, execute o procedimento armazenado seguinte.

EXEC sp_sqljdbc_xa_uninstall

Salta para a secção Configurar as definições de timeout do lado do servidor para reverter automaticamente transações não preparadas .

SQL Server 2017 CU15 e versões inferiores

Observação

Isto aplica-se apenas ao SQL Server 2017 CU15 e inferiores. As funções fornecidas pela sqljdbc_xa.dll já estão incluídas no SQL Server 2017 CU16 e versões superiores.

Os componentes de transações distribuídas JDBC estão incluídos no diretório xa da instalação do driver JDBC. Estes componentes incluem os ficheiros xa_install.sql e sqljdbc_xa.dll. Se tiveres versões diferentes do driver JDBC em clientes diferentes, recomenda-se usar o sqljdbc_xa.dll mais recente do servidor.

Pode configurar os componentes de transações distribuídas do driver JDBC seguindo estes passos:

  1. Copie o novo sqljdbc_xa.dll do diretório de instalação do driver JDBC para o diretório Binn de todos os computadores SQL Server que participem em transações distribuídas.

    Observação

    Se estiver a usar transações XA com um SQL Server de 32 bits (aplicável apenas ao SQL Server 2014 ou anterior), use o ficheiro sqljdbc_xa.dll na pasta x86, mesmo que o SQL Server esteja instalado num processador x64. Se estiveres a usar transações XA com um SQL Server de 64 bits no processador x64, usa o ficheiro sqljdbc_xa.dll na pasta x64.

  2. Execute o script da base de dados xa_install.sql em cada instância do SQL Server que participe em transações distribuídas. Este script instala os procedimentos armazenados estendidos que são chamados por sqljdbc_xa.dll. Estes procedimentos armazenados alargados implementam suporte a transações distribuídas e XA para o Microsoft JDBC Driver for SQL Server. Precisas de executar este script como administrador da instância SQL Server.

  3. Para conceder permissões a um utilizador específico para participar em transações distribuídas com o driver JDBC, adicione o utilizador ao papel SqlJDBCXAUser.

Só podes configurar uma versão do assembly sqljdbc_xa.dll em cada instância do SQL Server de cada vez. As aplicações podem precisar de usar versões diferentes do driver JDBC para se ligar à mesma instância SQL Server usando a ligação XA. Nesse caso, sqljdbc_xa.dll, que vem com o driver JDBC mais recente, deve ser instalado na instância do SQL Server.

Existem três formas de verificar a versão de sqljdbc_xa.dll atualmente instalada na instância do SQL Server:

  1. Abra o diretório LOG do computador SQL Server que participa em transações distribuídas. Selecione e abra o ficheiro "ERRORLOG" do SQL Server. Pesquise pela frase "Usar 'SQLJDBC_XA.dll' versão ..." no ficheiro "ERRORLOG".

  2. Abra o diretório Binn do computador SQL Server que participa em transações distribuídas. Selecione o assembly sqljdbc_xa.dll.

    • No Windows Vista ou versões posteriores: Clique com o botão direito sqljdbc_xa.dll e depois selecione Propriedades. Depois seleciona o separador Detalhes . O campo Versão do Ficheiro mostra a versão do sqljdbc_xa.dll que está atualmente instalada na instância do SQL Server.
  3. Defina a funcionalidade de registo conforme mostrado no exemplo de código na secção seguinte. Procure a frase "Server XA DLL version:..." no ficheiro de log de saída.

Atualizar o sqljdbc_xa.dll

Observação

Isto aplica-se apenas ao SQL Server 2017 CU15 e inferiores. As funções fornecidas pela sqljdbc_xa.dll já estão incluídas no SQL Server 2017 CU16 e versões superiores.

Quando instalares uma nova versão do driver JDBC, deves também usar sqljdbc_xa.dll da nova versão para atualizar sqljdbc_xa.dll no servidor.

Importante

Deves atualizar sqljdbc_xa.dll durante uma janela de manutenção ou quando não houver transações DTC MS em curso.

  1. Descarregue sqljdbc_xa.dll usando o comando Transact-SQL:

    DBCC sqljdbc_xa (FREE)
    
  2. Copie o novo sqljdbc_xa.dll do diretório de instalação do driver JDBC para o diretório Binn de todos os computadores SQL Server que participem em transações distribuídas.

    A nova DLL é carregada quando um procedimento estendido em sqljdbc_xa.dll é chamado. Não precisas de reiniciar o SQL Server para carregar as novas definições.

Configuração dos parâmetros de timeout no servidor para a reversão automática de transações não preparadas

Existem duas definições de registo (valores DWORD) para controlar o comportamento de timeout das transações distribuídas:

  • XADefaultTimeout (em segundos): O valor padrão de timeout a ser usado quando o utilizador não especifica nenhum timeout. O padrão é 0.

  • XAMaxTimeout (em segundos): O valor máximo do timeout que um utilizador pode definir. O padrão é 0.

Estas definições são específicas da instância do SQL Server e devem ser criadas sob a seguinte chave de registo:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL<version>.<instance_name>\XATimeout

Observação

Para SQL Server de 32 bits a correr em máquinas de 64 bits (aplicável apenas ao SQL Server 2014 e versões anteriores), as definições do registo devem ser criadas sob a seguinte tecla: HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SQL Server\MSSQL<version>.<instance_name>\XATimeout

Um valor de timeout é definido para cada transação quando esta é iniciada e o SQL Server reverte a transação se o timeout expirar. O timeout é determinado com base nestas definições do registo e no que o utilizador especificou através do XAResource.setTransactionTimeout(). Alguns exemplos de como estes valores de timeout são interpretados da seguinte forma:

  • XADefaultTimeout = 0, XAMaxTimeout = 0

    Isto significa que não é usado um timeout padrão e não é aplicado um limite máximo aos clientes. Neste caso, as transações têm um timeout apenas se o cliente definir um timeout usando XAResource.setTransactionTimeout.

  • XADefaultTimeout = 60, XAMaxTimeout = 0

    Isto significa que todas as transações têm um timeout de 60 segundos se o cliente não especificar nenhum timeout. Se o cliente especificar um timeout, então esse valor de timeout é utilizado. Não é aplicado um valor máximo para o timeout.

  • XADefaultTimeout = 30, XAMaxTimeout = 60

    Isto significa que todas as transações têm um timeout de 30 segundos se o cliente não especificar nenhum timeout. Se o cliente especificar algum timeout, então o timeout do cliente é usado desde que seja inferior a 60 segundos (o valor máximo).

  • XADefaultTimeout = 0, XAMaxTimeout = 30

    Isto significa que todas as transações têm um timeout de 30 segundos (o valor máximo) se o cliente não especificar nenhum timeout. Se o cliente especificar algum timeout, então o timeout do cliente é usado desde que seja inferior a 30 segundos (o valor máximo).

Configuração dos papéis definidos pelo utilizador

Para conceder permissões a um utilizador específico para participar em transações distribuídas com o driver JDBC, adicione o utilizador ao papel SqlJDBCXAUser. Por exemplo, use o seguinte código Transact-SQL para adicionar um utilizador chamado 'shelly' (utilizador de login padrão SQL chamado 'shelly') ao papel SqlJDBCXAUser:

USE master
GO
EXEC sp_grantdbaccess 'shelly', 'shelly'
GO
EXEC sp_addrolemember [SqlJDBCXAUser], 'shelly'

Os papéis definidos pelo utilizador em SQL são definidos por cada base de dados. Para criar a sua própria função para fins de segurança, tem de definir a função em cada base de dados e adicionar utilizadores individualmente para cada base de dados. O papel SqlJDBCXAUser está estritamente definido na base de dados 'master' porque é usado para conceder acesso aos procedimentos armazenados estendidos SQL JDBC que residem na 'master'. Primeiro deves conceder acesso aos utilizadores individuais ao master, e depois conceder-lhes acesso à função SqlJDBCXAUser enquanto estiveres ligado à base de dados master.

Example

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 = "<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);
    }
}

Consulte também

Realização de transações com o driver JDBC