Amazon QLDB Driver pour Java — Guide de référence du livre de recettes - Base de données Amazon Quantum Ledger (AmazonQLDB)

Les traductions sont fournies par des outils de traduction automatique. En cas de conflit entre le contenu d'une traduction et celui de la version originale en anglais, la version anglaise prévaudra.

Amazon QLDB Driver pour Java — Guide de référence du livre de recettes

Important

Avis de fin de support : les clients existants pourront utiliser Amazon QLDB jusqu'à la fin du support le 31 juillet 2025. Pour plus de détails, consultez Migrer un Amazon QLDB Ledger vers Amazon Aurora SQL Postgre.

Ce guide de référence présente les cas d'utilisation courants du QLDB pilote Amazon pour Java. Il fournit des exemples de code Java qui montrent comment utiliser le pilote pour exécuter des opérations de base de création, de lecture, de mise à jour et de suppression (CRUD). Il inclut également des exemples de code pour le traitement des données Amazon Ion. En outre, ce guide met en évidence les meilleures pratiques pour rendre les transactions idempotentes et mettre en œuvre des contraintes d'unicité.

Note

Le cas échéant, certains cas d'utilisation comportent des exemples de code différents pour chaque version majeure prise en charge du QLDB pilote pour Java.

Importation du pilote

L'exemple de code suivant importe le pilote, le client de QLDB session, les packages Amazon Ion et les autres dépendances associées.

2.x
import com.amazon.ion.IonStruct; import com.amazon.ion.IonSystem; import com.amazon.ion.IonValue; import com.amazon.ion.system.IonSystemBuilder; import software.amazon.awssdk.services.qldbsession.QldbSessionClient; import software.amazon.qldb.QldbDriver; import software.amazon.qldb.Result;
1.x
import com.amazon.ion.IonStruct; import com.amazon.ion.IonSystem; import com.amazon.ion.IonValue; import com.amazon.ion.system.IonSystemBuilder; import com.amazonaws.services.qldbsession.AmazonQLDBSessionClientBuilder; import software.amazon.qldb.PooledQldbDriver; import software.amazon.qldb.Result;

Instanciation du pilote

L'exemple de code suivant crée une instance de pilote qui se connecte à un nom de registre spécifié et utilise la logique de nouvelles tentatives spécifiée avec une limite de nouvelles tentatives personnalisée.

Note

Cet exemple instancie également un objet système Amazon Ion ()IonSystem. Vous avez besoin de cet objet pour traiter les données ioniques lors de l'exécution de certaines opérations de données dans cette référence. Pour en savoir plus, veuillez consulter la section Travailler avec Amazon Ion.

2.x
QldbDriver qldbDriver = QldbDriver.builder() .ledger("vehicle-registration") .transactionRetryPolicy(RetryPolicy .builder() .maxRetries(3) .build()) .sessionClientBuilder(QldbSessionClient.builder()) .build(); IonSystem SYSTEM = IonSystemBuilder.standard().build();
1.x
PooledQldbDriver qldbDriver = PooledQldbDriver.builder() .withLedger("vehicle-registration") .withRetryLimit(3) .withSessionClientBuilder(AmazonQLDBSessionClientBuilder.standard()) .build(); IonSystem SYSTEM = IonSystemBuilder.standard().build();

CRUDopérations

QLDBexécute les opérations de création, de lecture, de mise à jour et de suppression (CRUD) dans le cadre d'une transaction.

Avertissement

La meilleure pratique consiste à rendre vos transactions d'écriture strictement idempotentes.

Rendre les transactions idempotentes

Nous vous recommandons de rendre les transactions d'écriture idempotentes afin d'éviter tout effet secondaire inattendu en cas de nouvelle tentative. Une transaction est idempotente si elle peut être exécutée plusieurs fois et produire des résultats identiques à chaque fois.

Prenons l'exemple d'une transaction qui insère un document dans une table nomméePerson. La transaction doit d'abord vérifier si le document existe déjà dans le tableau. Sans cette vérification, le tableau peut se retrouver avec des documents dupliqués.

Supposons que la transaction soit validée QLDB avec succès côté serveur, mais que le client expire le délai d'attente d'une réponse. Si la transaction n'est pas idempotente, le même document peut être inséré plusieurs fois en cas de nouvelle tentative.

Utilisation d'index pour éviter l'analyse complète des tables

Nous vous recommandons également d'exécuter des instructions contenant une clause de WHERE prédicat à l'aide d'un opérateur d'égalité sur un champ indexé ou un identifiant de document ; par exemple, WHERE indexedField = 123 ou. WHERE indexedField IN (456, 789) Sans cette recherche indexée, il est QLDB nécessaire d'effectuer une analyse des tables, ce qui peut entraîner des délais d'expiration des transactions ou des conflits optimistes de contrôle simultané ()OCC.

Pour plus d'informations sur OCC, consultez Modèle de QLDB simultanéité Amazon.

Transactions créées implicitement

La méthode QldbDriver.execute accepte une fonction lambda qui reçoit une instance d'Executor, que vous pouvez utiliser pour exécuter des instructions. L'Executorinstance encapsule une transaction créée implicitement.

Vous pouvez exécuter des instructions dans la fonction lambda à l'aide de la Executor.execute méthode. Le pilote valide implicitement la transaction lorsque la fonction lambda revient.

Les sections suivantes montrent comment exécuter des CRUD opérations de base, spécifier une logique de nouvelle tentative personnalisée et implémenter des contraintes d'unicité.

Note

Le cas échéant, ces sections fournissent des exemples de code relatifs au traitement des données Amazon Ion à l'aide de la bibliothèque Ion intégrée et de la bibliothèque de mappage Jackson Ion. Pour en savoir plus, veuillez consulter la section Travailler avec Amazon Ion.

Création de tables

qldbDriver.execute(txn -> { txn.execute("CREATE TABLE Person"); });

Création d'index

qldbDriver.execute(txn -> { txn.execute("CREATE INDEX ON Person(GovId)"); });

Lecture de documents

// Assumes that Person table has documents as follows: // { GovId: "TOYENC486FH", FirstName: "Brent" } qldbDriver.execute(txn -> { Result result = txn.execute("SELECT * FROM Person WHERE GovId = 'TOYENC486FH'"); IonStruct person = (IonStruct) result.iterator().next(); System.out.println(person.get("GovId")); // prints TOYENC486FH System.out.println(person.get("FirstName")); // prints Brent });

Utilisation des paramètres de requête

L'exemple de code suivant utilise un paramètre de requête de type Ion.

qldbDriver.execute(txn -> { Result result = txn.execute("SELECT * FROM Person WHERE GovId = ?", SYSTEM.newString("TOYENC486FH")); IonStruct person = (IonStruct) result.iterator().next(); System.out.println(person.get("GovId")); // prints TOYENC486FH System.out.println(person.get("FirstName")); // prints Brent });

L'exemple de code suivant utilise plusieurs paramètres de requête.

qldbDriver.execute(txn -> { Result result = txn.execute("SELECT * FROM Person WHERE GovId = ? AND FirstName = ?", SYSTEM.newString("TOYENC486FH"), SYSTEM.newString("Brent")); IonStruct person = (IonStruct) result.iterator().next(); System.out.println(person.get("GovId")); // prints TOYENC486FH System.out.println(person.get("FirstName")); // prints Brent });

L'exemple de code suivant utilise une liste de paramètres de requête.

qldbDriver.execute(txn -> { final List<IonValue> parameters = new ArrayList<>(); parameters.add(SYSTEM.newString("TOYENC486FH")); parameters.add(SYSTEM.newString("ROEE1")); parameters.add(SYSTEM.newString("YH844")); Result result = txn.execute("SELECT * FROM Person WHERE GovId IN (?,?,?)", parameters); IonStruct person = (IonStruct) result.iterator().next(); System.out.println(person.get("GovId")); // prints TOYENC486FH System.out.println(person.get("FirstName")); // prints Brent });
// Assumes that Person table has documents as follows: // {GovId: "TOYENC486FH", FirstName: "Brent" } qldbDriver.execute(txn -> { try { Result result = txn.execute("SELECT * FROM Person WHERE GovId = 'TOYENC486FH'"); Person person = MAPPER.readValue(result.iterator().next(), Person.class); System.out.println(person.getFirstName()); // prints Brent System.out.println(person.getGovId()); // prints TOYENC486FH } catch (IOException e) { e.printStackTrace(); } });

Utilisation des paramètres de requête

L'exemple de code suivant utilise un paramètre de requête de type Ion.

qldbDriver.execute(txn -> { try { Result result = txn.execute("SELECT * FROM Person WHERE GovId = ?", MAPPER.writeValueAsIonValue("TOYENC486FH")); Person person = MAPPER.readValue(result.iterator().next(), Person.class); System.out.println(person.getFirstName()); // prints Brent System.out.println(person.getGovId()); // prints TOYENC486FH } catch (IOException e) { e.printStackTrace(); } });

L'exemple de code suivant utilise plusieurs paramètres de requête.

qldbDriver.execute(txn -> { try { Result result = txn.execute("SELECT * FROM Person WHERE GovId = ? AND FirstName = ?", MAPPER.writeValueAsIonValue("TOYENC486FH"), MAPPER.writeValueAsIonValue("Brent")); Person person = MAPPER.readValue(result.iterator().next(), Person.class); System.out.println(person.getFirstName()); // prints Brent System.out.println(person.getGovId()); // prints TOYENC486FH } catch (IOException e) { e.printStackTrace(); } });

L'exemple de code suivant utilise une liste de paramètres de requête.

qldbDriver.execute(txn -> { try { final List<IonValue> parameters = new ArrayList<>(); parameters.add(MAPPER.writeValueAsIonValue("TOYENC486FH")); parameters.add(MAPPER.writeValueAsIonValue("ROEE1")); parameters.add(MAPPER.writeValueAsIonValue("YH844")); Result result = txn.execute("SELECT * FROM Person WHERE GovId IN (?,?,?)", parameters); Person person = MAPPER.readValue(result.iterator().next(), Person.class); System.out.println(person.getFirstName()); // prints Brent System.out.println(person.getGovId()); // prints TOYENC486FH } catch (IOException e) { e.printStackTrace(); } });
Note

Lorsque vous exécutez une requête sans recherche indexée, elle appelle une analyse complète de la table. Dans cet exemple, nous recommandons d'avoir un index sur le GovId terrain pour optimiser les performances. Sans index activéGovId, les requêtes peuvent avoir une latence plus importante et peuvent également entraîner des OCC conflits, des exceptions ou des délais de transaction.

Insertion de documents

L'exemple de code suivant insère les types de données Ion.

qldbDriver.execute(txn -> { // Check if a document with GovId:TOYENC486FH exists // This is critical to make this transaction idempotent Result result = txn.execute("SELECT * FROM Person WHERE GovId = ?", SYSTEM.newString("TOYENC486FH")); // Check if there is a result if (!result.iterator().hasNext()) { IonStruct person = SYSTEM.newEmptyStruct(); person.put("GovId").newString("TOYENC486FH"); person.put("FirstName").newString("Brent"); // Insert the document txn.execute("INSERT INTO Person ?", person); } });

L'exemple de code suivant insère les types de données Ion.

qldbDriver.execute(txn -> { try { // Check if a document with GovId:TOYENC486FH exists // This is critical to make this transaction idempotent Result result = txn.execute("SELECT * FROM Person WHERE GovId = ?", MAPPER.writeValueAsIonValue("TOYENC486FH")); // Check if there is a result if (!result.iterator().hasNext()) { // Insert the document txn.execute("INSERT INTO Person ?", MAPPER.writeValueAsIonValue(new Person("Brent", "TOYENC486FH"))); } } catch (IOException e) { e.printStackTrace(); } });

Cette transaction insère un document dans le Person tableau. Avant l'insertion, il vérifie d'abord si le document existe déjà dans le tableau. Cette vérification rend la transaction idempotente par nature. Même si vous exécutez cette transaction plusieurs fois, elle ne provoquera aucun effet secondaire involontaire.

Note

Dans cet exemple, nous recommandons d'avoir un index sur le GovId terrain pour optimiser les performances. Sans index activéGovId, les instructions peuvent avoir une latence plus importante et peuvent également entraîner des OCC conflits, des exceptions ou des délais d'attente pour les transactions.

Insertion de plusieurs documents dans une seule déclaration

Pour insérer plusieurs documents à l'aide d'une seule INSERT instruction, vous pouvez transmettre un paramètre de type IonList(explicitement converti en tant queIonValue) à l'instruction comme suit.

// people is an IonList explicitly cast as an IonValue txn.execute("INSERT INTO People ?", (IonValue) people);

Vous ne placez pas la variable placeholder (?) entre crochets (<<...>>) lorsque vous passez un. IonList Dans les instructions partiQL manuelles, les crochets à double angle indiquent une collection non ordonnée appelée sac.

La méthode TransactionExecutor.execute est surchargée. Il accepte un nombre variable d'IonValuearguments (varargs) ou un seul List<IonValue> argument. Dans ion-java, IonList est implémenté en tant que. List<IonValue>

Java utilise par défaut l'implémentation de méthode la plus spécifique lorsque vous appelez une méthode surchargée. Dans ce cas, lorsque vous transmettez un IonList paramètre, il utilise par défaut la méthode qui prend unList<IonValue>. Lorsqu'elle est invoquée, cette implémentation de méthode transmet les IonValue éléments de la liste sous forme de valeurs distinctes. Ainsi, pour invoquer la méthode varargs à la place, vous devez explicitement convertir un IonList paramètre en. IonValue

Mettre à jour des documents

qldbDriver.execute(txn -> { final List<IonValue> parameters = new ArrayList<>(); parameters.add(SYSTEM.newString("John")); parameters.add(SYSTEM.newString("TOYENC486FH")); txn.execute("UPDATE Person SET FirstName = ? WHERE GovId = ?", parameters); });
qldbDriver.execute(txn -> { try { final List<IonValue> parameters = new ArrayList<>(); parameters.add(MAPPER.writeValueAsIonValue("John")); parameters.add(MAPPER.writeValueAsIonValue("TOYENC486FH")); txn.execute("UPDATE Person SET FirstName = ? WHERE GovId = ?", parameters); } catch (IOException e) { e.printStackTrace(); } });
Note

Dans cet exemple, nous recommandons d'avoir un index sur le GovId terrain pour optimiser les performances. Sans index activéGovId, les instructions peuvent avoir une latence plus importante et peuvent également entraîner des OCC conflits, des exceptions ou des délais d'attente pour les transactions.

Supprimer des documents

qldbDriver.execute(txn -> { txn.execute("DELETE FROM Person WHERE GovId = ?", SYSTEM.newString("TOYENC486FH")); });
qldbDriver.execute(txn -> { try { txn.execute("DELETE FROM Person WHERE GovId = ?", MAPPER.writeValueAsIonValue("TOYENC486FH")); } catch (IOException e) { e.printStackTrace(); } });
Note

Dans cet exemple, nous recommandons d'avoir un index sur le GovId terrain pour optimiser les performances. Sans index activéGovId, les instructions peuvent avoir une latence plus importante et peuvent également entraîner des OCC conflits, des exceptions ou des délais d'attente pour les transactions.

Exécution de plusieurs instructions dans une transaction

// This code snippet is intentionally trivial. In reality you wouldn't do this because you'd // set your UPDATE to filter on vin and insured, and check if you updated something or not. public static boolean InsureCar(QldbDriver qldbDriver, final String vin) { final IonSystem ionSystem = IonSystemBuilder.standard().build(); final IonString ionVin = ionSystem.newString(vin); return qldbDriver.execute(txn -> { Result result = txn.execute( "SELECT insured FROM Vehicles WHERE vin = ? AND insured = FALSE", ionVin); if (!result.isEmpty()) { txn.execute("UPDATE Vehicles SET insured = TRUE WHERE vin = ?", ionVin); return true; } return false; }); }

Logique des nouvelles tentatives

La execute méthode du pilote comporte un mécanisme de nouvelle tentative intégré qui permet de réessayer la transaction si une exception réessayable se produit (comme un délai d'expiration ou un conflit). OCC

2.x

Le nombre maximum de tentatives et la stratégie d'interruption sont configurables.

La limite de tentatives par défaut est4, et la stratégie de réduction par défaut est. DefaultQldbTransactionBackoffStrategy Vous pouvez définir la configuration des nouvelles tentatives par instance de pilote et également par transaction en utilisant une instance de RetryPolicy.

L'exemple de code suivant spécifie une logique de nouvelles tentatives avec une limite de tentatives personnalisée et une stratégie de temporisation personnalisée pour une instance du pilote.

public void retry() { QldbDriver qldbDriver = QldbDriver.builder() .ledger("vehicle-registration") .transactionRetryPolicy(RetryPolicy.builder() .maxRetries(2) .backoffStrategy(new CustomBackOffStrategy()).build()) .sessionClientBuilder(QldbSessionClient.builder()) .build(); } private class CustomBackOffStrategy implements BackoffStrategy { @Override public Duration calculateDelay(RetryPolicyContext retryPolicyContext) { return Duration.ofMillis(1000 * retryPolicyContext.retriesAttempted()); } }

L'exemple de code suivant spécifie une logique de nouvelles tentatives avec une limite de tentatives personnalisée et une stratégie de temporisation personnalisée pour une transaction particulière. Cette configuration execute remplace la logique de nouvelle tentative définie pour l'instance de pilote.

public void retry() { Result result = qldbDriver.execute(txn -> { txn.execute("SELECT * FROM Person WHERE GovId = ?", SYSTEM.newString("TOYENC486FH")); }, RetryPolicy.builder() .maxRetries(2) .backoffStrategy(new CustomBackOffStrategy()) .build()); } private class CustomBackOffStrategy implements BackoffStrategy { // Configuring a custom backoff which increases delay by 1s for each attempt. @Override public Duration calculateDelay(RetryPolicyContext retryPolicyContext) { return Duration.ofMillis(1000 * retryPolicyContext.retriesAttempted()); } }
1.x

Le nombre maximum de nouvelles tentatives est configurable. Vous pouvez configurer la limite de tentatives en définissant la retryLimit propriété lors de l'initialisationPooledQldbDriver.

La limite de nouvelles tentatives par défaut est 4 de.

Mettre en œuvre des contraintes d'unicité

QLDBne prend pas en charge les index uniques, mais vous pouvez implémenter ce comportement dans votre application.

Supposons que vous souhaitiez implémenter une contrainte d'unicité sur le GovId champ de la Person table. Pour ce faire, vous pouvez écrire une transaction qui effectue les opérations suivantes :

  1. Affirme que le tableau ne contient aucun document existant avec une valeur spécifiéeGovId.

  2. Insérez le document si l'assertion est acceptée.

Si une transaction concurrente passe simultanément l'assertion, une seule des transactions sera validée avec succès. L'autre transaction échouera avec une exception de OCC conflit.

L'exemple de code suivant montre comment implémenter cette logique de contrainte d'unicité.

qldbDriver.execute(txn -> { Result result = txn.execute("SELECT * FROM Person WHERE GovId = ?", SYSTEM.newString("TOYENC486FH")); // Check if there is a result if (!result.iterator().hasNext()) { IonStruct person = SYSTEM.newEmptyStruct(); person.put("GovId").newString("TOYENC486FH"); person.put("FirstName").newString("Brent"); // Insert the document txn.execute("INSERT INTO Person ?", person); } });
Note

Dans cet exemple, nous recommandons d'avoir un index sur le GovId terrain pour optimiser les performances. Sans index activéGovId, les instructions peuvent avoir une latence plus importante et peuvent également entraîner des OCC conflits, des exceptions ou des délais d'attente pour les transactions.

Travailler avec Amazon Ion

Il existe plusieurs méthodes pour traiter les données Amazon Ion dansQLDB. Vous pouvez utiliser les méthodes intégrées de la bibliothèque Ion pour créer et modifier des documents de manière flexible selon vos besoins. Vous pouvez également utiliser le module de format XML de données Jackson de Faster pour Ion afin de mapper les documents Ion sur de simples anciens modèles d'objets Java (POJO).

Les sections suivantes fournissent des exemples de code de traitement de données ioniques à l'aide des deux techniques.

Importation des packages Ion

Ajoutez l'artefact ion-java en tant que dépendance dans votre projet Java.

Gradle
dependencies { compile group: 'com.amazon.ion', name: 'ion-java', version: '1.6.1' }
Maven
<dependencies> <dependency> <groupId>com.amazon.ion</groupId> <artifactId>ion-java</artifactId> <version>1.6.1</version> </dependency> </dependencies>

Importez les packages Ion suivants.

import com.amazon.ion.IonStruct; import com.amazon.ion.IonSystem; import com.amazon.ion.system.IonSystemBuilder;

Ajoutez l'artefact jackson-dataformat-ionen tant que dépendance dans votre projet Java. QLDBnécessite une version 2.10.0 ou une version ultérieure.

Gradle
dependencies { compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-ion', version: '2.10.0' }
Maven
<dependencies> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-ion</artifactId> <version>2.10.0</version> </dependency> </dependencies>

Importez les packages Ion suivants.

import com.amazon.ion.IonReader; import com.amazon.ion.IonStruct; import com.amazon.ion.system.IonReaderBuilder; import com.amazon.ion.system.IonSystemBuilder; import com.fasterxml.jackson.dataformat.ion.IonObjectMapper; import com.fasterxml.jackson.dataformat.ion.ionvalue.IonValueMapper;

Initialisation d'Ion

IonSystem SYSTEM = IonSystemBuilder.standard().build();
IonObjectMapper MAPPER = new IonValueMapper(IonSystemBuilder.standard().build());

Création d'objets Ion

L'exemple de code suivant crée un objet Ion à l'aide de l'IonStructinterface et de ses méthodes intégrées.

IonStruct ionStruct = SYSTEM.newEmptyStruct(); ionStruct.put("GovId").newString("TOYENC486FH"); ionStruct.put("FirstName").newString("Brent"); System.out.println(ionStruct.toPrettyString()); // prints a nicely formatted copy of ionStruct

Supposons que vous ayez une classe de modèle JSON -mapped nommée comme Person suit.

import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; public static class Person { private final String firstName; private final String govId; @JsonCreator public Person(@JsonProperty("FirstName") final String firstName, @JsonProperty("GovId") final String govId) { this.firstName = firstName; this.govId = govId; } @JsonProperty("FirstName") public String getFirstName() { return firstName; } @JsonProperty("GovId") public String getGovId() { return govId; } }

L'exemple de code suivant crée un IonStruct objet à partir d'une instance dePerson.

IonStruct ionStruct = (IonStruct) MAPPER.writeValueAsIonValue(new Person("Brent", "TOYENC486FH"));

Lecture d'objets ioniques

L'exemple de code suivant imprime chaque champ de l'ionStructinstance.

// ionStruct is an instance of IonStruct System.out.println(ionStruct.get("GovId")); // prints TOYENC486FH System.out.println(ionStruct.get("FirstName")); // prints Brent

L'exemple de code suivant lit un IonStruct objet et le met en correspondance avec une instance dePerson.

// ionStruct is an instance of IonStruct IonReader reader = IonReaderBuilder.standard().build(ionStruct); Person person = MAPPER.readValue(reader, Person.class); System.out.println(person.getFirstName()); // prints Brent System.out.println(person.getGovId()); // prints TOYENC486FH

Pour plus d'informations sur l'utilisation d'Ion, consultez la documentation Amazon Ion sur GitHub. Pour d'autres exemples de code relatifs à l'utilisation d'Ion inQLDB, consultezUtilisation des types de données Amazon Ion dans Amazon QLDB.