Présentation des transactions XA

Télécharger le pilote JDBC

Le Pilote Microsoft JDBC pour SQL Server prend en charge les transactions distribuées facultatives Java Platform, Enterprise Edition/JDBC 2.0. Les connexions JDBC obtenues à partir de la classe SQLServerXADataSource peuvent participer aux environnements de traitement des transactions distribuées standard, tels que les serveurs d’applications Java Platform, Enterprise Edition (Java EE).

Dans cet article, XA signifie « Extended Architecture », soit architecture étendue.

Avertissement

Microsoft JDBC Driver 4.2 pour SQL (et versions ultérieures) inclut de nouvelles options de délai d’attente pour la fonctionnalité existante de restauration automatique des transactions non préparées. Pour plus d’informations, consultez Configurer les paramètres du délai d’attente côté serveur pour la restauration automatique des transactions non préparées plus loin dans cette rubrique.

Notes

Les classes pour l'implémentation des transactions distribuées sont les suivantes :

Classe Implémentations Description
com.microsoft.sqlserver.jdbc.SQLServerXADataSource javax.sql.XADataSource Fabrique de classe pour les connexions distribuées.
com.microsoft.sqlserver.jdbc.SQLServerXAResource javax.transaction.xa.XAResource Adaptateur de ressources pour le gestionnaire de transactions.

Notes

Les connexions de transactions distribuées XA sont définies par défaut au niveau d'isolation Read Committed.

Recommandations et limitations relatives à l'utilisation de transactions XA

Les lignes directrices supplémentaires suivantes s'appliquent aux transactions fortement couplées :

  • Lorsque vous utilisez des transactions XA avec Microsoft Distributed Transaction Coordinator (MS DTC), vous pouvez remarquer que la version actuelle de MS DTC ne prend pas en charge le comportement de branche XA fortement couplée. Par exemple, MS DTC a un mappage un-à-un entre un ID de transaction de branche XA (XID) et un ID de transaction MS DTC et le travail effectué par les branches XA couplées de manière lâche est isolé.

  • MS DTC prend également en charge les branches XA fortement couplées quand plusieurs branches XA portant le même ID de transaction globale (GTRID) sont mappées à un seul ID de transaction MS DTC. Cette prise en charge permet à plusieurs branches XA fortement couplées de voir les changements apportés à chacune d’elles dans le gestionnaire de ressources, par exemple SQL Server.

  • Un indicateur SSTRANSTIGHTLYCPLD permet aux applications d’utiliser les transactions XA fortement couplées qui ont des ID de transaction de branche XA (BQUAL) différents mais le même ID de transaction global (GTRID) et ID de format (FormatID). Pour pouvoir utiliser cette fonctionnalité, vous devez définir SSTRANSTIGHTLYCPLD sur le paramètre flags de la méthode XAResource.start :

    xaRes.start(xid, SQLServerXAResource.SSTRANSTIGHTLYCPLD);
    

Instructions de configuration

Les étapes suivantes sont requises si vous souhaitez utiliser des sources de données XA avec Microsoft Distributed Transaction Coordinator (MS DTC) pour manipuler des transactions distribuées. Les étapes principales sont les suivantes :

  1. Vérifiez que le service MS DTC est en cours d’exécution et démarre automatiquement.
  2. Configurez les composants côté serveur.
  3. Configurez le délai d’attente côté serveur (facultatif).
  4. Octroyez l’accès aux utilisateurs.

Exécution du service MS DTC

Le service MS DTC doit être marqué comme Automatique dans Service Manager afin de garantir son exécution quand le service SQL Server démarre. Pour activer MS DTC pour les transactions XA, vous devez procéder comme suit :

Sur Windows Vista et versions ultérieures :

  1. Sélectionnez le bouton Démarrer, tapez dcomcnfg dans la boîte Démarrer la recherche, puis appuyez sur Entrée pour ouvrir Services de composants. Vous pouvez également taper %windir%\system32\comexp.msc dans la boîte Démarrer la recherche pour ouvrir Services de composants.

  2. Développez Services de composants, Ordinateurs, Poste de travail, puis Distributed Transaction Coordinator.

  3. Cliquez avec le bouton droit sur DTC local, puis sélectionnez Propriétés.

  4. Sélectionnez l’onglet Sécurité dans la boîte de dialoguePropriétés DTC locales.

  5. Cochez la case Activer les transactions XA, puis sélectionnez OK. Cette action entraîne le redémarrage du service MS DTC.

  6. Sélectionnez à nouveau OK pour fermer la boîte de dialogue Propriétés, puis fermez Services de composants.

  7. Arrêtez, puis redémarrez SQL Server afin de garantir sa synchronisation avec les changements de MS DTC. (Cette étape est facultative pour SQL Server 2019 et SQL Server 2017 CU 16 et versions ultérieures.)

Configuration des composants de transaction distribuée JDBC

Les étapes de configuration des composants côté serveur diffèrent selon la version de votre serveur cible. Pour vérifier la version de votre serveur, exécutez la requête SELECT @@VERSION sur le serveur et affichez la sortie. Pour la mise à jour cumulative (CU) 16 SQL Server 2017 et versions ultérieures, suivez les instructions SQL Server 2017 CU16 et ultérieures. Pour les versions antérieures SQL Server, suivez les instructions SQL Server 2017 CU15 et antérieures.

SQL Server 2017 CU16 et versions ultérieures

Pour permettre aux composants requis d’effectuer des transactions distribuées XA avec le pilote JDBC, exécutez la procédure stockée suivante.

EXEC sp_sqljdbc_xa_install

Pour désactiver les composants, exécutez la procédure stockée suivante.

EXEC sp_sqljdbc_xa_uninstall

Passez à la section Configuration des paramètres du délai d’attente côté serveur pour la restauration automatique des transactions non préparées.

SQL Server 2017 CU15 et versions antérieures

Remarque

Cela s’applique uniquement à SQL Server 2017 CU15 et versions antérieures. Les fonctions fournies par sqljdbc_xa.dll sont déjà incluses dans SQL Server 2017 CU16 et versions ultérieures.

Les composants de transaction distribuée JDBC sont inclus dans le répertoire xa de l'installation du pilote JDBC. Ces composants incluent les fichiers xa_install.sql et sqljdbc_xa.dll. Si vous disposez de différentes versions du pilote JDBC sur différents clients, il est recommandé d’utiliser le dernier fichier sqljdbc_xa.dll sur le serveur.

Vous pouvez configurer les composants de transaction distribuée du pilote JDBC en suivant les étapes suivantes :

  1. Copiez le nouveau fichier sqljdbc_xa.dll à partir du répertoire d’installation du pilote JDBC vers le répertoire Binn de chaque ordinateur SQL Server qui participe à des transactions distribuées.

    Notes

    Si vous utilisez des transactions XA avec un SQL Server 32 bits (seulement applicable à SQL Server 2014 ou antérieure), utilisez le fichier sqljdbc_xa.dll dans le dossier x86, même si SQL Server est installé sur un processeur x64. Si vous utilisez des transactions XA avec une version 64 bits de SQL Server sur un processeur x64, utilisez le fichier sqljdbc_xa.dll dans le dossier x64.

  2. Exécutez le script de base de données xa_install.sql sur chaque instance SQL qui participe à des transactions distribuées. Ce script installe les procédures stockées étendues qui sont appelées par sqljdbc_xa.dll. Ces procédures stockées étendues implémentent la prise en charge des transactions distribuées et de XA pour Pilote Microsoft JDBC pour SQL Server. Vous devez exécuter ce script en tant qu'administrateur de l'instance SQL.

  3. Pour autoriser un utilisateur spécifique à participer à des transactions distribuées avec le pilote JDBC, ajoutez-le au rôle SqlJDBCXAUser.

Vous ne pouvez configurer qu’une seule version à la fois de l’assembly sqljdbc_xa.dll sur chaque instance de SQL Server. Les applications devront peut-être utiliser des versions différentes du pilote JDBC pour se connecter à la même instance SQL Server par le biais de la connexion XA. Dans ce cas, sqljdbc_xa.dll, qui est livré avec le pilote JDBC le plus récent, doit être installé sur l’instance de SQL Server.

Il existe trois façons de vérifier la version actuellement installée de sqljdbc_xa.dll sur l’instance de SQL Server :

  1. Ouvrez le répertoire LOG de l’ordinateur SQL Server qui participe aux transactions distribuées. Sélectionnez et ouvrez le fichier ERRORLOG SQL Server. Recherchez l'expression « Using 'SQLJDBC_XA.dll' version ... » dans le fichier ERRORLOG.

  2. Ouvrez le répertoire Binn de l’ordinateur SQL Server qui participe aux transactions distribuées. Sélectionnez l’assembly sqljdbc_xa.dll.

    • Sur Windows Vista et versions ultérieures : cliquez avec le bouton droit sur sqljdbc_xa.dll, puis sélectionnez Propriétés. Sélectionnez ensuite l’onglet Détails. Le champ Version du fichier indique la version de sqljdbc_xa.dll actuellement installée sur l’instance SQL.
  3. Définissez la fonctionnalité de journalisation comme indiqué dans l'exemple de code de la prochaine section. Recherchez l'expression « Server XA DLL version:... » dans le fichier journal de sortie.

Mise à niveau du fichier sqljdbc_xa.dll

Remarque

Cela s’applique uniquement à SQL Server 2017 CU15 et versions antérieures. Les fonctions fournies par sqljdbc_xa.dll sont déjà incluses dans SQL Server 2017 CU16 et versions ultérieures.

Lorsque vous installez une nouvelle version du pilote JDBC, vous devez utiliser le fichier sqljdbc_xa.dll de la nouvelle version pour mettre à niveau le fichier sqljdbc_xa.dll sur le serveur.

Important

Vous devez mettre à niveau le fichier sqljdbc_xa.dll dans une fenêtre de maintenance ou quand aucune transaction MS DTC n’est en cours.

  1. Déchargez sqljdbc_xa.dll avec la commande Transact-SQL :

    DBCC sqljdbc_xa (FREE)
    
  2. Copiez le nouveau fichier sqljdbc_xa.dll à partir du répertoire d’installation du pilote JDBC vers le répertoire Binn de chaque ordinateur SQL Server qui participe à des transactions distribuées.

    La nouvelle DLL sera chargée à l'appel d'une procédure étendue dans sqljdbc_xa.dll. Il n’est pas nécessaire de redémarrer SQL Server pour charger les nouvelles définitions.

Configuration des paramètres du délai d'attente côté serveur pour la restauration automatique des transactions non préparées

Il existe deux paramètres du Registre (valeurs DWORD) pour contrôler le comportement du délai d'attente des transactions distribuées :

  • XADefaultTimeout (en secondes) : valeur par défaut du délai d’attente, à utiliser quand l’utilisateur n’en spécifie aucune. La valeur par défaut est 0.

  • XAMaxTimeout (en secondes) : valeur maximale du délai d’attente qu’un utilisateur peut définir. La valeur par défaut est 0.

Ces paramètres sont spécifiques de l'instance SQL Server et doivent être créés sous la clé de Registre suivante :

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

Notes

Pour SQL Server 32 bits exécuté sur des ordinateurs 64 bits (uniquement applicable à SQL Server 2014 ou antérieure), les paramètres de Registre doivent être créés sous la clé suivante : HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SQL Server\MSSQL<version>.<instance_name>\XATimeout

Une valeur de délai d'attente est définie au démarrage de chaque transaction et SQL Server restaure la transaction si le délai d’attente expire. Le délai d'attente est déterminé en fonction de ces paramètres de Registre et de ce que l'utilisateur a spécifié via XAResource.setTransactionTimeout(). Voici quelques exemples d'interprétation des valeurs de délai d'attente :

  • XADefaultTimeout = 0, XAMaxTimeout = 0

    Signifie qu'aucun délai d'attente par défaut n’est utilisé et qu'aucun délai d'attente maximal n’est appliqué sur les clients. Dans ce cas, les transactions auront un délai d'attente uniquement si le client en définit un à l'aide de XAResource.setTransactionTimeout.

  • XADefaultTimeout = 60, XAMaxTimeout = 0

    Signifie que toutes les transactions ont un délai d’attente de 60 secondes si le client n’en spécifie aucun. Si le client spécifie un délai d'attente, cette valeur sera alors utilisée. Aucune valeur de délai d'attente maximale n'est appliquée.

  • XADefaultTimeout = 30, XAMaxTimeout = 60

    Signifie que toutes les transactions ont un délai d’attente de 30 secondes si le client n’en spécifie aucun. Si le client spécifie un délai d’attente, ce dernier sera utilisé s’il est inférieur à 60 secondes (valeur maximale).

  • XADefaultTimeout = 0, XAMaxTimeout = 30

    Signifie que toutes les transactions ont un délai d’attente de 30 secondes (valeur maximale) si le client n’en spécifie aucun. Si le client spécifie un délai d’attente, ce dernier sera utilisé s’il est inférieur à 30 secondes (valeur maximale).

Configuration des rôles définis par l'utilisateur

Pour autoriser un utilisateur spécifique à participer à des transactions distribuées avec le pilote JDBC, ajoutez-le au rôle SqlJDBCXAUser. Par exemple, utilisez le code Transact-SQL suivant pour ajouter un utilisateur appelé « shelly » (« shelly » est un nom d’utilisateur de connexion standard SQL) au rôle SqlJDBCXAUser :

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

Les rôles définis par l'utilisateur SQL sont définis par base de données. Pour des raisons de sécurité, pour créer votre propre rôle, vous devrez le définir dans chaque base de données et ajouter des utilisateurs base de données par base de données. Le rôle SqlJDBCXAUser est strictement défini dans la base de données MASTER, car il est utilisé pour accorder l'accès aux procédures stockées étendues SQL JDBC s’y trouvant. Vous devrez d'abord accorder à l'utilisateur un accès à Master, puis un accès au rôle SqlJDBCXAUser en étant connecté à la base de données Master.

Exemple

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

Voir aussi

Réalisation de transactions avec le pilote JDBC