Introduzione a Spanner in Java


Obiettivi

Questo tutorial illustra i passaggi seguenti utilizzando la libreria client Spanner per Java:

  • Creare un'istanza e un database Spanner.
  • Scrivere, leggere ed eseguire query SQL sui dati nel database.
  • Aggiorna lo schema del database.
  • Aggiornare i dati utilizzando una transazione di lettura e scrittura.
  • Aggiungi un indice secondario al database.
  • Utilizza l'indice per leggere ed eseguire query SQL sui dati.
  • Recupera i dati utilizzando una transazione di sola lettura.

Costi

Questo tutorial utilizza Spanner, che è un componente fatturabile di Google Cloud. Per informazioni sul costo dell'utilizzo di Spanner, consulta i prezzi.

Prima di iniziare

Completa i passaggi descritti in Configurazione, che riguardano la creazione e l'impostazione di un progetto Google Cloud predefinito, l'abilitazione della fatturazione, l'abilitazione dell'API Cloud Spanner e la configurazione di OAuth 2.0 per ottenere le credenziali di autenticazione da utilizzare per l'API Cloud Spanner.

In particolare, assicurati di eseguire gcloud auth application-default login per configurare il tuo ambiente di sviluppo locale con le credenziali di autenticazione.

Prepara l'ambiente Java locale

  1. Installa il seguente codice sulla macchina di sviluppo, se non è già installato:

  2. Clona il repository dell'app di esempio sulla tua macchina locale:

    git clone https://github.com/googleapis/java-spanner.git
    
  3. Passa alla directory che contiene il codice campione Spanner:

    cd java-spanner/samples/snippets
    

Creazione di un'istanza

Quando utilizzi Spanner per la prima volta, devi creare un'istanza, ovvero un'allocazione di risorse utilizzate dai database Spanner. Quando crei un'istanza, scegli una configurazione dell'istanza, che determina dove sono archiviati i dati e il numero di nodi da utilizzare, che determina la quantità di risorse di gestione e archiviazione nell'istanza.

Esegui questo comando per creare un'istanza Spanner nella regione us-central1 con 1 nodo:

gcloud spanner instances create test-instance --config=regional-us-central1 \
    --description="Test Instance" --nodes=1

Tieni presente che viene creata un'istanza con le seguenti caratteristiche:

  • ID istanza test-instance
  • Nome visualizzato Test Instance
  • La configurazione dell'istanza regional-us-central1 (le configurazioni regionali archiviano i dati in una singola regione, mentre le configurazioni multiregionali distribuiscono i dati in più regioni. Per saperne di più, consulta Informazioni sulle istanze.
  • Conteggio dei nodi pari a 1 (node_count corrisponde alla quantità di risorse di gestione e archiviazione disponibili per i database nell'istanza. Scopri di più in Nodi e unità di elaborazione.

Dovresti vedere:

Creating instance...done.

Cerca tra i file di esempio

Il repository di esempi contiene un esempio che mostra come utilizzare Spanner con Java.

Crea un database

Crea un database denominato example-db nell'istanza denominata test-instance eseguendo questo comando sulla riga di comando.

GoogleSQL

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
createdatabase test-instance example-db

PostgreSQL

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
createpgdatabase test-instance example-db

Dovresti vedere:

Created database [example-db]

Il codice seguente crea un database e due tabelle al suo interno.

GoogleSQL

static void createDatabase(DatabaseAdminClient dbAdminClient,
    InstanceName instanceName, String databaseId) {
  CreateDatabaseRequest createDatabaseRequest =
      CreateDatabaseRequest.newBuilder()
          .setCreateStatement("CREATE DATABASE `" + databaseId + "`")
          .setParent(instanceName.toString())
          .addAllExtraStatements(Arrays.asList(
              "CREATE TABLE Singers ("
                  + "  SingerId   INT64 NOT NULL,"
                  + "  FirstName  STRING(1024),"
                  + "  LastName   STRING(1024),"
                  + "  SingerInfo BYTES(MAX),"
                  + "  FullName STRING(2048) AS "
                  + "  (ARRAY_TO_STRING([FirstName, LastName], \" \")) STORED"
                  + ") PRIMARY KEY (SingerId)",
              "CREATE TABLE Albums ("
                  + "  SingerId     INT64 NOT NULL,"
                  + "  AlbumId      INT64 NOT NULL,"
                  + "  AlbumTitle   STRING(MAX)"
                  + ") PRIMARY KEY (SingerId, AlbumId),"
                  + "  INTERLEAVE IN PARENT Singers ON DELETE CASCADE")).build();
  try {
    // Initiate the request which returns an OperationFuture.
    com.google.spanner.admin.database.v1.Database db =
        dbAdminClient.createDatabaseAsync(createDatabaseRequest).get();
    System.out.println("Created database [" + db.getName() + "]");
  } catch (ExecutionException e) {
    // If the operation failed during execution, expose the cause.
    throw (SpannerException) e.getCause();
  } catch (InterruptedException e) {
    // Throw when a thread is waiting, sleeping, or otherwise occupied,
    // and the thread is interrupted, either before or during the activity.
    throw SpannerExceptionFactory.propagateInterrupt(e);
  }
}

PostgreSQL

static void createPostgreSqlDatabase(
    DatabaseAdminClient dbAdminClient, String projectId, String instanceId, String databaseId) {
  final CreateDatabaseRequest request =
      CreateDatabaseRequest.newBuilder()
          .setCreateStatement("CREATE DATABASE \"" + databaseId + "\"")
          .setParent(InstanceName.of(projectId, instanceId).toString())
          .setDatabaseDialect(DatabaseDialect.POSTGRESQL).build();

  try {
    // Initiate the request which returns an OperationFuture.
    Database db = dbAdminClient.createDatabaseAsync(request).get();
    System.out.println("Created database [" + db.getName() + "]");
  } catch (ExecutionException e) {
    // If the operation failed during execution, expose the cause.
    throw (SpannerException) e.getCause();
  } catch (InterruptedException e) {
    // Throw when a thread is waiting, sleeping, or otherwise occupied,
    // and the thread is interrupted, either before or during the activity.
    throw SpannerExceptionFactory.propagateInterrupt(e);
  }
}
static void createTableUsingDdl(DatabaseAdminClient dbAdminClient, DatabaseName databaseName) {
  try {
    // Initiate the request which returns an OperationFuture.
    dbAdminClient.updateDatabaseDdlAsync(
        databaseName,
        Arrays.asList(
            "CREATE TABLE Singers ("
                + "  SingerId   bigint NOT NULL,"
                + "  FirstName  character varying(1024),"
                + "  LastName   character varying(1024),"
                + "  SingerInfo bytea,"
                + "  FullName character varying(2048) GENERATED "
                + "  ALWAYS AS (FirstName || ' ' || LastName) STORED,"
                + "  PRIMARY KEY (SingerId)"
                + ")",
            "CREATE TABLE Albums ("
                + "  SingerId     bigint NOT NULL,"
                + "  AlbumId      bigint NOT NULL,"
                + "  AlbumTitle   character varying(1024),"
                + "  PRIMARY KEY (SingerId, AlbumId)"
                + ") INTERLEAVE IN PARENT Singers ON DELETE CASCADE")).get();
    System.out.println("Created Singers & Albums tables in database: [" + databaseName + "]");
  } catch (ExecutionException e) {
    // If the operation failed during execution, expose the cause.
    throw SpannerExceptionFactory.asSpannerException(e);
  } catch (InterruptedException e) {
    // Throw when a thread is waiting, sleeping, or otherwise occupied,
    // and the thread is interrupted, either before or during the activity.
    throw SpannerExceptionFactory.propagateInterrupt(e);
  }
}

Il passaggio successivo consiste nella scrittura dei dati nel database.

crea un client di database

Prima di poter eseguire operazioni di lettura o scrittura, devi creare un DatabaseClient. Puoi considerare DatabaseClient come una connessione al database: tutte le tue interazioni con Spanner devono passare attraverso un DatabaseClient. In genere crei un DatabaseClient all'avvio dell'applicazione, poi riutilizzi DatabaseClient per leggere, scrivere ed eseguire transazioni.

GoogleSQL

SpannerOptions options = SpannerOptions.newBuilder().build();
Spanner spanner = options.getService();
DatabaseAdminClient dbAdminClient = null;
try {
  final String command = args[0];
  DatabaseId db = DatabaseId.of(options.getProjectId(), args[1], args[2]);
  DatabaseClient dbClient = spanner.getDatabaseClient(db);
  dbAdminClient = spanner.createDatabaseAdminClient();

  // Use client here...
} finally {
  if (dbAdminClient != null) {
    if (!dbAdminClient.isShutdown() || !dbAdminClient.isTerminated()) {
      dbAdminClient.close();
    }
  }
  spanner.close();
}

PostgreSQL

SpannerOptions options = SpannerOptions.newBuilder().build();
Spanner spanner = options.getService();
DatabaseAdminClient dbAdminClient = null;
try {
  DatabaseClient dbClient = spanner.getDatabaseClient(db);
  dbAdminClient = spanner.createDatabaseAdminClient();
} finally {
  if (dbAdminClient != null) {
    if (!dbAdminClient.isShutdown() || !dbAdminClient.isTerminated()) {
      dbAdminClient.close();
    }
  }
  spanner.close();
}

Poiché ogni client utilizza risorse in Spanner, è buona norma chiudere i client non necessari chiamando close().

Scopri di più nel riferimento a Javadoc DatabaseClient.

Scrivi dati con DML

Puoi inserire i dati utilizzando DML (Data Manipulation Language) in una transazione di lettura e scrittura.

Usa il metodo executeUpdate() per eseguire un'istruzione DML.

static void writeUsingDml(DatabaseClient dbClient) {
  // Insert 4 singer records
  dbClient
      .readWriteTransaction()
      .run(transaction -> {
        String sql =
            "INSERT INTO Singers (SingerId, FirstName, LastName) VALUES "
                + "(12, 'Melissa', 'Garcia'), "
                + "(13, 'Russell', 'Morales'), "
                + "(14, 'Jacqueline', 'Long'), "
                + "(15, 'Dylan', 'Shaw')";
        long rowCount = transaction.executeUpdate(Statement.of(sql));
        System.out.printf("%d records inserted.\n", rowCount);
        return null;
      });
}

Esegui l'esempio utilizzando l'argomento writeusingdml.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    writeusingdml test-instance example-db

Dovresti vedere:

4 records inserted.

Scrivere dati con mutazioni

Puoi anche inserire i dati utilizzando le mutazioni.

Puoi scrivere dati utilizzando un oggetto Mutation. Un oggetto Mutation è un contenitore per le operazioni di mutazione. Un elemento Mutation rappresenta una sequenza di inserti, aggiornamenti ed eliminazioni che Spanner si applica a livello atomico a diverse righe e tabelle in un database Spanner.

Il metodo newInsertBuilder() nella classe Mutation crea una mutazione INSERT, che inserisce una nuova riga in una tabella. Se la riga esiste già, la scrittura non riesce. In alternativa, puoi utilizzare il metodo newInsertOrUpdateBuilder per creare una modifica INSERT_OR_UPDATE, che aggiorna i valori della colonna se la riga esiste già.

Il metodo write() nella classe DatabaseClient scrive le mutazioni. Tutte le mutazioni in un singolo lotto vengono applicate a livello atomico.

Questo codice mostra come scrivere i dati utilizzando le mutazioni:

static final List<Singer> SINGERS =
    Arrays.asList(
        new Singer(1, "Marc", "Richards"),
        new Singer(2, "Catalina", "Smith"),
        new Singer(3, "Alice", "Trentor"),
        new Singer(4, "Lea", "Martin"),
        new Singer(5, "David", "Lomond"));

static final List<Album> ALBUMS =
    Arrays.asList(
        new Album(1, 1, "Total Junk"),
        new Album(1, 2, "Go, Go, Go"),
        new Album(2, 1, "Green"),
        new Album(2, 2, "Forever Hold Your Peace"),
        new Album(2, 3, "Terrified"));
static void writeExampleData(DatabaseClient dbClient) {
  List<Mutation> mutations = new ArrayList<>();
  for (Singer singer : SINGERS) {
    mutations.add(
        Mutation.newInsertBuilder("Singers")
            .set("SingerId")
            .to(singer.singerId)
            .set("FirstName")
            .to(singer.firstName)
            .set("LastName")
            .to(singer.lastName)
            .build());
  }
  for (Album album : ALBUMS) {
    mutations.add(
        Mutation.newInsertBuilder("Albums")
            .set("SingerId")
            .to(album.singerId)
            .set("AlbumId")
            .to(album.albumId)
            .set("AlbumTitle")
            .to(album.albumTitle)
            .build());
  }
  dbClient.write(mutations);
}

Esegui l'esempio utilizzando l'argomento write.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    write test-instance example-db

Dovresti vedere che il comando viene eseguito correttamente.

Esegui query sui dati utilizzando SQL

Spanner supporta un'interfaccia SQL per la lettura dei dati, a cui puoi accedere dalla riga di comando utilizzando Google Cloud CLI o in modo programmatico utilizzando la libreria client Spanner per Java.

Nella riga di comando

Esegui questa istruzione SQL per leggere i valori di tutte le colonne della tabella Albums:

gcloud spanner database run-sql example-db --instance=test-instance \ --sql='SELECT SingerId, AlbumId, AlbumTitle FROM Albums'

Il risultato dovrebbe essere:

SingerId AlbumId AlbumTitle
1        1       Total Junk
1        2       Go, Go, Go
2        1       Green
2        2       Forever Hold Your Peace
2        3       Terrified

Utilizzare la libreria client Spanner per Java

Oltre a eseguire un'istruzione SQL sulla riga di comando, puoi emettere la stessa istruzione SQL in modo programmatico utilizzando la libreria client Spanner per Java.

Per eseguire la query SQL vengono utilizzati i metodi e le classi seguenti:

  • Il metodo singleUse() nella classe DatabaseClient: utilizza questo metodo per leggere il valore di una o più colonne da una o più righe di una tabella Spanner. singleUse() restituisce un oggetto ReadContext, utilizzato per eseguire un'istruzione di lettura o SQL.
  • Metodo executeQuery() della classe ReadContext: utilizza questo metodo per eseguire una query su un database.
  • La classe Statement consente di creare una stringa SQL.
  • La classe ResultSet utilizza questa opzione per accedere ai dati restituiti da un'istruzione SQL o da una chiamata di lettura.

Ecco come inviare la query e accedere ai dati:

static void query(DatabaseClient dbClient) {
  try (ResultSet resultSet =
      dbClient
          .singleUse() // Execute a single read or query against Cloud Spanner.
          .executeQuery(Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"))) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %d %s\n", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2));
    }
  }
}

Esegui l'esempio utilizzando l'argomento query.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    query test-instance example-db

Dovresti vedere il seguente risultato:

1 1 Total Junk
1 2 Go, Go, Go
2 1 Green
2 2 Forever Hold Your Peace
2 3 Terrified

Query utilizzando un parametro SQL

Se la tua applicazione ha una query eseguita di frequente, puoi migliorarne le prestazioni parametrizzandola. La query parametrica risultante può essere memorizzata nella cache e riutilizzata, riducendo i costi di compilazione. Per maggiori informazioni, consulta Utilizzare parametri di ricerca per velocizzare le query eseguite di frequente.

Ecco un esempio di utilizzo di un parametro nella clausola WHERE per eseguire query sui record contenenti un valore specifico per LastName.

GoogleSQL

static void queryWithParameter(DatabaseClient dbClient) {
  Statement statement =
      Statement.newBuilder(
              "SELECT SingerId, FirstName, LastName "
                  + "FROM Singers "
                  + "WHERE LastName = @lastName")
          .bind("lastName")
          .to("Garcia")
          .build();
  try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %s %s\n",
          resultSet.getLong("SingerId"),
          resultSet.getString("FirstName"),
          resultSet.getString("LastName"));
    }
  }
}

PostgreSQL

static void queryWithParameter(DatabaseClient dbClient) {
  Statement statement =
      Statement.newBuilder(
              "SELECT singerid AS \"SingerId\", "
                  + "firstname as \"FirstName\", lastname as \"LastName\" "
                  + "FROM Singers "
                  + "WHERE LastName = $1")
          .bind("p1")
          .to("Garcia")
          .build();
  try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %s %s\n",
          resultSet.getLong("SingerId"),
          resultSet.getString("FirstName"),
          resultSet.getString("LastName"));
    }
  }
}

Esegui l'esempio utilizzando l'argomento queryWithParameter.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    querywithparameter test-instance example-db

Dovresti vedere il seguente risultato:

12 Melissa Garcia

Lettura dei dati utilizzando l'API di lettura

Oltre all'interfaccia SQL di Spanner, Spanner supporta anche un'interfaccia di lettura.

Utilizza il metodo read() della classe ReadContext per leggere le righe dal database. Utilizza un oggetto KeySet per definire una raccolta di chiavi e intervalli di chiavi da leggere.

Ecco come leggere i dati:

static void read(DatabaseClient dbClient) {
  try (ResultSet resultSet =
      dbClient
          .singleUse()
          .read(
              "Albums",
              KeySet.all(), // Read all rows in a table.
              Arrays.asList("SingerId", "AlbumId", "AlbumTitle"))) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %d %s\n", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2));
    }
  }
}

Esegui l'esempio utilizzando l'argomento read.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    read test-instance example-db

Dovresti visualizzare un output simile al seguente:

1 1 Total Junk
1 2 Go, Go, Go
2 1 Green
2 2 Forever Hold Your Peace
2 3 Terrified

Aggiorna lo schema del database

Supponi di dover aggiungere una nuova colonna denominata MarketingBudget alla tabella Albums. L'aggiunta di una nuova colonna a una tabella esistente richiede un aggiornamento dello schema del database. Spanner supporta gli aggiornamenti dello schema in un database mentre il database continua a gestire il traffico. Gli aggiornamenti dello schema non richiedono di disconnettere il database e non bloccano intere tabelle o colonne; puoi continuare a scrivere dati nel database durante l'aggiornamento dello schema. Scopri di più sugli aggiornamenti dello schema supportati e sulle prestazioni delle modifiche dello schema nella pagina Eseguire aggiornamenti dello schema.

Aggiungi una colonna

Puoi aggiungere una colonna nella riga di comando utilizzando Google Cloud CLI o in modo programmatico utilizzando la libreria client Spanner per Java.

Nella riga di comando

Utilizza il seguente comando ALTER TABLE per aggiungere la nuova colonna alla tabella:

GoogleSQL

gcloud spanner databases ddl update example-db --instance=test-instance \
    --ddl='ALTER TABLE Albums ADD COLUMN MarketingBudget INT64'

PostgreSQL

gcloud spanner databases ddl update example-db --instance=test-instance \
    --ddl='ALTER TABLE Albums ADD COLUMN MarketingBudget BIGINT'

Dovresti vedere:

Schema updating...done.

Utilizzare la libreria client Spanner per Java

Utilizza il metodo updateDatabaseDdl() della classe DatabaseAdminClient per modificare lo schema:

GoogleSQL

static void addMarketingBudget(DatabaseAdminClient adminClient, DatabaseName databaseName) {
  try {
    // Initiate the request which returns an OperationFuture.
    adminClient.updateDatabaseDdlAsync(
        databaseName,
        Arrays.asList("ALTER TABLE Albums ADD COLUMN MarketingBudget INT64")).get();
    System.out.println("Added MarketingBudget column");
  } catch (ExecutionException e) {
    // If the operation failed during execution, expose the cause.
    throw (SpannerException) e.getCause();
  } catch (InterruptedException e) {
    // Throw when a thread is waiting, sleeping, or otherwise occupied,
    // and the thread is interrupted, either before or during the activity.
    throw SpannerExceptionFactory.propagateInterrupt(e);
  }
}

PostgreSQL

static void addMarketingBudget(DatabaseAdminClient adminClient, DatabaseName databaseName) {
  try {
    // Initiate the request which returns an OperationFuture.
    adminClient.updateDatabaseDdlAsync(
        databaseName,
        Arrays.asList("ALTER TABLE Albums ADD COLUMN MarketingBudget bigint")).get();
    System.out.println("Added MarketingBudget column");
  } catch (ExecutionException e) {
    // If the operation failed during execution, expose the cause.
    throw (SpannerException) e.getCause();
  } catch (InterruptedException e) {
    // Throw when a thread is waiting, sleeping, or otherwise occupied,
    // and the thread is interrupted, either before or during the activity.
    throw SpannerExceptionFactory.propagateInterrupt(e);
  }
}

Esegui l'esempio utilizzando l'argomento addmarketingbudget.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    addmarketingbudget test-instance example-db

Dovresti vedere:

Added MarketingBudget column.

Scrivi i dati nella nuova colonna

Il codice seguente scrive i dati nella nuova colonna. Imposta MarketingBudget su 100000 per la riga codificata da Albums(1, 1) e su 500000 per la riga codificata da Albums(2, 2).

static void update(DatabaseClient dbClient) {
  // Mutation can be used to update/insert/delete a single row in a table. Here we use
  // newUpdateBuilder to create update mutations.
  List<Mutation> mutations =
      Arrays.asList(
          Mutation.newUpdateBuilder("Albums")
              .set("SingerId")
              .to(1)
              .set("AlbumId")
              .to(1)
              .set("MarketingBudget")
              .to(100000)
              .build(),
          Mutation.newUpdateBuilder("Albums")
              .set("SingerId")
              .to(2)
              .set("AlbumId")
              .to(2)
              .set("MarketingBudget")
              .to(500000)
              .build());
  // This writes all the mutations to Cloud Spanner atomically.
  dbClient.write(mutations);
}

Esegui l'esempio utilizzando l'argomento update.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    update test-instance example-db

Puoi anche eseguire una query SQL o una chiamata di lettura per recuperare i valori che hai appena scritto.

Ecco il codice per eseguire la query:

GoogleSQL

static void queryMarketingBudget(DatabaseClient dbClient) {
  // Rows without an explicit value for MarketingBudget will have a MarketingBudget equal to
  // null. A try-with-resource block is used to automatically release resources held by
  // ResultSet.
  try (ResultSet resultSet =
      dbClient
          .singleUse()
          .executeQuery(Statement.of("SELECT SingerId, AlbumId, MarketingBudget FROM Albums"))) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %d %s\n",
          resultSet.getLong("SingerId"),
          resultSet.getLong("AlbumId"),
          // We check that the value is non null. ResultSet getters can only be used to retrieve
          // non null values.
          resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget"));
    }
  }
}

PostgreSQL

static void queryMarketingBudget(DatabaseClient dbClient) {
  // Rows without an explicit value for MarketingBudget will have a MarketingBudget equal to
  // null. A try-with-resource block is used to automatically release resources held by
  // ResultSet.
  try (ResultSet resultSet =
      dbClient
          .singleUse()
          .executeQuery(Statement.of("SELECT singerid as \"SingerId\", "
              + "albumid as \"AlbumId\", marketingbudget as \"MarketingBudget\" "
              + "FROM Albums"))) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %d %s\n",
          resultSet.getLong("SingerId"),
          resultSet.getLong("AlbumId"),
          // We check that the value is non null. ResultSet getters can only be used to retrieve
          // non null values.
          resultSet.isNull("MarketingBudget") ? "NULL" :
              resultSet.getLong("MarketingBudget"));
    }
  }
}

Per eseguire questa query, esegui l'esempio utilizzando l'argomento querymarketingbudget.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    querymarketingbudget test-instance example-db

Dovresti vedere:

1 1 100000
1 2 NULL
2 1 NULL
2 2 500000
2 3 NULL

Aggiorna dati

Puoi aggiornare i dati utilizzando DML in una transazione di lettura e scrittura.

Per eseguire un'istruzione DML, utilizzerai il metodo executeUpdate().

GoogleSQL

static void writeWithTransactionUsingDml(DatabaseClient dbClient) {
  dbClient
      .readWriteTransaction()
      .run(transaction -> {
        // Transfer marketing budget from one album to another. We do it in a transaction to
        // ensure that the transfer is atomic.
        String sql1 =
            "SELECT MarketingBudget from Albums WHERE SingerId = 2 and AlbumId = 2";
        ResultSet resultSet = transaction.executeQuery(Statement.of(sql1));
        long album2Budget = 0;
        while (resultSet.next()) {
          album2Budget = resultSet.getLong("MarketingBudget");
        }
        // Transaction will only be committed if this condition still holds at the time of
        // commit. Otherwise it will be aborted and the callable will be rerun by the
        // client library.
        long transfer = 200000;
        if (album2Budget >= transfer) {
          String sql2 =
              "SELECT MarketingBudget from Albums WHERE SingerId = 1 and AlbumId = 1";
          ResultSet resultSet2 = transaction.executeQuery(Statement.of(sql2));
          long album1Budget = 0;
          while (resultSet2.next()) {
            album1Budget = resultSet2.getLong("MarketingBudget");
          }
          album1Budget += transfer;
          album2Budget -= transfer;
          Statement updateStatement =
              Statement.newBuilder(
                      "UPDATE Albums "
                          + "SET MarketingBudget = @AlbumBudget "
                          + "WHERE SingerId = 1 and AlbumId = 1")
                  .bind("AlbumBudget")
                  .to(album1Budget)
                  .build();
          transaction.executeUpdate(updateStatement);
          Statement updateStatement2 =
              Statement.newBuilder(
                      "UPDATE Albums "
                          + "SET MarketingBudget = @AlbumBudget "
                          + "WHERE SingerId = 2 and AlbumId = 2")
                  .bind("AlbumBudget")
                  .to(album2Budget)
                  .build();
          transaction.executeUpdate(updateStatement2);
        }
        return null;
      });
}

PostgreSQL

static void writeWithTransactionUsingDml(DatabaseClient dbClient) {
  dbClient
      .readWriteTransaction()
      .run(transaction -> {
        // Transfer marketing budget from one album to another. We do it in a transaction to
        // ensure that the transfer is atomic.
        String sql1 =
            "SELECT marketingbudget as \"MarketingBudget\" from Albums WHERE "
                + "SingerId = 2 and AlbumId = 2";
        ResultSet resultSet = transaction.executeQuery(Statement.of(sql1));
        long album2Budget = 0;
        while (resultSet.next()) {
          album2Budget = resultSet.getLong("MarketingBudget");
        }
        // Transaction will only be committed if this condition still holds at the time of
        // commit. Otherwise it will be aborted and the callable will be rerun by the
        // client library.
        long transfer = 200000;
        if (album2Budget >= transfer) {
          String sql2 =
              "SELECT marketingbudget as \"MarketingBudget\" from Albums WHERE "
                  + "SingerId = 1 and AlbumId = 1";
          ResultSet resultSet2 = transaction.executeQuery(Statement.of(sql2));
          long album1Budget = 0;
          while (resultSet2.next()) {
            album1Budget = resultSet2.getLong("MarketingBudget");
          }
          album1Budget += transfer;
          album2Budget -= transfer;
          Statement updateStatement =
              Statement.newBuilder(
                      "UPDATE Albums "
                          + "SET MarketingBudget = $1"
                          + "WHERE SingerId = 1 and AlbumId = 1")
                  .bind("p1")
                  .to(album1Budget)
                  .build();
          transaction.executeUpdate(updateStatement);
          Statement updateStatement2 =
              Statement.newBuilder(
                      "UPDATE Albums "
                          + "SET MarketingBudget = $1"
                          + "WHERE SingerId = 2 and AlbumId = 2")
                  .bind("p1")
                  .to(album2Budget)
                  .build();
          transaction.executeUpdate(updateStatement2);
        }
        return null;
      });
}

Esegui l'esempio utilizzando l'argomento writewithtransactionusingdml.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    writewithtransactionusingdml test-instance example-db

Utilizza un indice secondario

Supponi di voler recuperare tutte le righe di Albums che hanno valori AlbumTitle in un determinato intervallo. Puoi leggere tutti i valori della colonna AlbumTitle utilizzando un'istruzione SQL o una chiamata di lettura, quindi ignorare le righe che non soddisfano i criteri, ma eseguire questa scansione completa della tabella è costoso, soprattutto per le tabelle con molte righe. Puoi invece velocizzare il recupero delle righe durante la ricerca per colonne di chiave non primaria creando un indice secondario nella tabella.

L'aggiunta di un indice secondario a una tabella esistente richiede un aggiornamento dello schema. Come altri aggiornamenti dello schema, Spanner supporta l'aggiunta di un indice mentre il database continua a gestire il traffico. Spanner esegue automaticamente il backfill dell'indice con i dati esistenti. Il completamento dei backfill potrebbe richiedere alcuni minuti, ma non è necessario portare il database offline o evitare di scrivere nella tabella indicizzata durante questo processo. Per maggiori dettagli, consulta Aggiungere un indice secondario.

Dopo aver aggiunto un indice secondario, Spanner lo utilizza automaticamente per le query SQL che hanno maggiori probabilità di essere eseguite più velocemente con l'indice. Se utilizzi l'interfaccia di lettura, devi specificare l'indice che vuoi utilizzare.

Aggiungi un indice secondario

Puoi aggiungere un indice dalla riga di comando utilizzando gcloud CLI o in modo programmatico utilizzando la libreria client Spanner per Java.

Nella riga di comando

Utilizza il seguente comando CREATE INDEX per aggiungere un indice al database:

gcloud spanner databases ddl update example-db --instance=test-instance \
    --ddl='CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)'

Dovresti vedere:

Schema updating...done.

Utilizzo della libreria client Spanner per Java

Utilizza il metodo updateDatabaseDdl() della classe DatabaseAdminClient per aggiungere un indice:

static void addIndex(DatabaseAdminClient adminClient, DatabaseName databaseName) {
  try {
    // Initiate the request which returns an OperationFuture.
    adminClient.updateDatabaseDdlAsync(
        databaseName,
        Arrays.asList("CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)")).get();
    System.out.println("Added AlbumsByAlbumTitle index");
  } catch (ExecutionException e) {
    // If the operation failed during execution, expose the cause.
    throw (SpannerException) e.getCause();
  } catch (InterruptedException e) {
    // Throw when a thread is waiting, sleeping, or otherwise occupied,
    // and the thread is interrupted, either before or during the activity.
    throw SpannerExceptionFactory.propagateInterrupt(e);
  }
}

Esegui l'esempio utilizzando l'argomento addindex.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    addindex test-instance example-db

L'aggiunta di un indice può richiedere alcuni minuti. Dopo aver aggiunto l'indice, dovresti vedere:

Added the AlbumsByAlbumTitle index.

Lettura utilizzando l'indice

Per le query SQL, Spanner utilizza automaticamente un indice appropriato. Nell'interfaccia di lettura, devi specificare l'indice nella richiesta.

Per utilizzare l'indice nell'interfaccia di lettura, usa il metodo readUsingIndex() della classe ReadContext.

Il seguente codice recupera tutte le colonne AlbumId e AlbumTitle dall'indice AlbumsByAlbumTitle.

static void readUsingIndex(DatabaseClient dbClient) {
  try (ResultSet resultSet =
      dbClient
          .singleUse()
          .readUsingIndex(
              "Albums",
              "AlbumsByAlbumTitle",
              KeySet.all(),
              Arrays.asList("AlbumId", "AlbumTitle"))) {
    while (resultSet.next()) {
      System.out.printf("%d %s\n", resultSet.getLong(0), resultSet.getString(1));
    }
  }
}

Esegui l'esempio utilizzando l'argomento readindex.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    readindex test-instance example-db

Dovresti vedere:

2 Forever Hold Your Peace
2 Go, Go, Go
1 Green
3 Terrified
1 Total Junk

Aggiungi un indice per le letture solo dell'indice

Potresti aver notato che l'esempio di lettura precedente non include la lettura della colonna MarketingBudget. Questo perché l'interfaccia di lettura di Spanner non supporta la possibilità di unire un indice a una tabella di dati per cercare valori non archiviati nell'indice.

Crea una definizione alternativa di AlbumsByAlbumTitle che archivia una copia di MarketingBudget nell'indice.

Nella riga di comando

GoogleSQL

gcloud spanner databases ddl update example-db --instance=test-instance \
    --ddl='CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)

PostgreSQL

gcloud spanner databases ddl update example-db --instance=test-instance \
    --ddl='CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) INCLUDE (MarketingBudget)

L'aggiunta di un indice può richiedere alcuni minuti. Dopo aver aggiunto l'indice, dovresti vedere:

Schema updating...done.

Utilizzo della libreria client Spanner per Java

Utilizza il metodo updateDatabaseDdl() della classe DatabaseAdminClient per aggiungere un indice con una clausola STORING per GoogleSQL e la clausola INCLUDE per PostgreSQL:

GoogleSQL

static void addStoringIndex(DatabaseAdminClient adminClient, DatabaseName databaseName) {
  try {
    // Initiate the request which returns an OperationFuture.
    adminClient.updateDatabaseDdlAsync(
        databaseName,
        Arrays.asList(
            "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) "
                + "STORING (MarketingBudget)")).get();
    System.out.println("Added AlbumsByAlbumTitle2 index");
  } catch (ExecutionException e) {
    // If the operation failed during execution, expose the cause.
    throw (SpannerException) e.getCause();
  } catch (InterruptedException e) {
    // Throw when a thread is waiting, sleeping, or otherwise occupied,
    // and the thread is interrupted, either before or during the activity.
    throw SpannerExceptionFactory.propagateInterrupt(e);
  }
}

PostgreSQL

static void addStoringIndex(DatabaseAdminClient adminClient, DatabaseName databaseName) {
  try {
    // Initiate the request which returns an OperationFuture.
    adminClient.updateDatabaseDdlAsync(
        databaseName,
        Arrays.asList(
            "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) "
                + "INCLUDE (MarketingBudget)")).get();
    System.out.println("Added AlbumsByAlbumTitle2 index");
  } catch (ExecutionException e) {
    // If the operation failed during execution, expose the cause.
    throw (SpannerException) e.getCause();
  } catch (InterruptedException e) {
    // Throw when a thread is waiting, sleeping, or otherwise occupied,
    // and the thread is interrupted, either before or during the activity.
    throw SpannerExceptionFactory.propagateInterrupt(e);
  }
}

Esegui l'esempio utilizzando l'argomento addstoringindex.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    addstoringindex test-instance example-db

L'aggiunta di un indice può richiedere alcuni minuti. Dopo aver aggiunto l'indice, dovresti vedere:

Added AlbumsByAlbumTitle2 index

Ora puoi eseguire una lettura che recupera tutte le colonne AlbumId, AlbumTitle e MarketingBudget dall'indice AlbumsByAlbumTitle2:

static void readStoringIndex(DatabaseClient dbClient) {
  // We can read MarketingBudget also from the index since it stores a copy of MarketingBudget.
  try (ResultSet resultSet =
      dbClient
          .singleUse()
          .readUsingIndex(
              "Albums",
              "AlbumsByAlbumTitle2",
              KeySet.all(),
              Arrays.asList("AlbumId", "AlbumTitle", "MarketingBudget"))) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %s %s\n",
          resultSet.getLong(0),
          resultSet.getString(1),
          resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget"));
    }
  }
}

Esegui l'esempio utilizzando l'argomento readstoringindex.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    readstoringindex test-instance example-db

Dovresti visualizzare un output simile al seguente:

2 Forever Hold Your Peace 300000
2 Go, Go, Go NULL
1 Green NULL
3 Terrified NULL
1 Total Junk 300000

Recuperare i dati utilizzando le transazioni di sola lettura

Supponi di voler eseguire più di una lettura allo stesso timestamp. Le transazioni di sola lettura osservano un prefisso coerente nella cronologia di commit delle transazioni, quindi l'applicazione riceve sempre dati coerenti. Utilizza un oggetto ReadOnlyTransaction per l'esecuzione di transazioni di sola lettura. Usa il metodo readOnlyTransaction() della classe DatabaseClient per ottenere un oggetto ReadOnlyTransaction.

Di seguito viene mostrato come eseguire una query ed eseguire una lettura nella stessa transazione di sola lettura:

static void readOnlyTransaction(DatabaseClient dbClient) {
  // ReadOnlyTransaction must be closed by calling close() on it to release resources held by it.
  // We use a try-with-resource block to automatically do so.
  try (ReadOnlyTransaction transaction = dbClient.readOnlyTransaction()) {
    ResultSet queryResultSet =
        transaction.executeQuery(
            Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"));
    while (queryResultSet.next()) {
      System.out.printf(
          "%d %d %s\n",
          queryResultSet.getLong(0), queryResultSet.getLong(1), queryResultSet.getString(2));
    }
    try (ResultSet readResultSet =
        transaction.read(
            "Albums", KeySet.all(), Arrays.asList("SingerId", "AlbumId", "AlbumTitle"))) {
      while (readResultSet.next()) {
        System.out.printf(
            "%d %d %s\n",
            readResultSet.getLong(0), readResultSet.getLong(1), readResultSet.getString(2));
      }
    }
  }
}

Esegui l'esempio utilizzando l'argomento readonlytransaction.

java -jar target/spanner-snippets/spanner-google-cloud-samples.jar \
    readonlytransaction test-instance example-db

Dovresti visualizzare un output simile al seguente:

2 2 Forever Hold Your Peace
1 2 Go, Go, Go
2 1 Green
2 3 Terrified
1 1 Total Junk
1 1 Total Junk
1 2 Go, Go, Go
2 1 Green
2 2 Forever Hold Your Peace
2 3 Terrified

esegui la pulizia

Per evitare che al tuo account di fatturazione Cloud vengano addebitati costi aggiuntivi per le risorse utilizzate in questo tutorial, elimina il database ed elimina l'istanza che hai creato.

Elimina il database

Se elimini un'istanza, tutti i database al suo interno vengono eliminati automaticamente. Questo passaggio mostra come eliminare un database senza eliminare un'istanza (ti verranno comunque addebitati costi per l'istanza).

Nella riga di comando

gcloud spanner databases delete example-db --instance=test-instance

Utilizzo della console Google Cloud

  1. Vai alla pagina Istanze Spanner nella console Google Cloud.

    Vai alla pagina Istanze

  2. Fai clic sull'istanza.

  3. Fai clic sul database che vuoi eliminare.

  4. Nella pagina Dettagli database, fai clic su Elimina.

  5. Conferma di voler eliminare il database e fai clic su Elimina.

Elimina l'istanza

L'eliminazione di un'istanza comporta l'eliminazione automatica di tutti i database creati nell'istanza.

Nella riga di comando

gcloud spanner instances delete test-instance

Utilizzo della console Google Cloud

  1. Vai alla pagina Istanze Spanner nella console Google Cloud.

    Vai alla pagina Istanze

  2. Fai clic sull'istanza.

  3. Fai clic su Elimina.

  4. Conferma di voler eliminare l'istanza e fai clic su Elimina.

Passaggi successivi