QLDBDriver Amazon para Java — Referência do livro de receitas - Banco de dados Amazon Quantum Ledger (AmazonQLDB)

As traduções são geradas por tradução automática. Em caso de conflito entre o conteúdo da tradução e da versão original em inglês, a versão em inglês prevalecerá.

QLDBDriver Amazon para Java — Referência do livro de receitas

Importante

Aviso de fim do suporte: os clientes existentes poderão usar a Amazon QLDB até o final do suporte em 31/07/2025. Para obter mais detalhes, consulte Migrar um Amazon QLDB Ledger para o Amazon Aurora Postgre. SQL

Este guia de referência mostra casos de uso comuns do QLDB driver da Amazon para Java. Ele fornece exemplos de código Java que demonstram como usar o driver para executar operações básicas de criação, leitura, atualização e exclusão (CRUD). Também inclui exemplos de código para processamento de dados do Amazon Ion. Além disso, este guia destaca as práticas recomendadas para tornar as transações idempotentes e implantar restrições de exclusividade.

nota

Quando aplicável, alguns casos de uso têm exemplos de código diferentes para cada versão principal compatível do QLDB driver para Java.

Importação do driver

O exemplo de código a seguir importa o driver, o cliente da QLDB sessão, os pacotes Amazon Ion e outras dependências relacionadas.

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;

Instanciação do driver

O exemplo de código a seguir cria uma instância de driver que se conecta a um nome do ledger especificado e usa a lógica de repetição especificada com um limite de repetição personalizado.

nota

Este exemplo também instancia um objeto do sistema Amazon Ion (IonSystem). Você precisa desse objeto para processar dados Ion ao executar algumas operações de dados nesta referência. Para saber mais, consulte Como trabalhar com o 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();

CRUDoperações

QLDBexecuta operações de criação, leitura, atualização e exclusão (CRUD) como parte de uma transação.

Atenção

Como prática recomendada, torne suas transações de gravação estritamente idempotentes.

Tornar as transações idempotentes

Recomendamos que você torne as transações de gravação idempotentes para evitar efeitos colaterais inesperados no caso de novas tentativas. Uma transação é idempotente se puder ser executada várias vezes e produzir resultados idênticos a cada vez.

Por exemplo, considere uma transação que insere um documento em uma tabela chamada Person. A transação deve primeiro verificar se o documento já existe ou não na tabela. Sem essa verificação, a tabela pode acabar com documentos duplicados.

Suponha que a transação seja confirmada QLDB com sucesso no lado do servidor, mas o cliente expire enquanto espera por uma resposta. Se a transação não for idempotente, o mesmo documento poderá ser inserido mais de uma vez no caso de uma nova tentativa.

Usar índices para evitar varreduras completas da tabela

Também recomendamos executar instruções com uma cláusula de predicado WHERE usando um operador de igualdade em um campo indexado ou em um ID de documento, por exemplo, WHERE indexedField = 123 ou WHERE indexedField IN (456, 789). Sem essa pesquisa indexada, é QLDB necessário fazer uma varredura da tabela, o que pode levar a tempos limite de transação ou conflitos otimistas de controle de simultaneidade (). OCC

Para obter mais informações sobre o OCC, consulte Modelo de QLDB concorrência da Amazon.

Transações criadas implicitamente

O método QldbDriver.execute aceita uma função lambda que recebe uma instância do Executor, que você pode usar para executar instruções. A instância Executor envolve uma transação criada implicitamente.

Você pode executar instruções na função do Lambda usando o método Executor.execute. O driver confirma implicitamente a transação quando a função do Lambda retorna.

As seções a seguir mostram como executar CRUD operações básicas, especificar a lógica de repetição personalizada e implementar restrições de exclusividade.

nota

Quando aplicável, essas seções fornecem exemplos de código de processamento de dados do Amazon Ion usando a biblioteca Ion integrada e a biblioteca Jackson Ion mapper. Para saber mais, consulte Como trabalhar com o Amazon Ion.

Criar tabelas

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

Criar índices

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

Ler documentos

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

Como usar parâmetros de consulta

O exemplo de código a seguir usa um parâmetro de consulta do tipo 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 });

O exemplo de código a seguir usa múltiplos parâmetros de consulta.

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

O exemplo de código a seguir usa uma lista de parâmetros de consulta.

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

Como usar parâmetros de consulta

O exemplo de código a seguir usa um parâmetro de consulta do tipo 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(); } });

O exemplo de código a seguir usa múltiplos parâmetros de consulta.

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

O exemplo de código a seguir usa uma lista de parâmetros de consulta.

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

Quando você executa uma consulta sem uma pesquisa indexada, ela invoca uma verificação completa da tabela. Neste exemplo, recomendamos ter um índice no campo GovId para otimizar o desempenho. Sem um índice ativadoGovId, as consultas podem ter mais latência e também podem levar a exceções de OCC conflito ou tempos limite de transação.

Inserir documentos

Os exemplos de código a seguir inserem os tipos de dados 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); } });

Os exemplos de código a seguir inserem os tipos de dados 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(); } });

Essa transação insere um documento na tabela Person. Antes de inserir, ele primeiro verifica se o documento já existe na tabela. Essa verificação torna a transação idempotente por natureza. Mesmo que você execute essa transação várias vezes, ela não causará efeitos colaterais indesejados.

nota

Neste exemplo, recomendamos ter um índice no campo GovId para otimizar o desempenho. Sem um índice ativadoGovId, as declarações podem ter mais latência e também podem levar a exceções de OCC conflito ou tempos limite de transação.

Como inserir vários documentos em uma instrução

Para inserir vários documentos usando uma única INSERT instrução, você pode passar um parâmetro do tipo IonList(convertido explicitamente como umIonValue) para a instrução da seguinte maneira.

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

Você não coloca o marcador variável (?) entre colchetes angulares duplos ( <<...>> ) ao passar uma IonList. Nas instruções manuais do PartiQL, colchetes angulares duplos denotam uma coleção não ordenada conhecida como bolsa.

O TransactionExecutormétodo.execute está sobrecarregado. Ele aceita um número variável de IonValue argumentos (varargs) ou um único argumento List<IonValue>. Em ion-java, IonList é implementado como um List<IonValue>.

O Java usa como padrão a implantação do método mais específico quando você chama um método sobrecarregado. Nesse caso, quando você passa um parâmetro IonList, o padrão é o método que usa a List<IonValue>. Quando invocada, essa implementação de método passa os elementos IonValue da lista como valores distintos. Portanto, para invocar o método varargs em vez disso, você deve converter explicitamente um parâmetro IonList em IonValue.

Como atualizar documentos

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

Neste exemplo, recomendamos ter um índice no campo GovId para otimizar o desempenho. Sem um índice ativadoGovId, as declarações podem ter mais latência e também podem levar a exceções de OCC conflito ou tempos limite de transação.

Como excluir documentos

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

Neste exemplo, recomendamos ter um índice no campo GovId para otimizar o desempenho. Sem um índice ativadoGovId, as declarações podem ter mais latência e também podem levar a exceções de OCC conflito ou tempos limite de transação.

Executando várias instruções em uma transação

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

Lógica de novas tentativas

O execute método do driver tem um mecanismo de repetição integrado que repete a transação se ocorrer uma exceção que pode ser repetida (como tempos limite ou conflitos). OCC

2.x

O número máximo de tentativas de repetição e a estratégia de recuo são configuráveis.

O limite padrão de repetição é4, e a estratégia de recuo padrão é. DefaultQldbTransactionBackoffStrategy Você pode definir a configuração de repetição por instância de driver e também por transação usando uma instância de RetryPolicy.

O exemplo de código a seguir especifica a lógica de repetição com um limite de repetição personalizado e uma estratégia de recuo personalizada para uma instância do driver.

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

O exemplo de código a seguir especifica a lógica de repetição com um limite de repetição personalizado e uma estratégia de recuo personalizada para uma instância particular. Essa configuração para execute substitui a lógica de repetição definida para a instância do driver.

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

O número máximo de tentativas de repetição pode ser configurado. Você pode configurar o limite de novas tentativas definindo a propriedade retryLimit ao inicializar PooledQldbDriver.

O limite padrão de novas tentativas é 4.

Implementação de restrições de exclusividade

QLDBnão oferece suporte a índices exclusivos, mas você pode implementar esse comportamento em seu aplicativo.

Suponha que você queira implementar uma restrição de exclusividade no campo GovId da tabela Person. Para fazer isso, você pode escrever uma transação que faça o seguinte:

  1. Afirme que a tabela não tem documentos existentes com um GovId especificado.

  2. Insira o documento se a afirmação for aprovada.

Se uma transação concorrente passar simultaneamente pela declaração, somente uma das transações será confirmada com sucesso. A outra transação falhará com uma exceção de OCC conflito.

O exemplo de código a seguir mostra como implementar essa lógica de restrição de exclusividade.

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

Neste exemplo, recomendamos ter um índice no campo GovId para otimizar o desempenho. Sem um índice ativadoGovId, as declarações podem ter mais latência e também podem levar a exceções de OCC conflito ou tempos limite de transação.

Como trabalhar com o Amazon Ion

Há várias maneiras de processar dados do Amazon Ion noQLDB. Você pode usar métodos integrados da biblioteca Ion para criar e modificar documentos com flexibilidade, conforme necessário. Ou você pode usar o módulo de formato XML de dados Jackson da Faster para Ion para mapear documentos Ion para modelos Java object (POJO) simples e antigos.

As seções a seguir fornecem exemplos de código de processamento de dados Ion usando ambas as técnicas.

Importando os pacotes Ion

Adicione o artefato ion-java como uma dependência em seu projeto 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>

Importe os seguintes pacotes de Ion.

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

Adicione o artefato jackson-dataformat-ioncomo uma dependência em seu projeto Java. QLDBrequer uma versão 2.10.0 ou posterior.

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>

Importe os seguintes pacotes de Ion.

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;

Inicializando Ion

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

Criação de objetos do Ion

O exemplo de código a seguir cria um objeto Ion usando a interface IonStruct e seus métodos integrados.

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

Suponha que você tenha uma classe JSON de modelo mapeada com o nome Person a seguir.

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

O exemplo de código a seguir cria um objeto IonStruct a partir de uma instância de Person.

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

Leitura de objetos do Ion

O exemplo de código a seguir imprime cada campo da instância ionStruct.

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

O exemplo de código a seguir lê o objeto e mapas de IonStruct a partir de uma instância de Person.

// 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

Para obter mais informações sobre como trabalhar com o Ion, consulte a documentação do Amazon Ion em GitHub. Para obter mais exemplos de código de como trabalhar com o Ion inQLDB, consulteTrabalhando com os tipos de dados Amazon Ion na Amazon QLDB.