Spanner と Spring Data の統合

Spring Data Spanner モジュールは、Spring Framework で構築された Java アプリケーションで Spanner を使用する際に有用です。

すべての Spring Data モジュールと同様に、Spring Data Spanner では Spanner の整合性の保証とスケーラビリティを保持する Spring ベースのプログラミング モデルが提供されます。その機能は、Spanner 用に設計されたアノテーションを使用した Spring Data JPAHibernate ORM に似ています。 Spanner で Spring Data JPA を使用する方法の詳細については、Spanner を Spring Data JPA(GoogleSQL 言語)と統合するをご覧ください。

Spring についてすでに習熟している場合、Spring Data Spanner を使用すると、アプリケーションで Spanner を使いやすくなり、作成する必要があるコードの量を減らすことができます。

このページでは、Spring Data Spanner を Java アプリケーションに追加する方法について説明します。モジュールの詳細については、Spring Data Spanner リファレンスをご覧ください。

モジュールをインストールします。

Maven を使用する場合は、Spring Cloud GCP の部品表(BOM)と Spring Data Spanner を pom.xml ファイルに追加します。これらの依存関係によって、Spring ApplicationContext に Spring Data Spanner コンポーネントが提供されます。

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>spring-cloud-gcp-dependencies</artifactId>
      <version>3.7.7</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-dependencies</artifactId>
      <version>${spring.boot.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>
  <dependency>
    <groupId>com.google.cloud</groupId>
    <artifactId>spring-cloud-gcp-starter-data-spanner</artifactId>
  </dependency>
</dependencies>

さらに、サービス アカウントを作成し、サービス アカウント キーを使用して Google Cloud で認証する必要もあります。

詳細については、Java 開発環境の設定の手順をご覧ください。Java 用 Google Cloud クライアント ライブラリをインストールする必要はありません。Spring Boot スターターによってクライアント ライブラリが自動的にインストールされます。

モジュールの設定

このセクションでは、Spring Data Spanner のよく使用されるいくつかの構成設定について説明します。設定の完全なリストについては、リファレンス ドキュメントをご覧ください。

インスタンスとデータベースを指定する

デフォルトのインスタンスとデータベースを指定するには、アプリケーションの次の構成プロパティを設定します。

プロパティ 説明
spring.cloud.gcp.spanner.project-id 省略可。Google Cloud プロジェクト ID。spring.cloud.gcp.config.project-id の値をオーバーライドします。
spring.cloud.gcp.spanner.instance-id : Spanner インスタンス ID
spring.cloud.gcp.spanner.database 接続先のデータベース。

Spanner データのモデル化

Spring Data Spanner では、POJO(Plain Old Java Object)を使用して Spanner テーブルに格納するデータをモデリングできます。

データベースの各テーブルに対して、そのテーブル内のレコードを表すエンティティを宣言します。アノテーションを使用して、エンティティとそのプロパティをテーブルとその列にマッピングします。

次のアノテーションを使用して、エンティティとテーブルとのシンプルな関係をモデリングできます。

エンティティ アノテーション
@Column(name = "columnName")

省略可。Spanner テーブルの特定の列にプロパティをマッピングし、名前を自動的にマッピングする命名方法をオーバーライドします。

このプロパティを省略した場合、Spring Data Spanner のデフォルトの命名方法では、Java の camelCase プロパティ名が PascalCase 列名にマッピングされます。たとえば、プロパティ singerId は列名 SingerId にマッピングされます。

@Embedded

プロパティが、主キーのコンポーネントを保持できる埋め込みオブジェクトであることを示します。プロパティを主キーで実際に使用する場合、@PrimaryKey アノテーションも含める必要があります。

@Interleaved

@Interleaved(lazy = true)

プロパティに、現在の行でインターリーブされる行の一覧が含まれていることを示します。

デフォルトでは、Spring Data Spanner はインスタンス作成時にインターリーブされた行をフェッチします。行を必要に応じてフェッチするには、プロパティにアクセスする際に @Interleaved(lazy = true) を使用します。

例: Singer エンティティが Album エントリを子としてインターリーブできた場合、List<Album> プロパティを Singer エンティティに追加します。さらに、@Interleaved アノテーションをプロパティに追加します。

@NotMapped

プロパティがデータベースに保存されず、無視されることを示します。

@PrimaryKey

@PrimaryKey(keyOrder = N)

プロパティが主キーのコンポーネントであり、1 から始まる主キー内のプロパティの位置を示します。デフォルトの keyOrder1 です。

例: @PrimaryKey(keyOrder = 3)

@Table(name = "TABLE_NAME")

エンティティによってモデリングされるテーブル。エンティティの各インスタンスは、テーブル内のレコードを表します。TABLE_NAME をテーブルの名前に置換します。

例: @Table(name = "Singers")

より複雑な関係をモデリングする必要がある場合は、モジュールがサポートするその他のアノテーションの詳細について、Spring Data Spanner リファレンスをご覧ください。

次の例は、Spring Data Spanner の Singers テーブルと Albums テーブルをモデリングする 1 つの方法を示しています。

  • Singer エンティティの場合、この例では、@Interleaved アノテーションを使用した albums プロパティが含まれます。このプロパティには、Singer エンティティによってインターリーブされるアルバムの一覧が含まれます。Spring Data Spanner では、このプロパティに自動的に値が設定されます。
  • Album エンティティの場合、この例では、Spanner に保存されていない relatedAlbums プロパティが含まれます。
import com.google.cloud.spring.data.spanner.core.mapping.Interleaved;
import com.google.cloud.spring.data.spanner.core.mapping.PrimaryKey;
import com.google.cloud.spring.data.spanner.core.mapping.Table;
import java.util.Date;
import java.util.List;

/**
 * An entity and table holding singers.
 */
@Table(name = "Singers")
public class Singer {
  @PrimaryKey
  long singerId;

  String firstName;

  String lastName;

  Date birthDate;

  @Interleaved
  List<Album> albums;
}
import com.google.cloud.spring.data.spanner.core.mapping.NotMapped;
import com.google.cloud.spring.data.spanner.core.mapping.PrimaryKey;
import com.google.cloud.spring.data.spanner.core.mapping.Table;
import java.util.List;

/**
 * An entity class representing an Album.
 */
@Table(name = "Albums")
public class Album {

  @PrimaryKey
  long singerId;

  @PrimaryKey(keyOrder = 2)
  long albumId;

  String albumTitle;

  long marketingBudget;

  @NotMapped
  List<Album> relatedAlbums;

  public Album(long singerId, long albumId, String albumTitle, long marketingBudget) {
    this.singerId = singerId;
    this.albumId = albumId;
    this.albumTitle = albumTitle;
    this.marketingBudget = marketingBudget;
  }
}

データのクエリと変更

Spring Data Spanner を使用してデータをクエリおよび変更するには、SpannerTemplate Bean を取得できます。これは、SpannerOperations を実装します。SpannerTemplate は、データ操作言語(DML)ステートメントによって、SQL クエリを実行し、データを変更する方法を提供します。この Bean を使用して、Spanner の読み取り APIMutation API にアクセスすることもできます。

また、SpannerRepository インターフェースを拡張し、Spanner でデータをクエリおよび変更するすべてのアプリケーション ロジックをカプセル化できます。

次のセクションでは、SpannerTemplateSpannerRepository の操作方法を説明します。

テンプレート Bean の取得

@Autowired アノテーションを使用して、SpannerTemplate Bean を自動的に取得します。その後、クラス全体で SpannerTemplate を使用できます。

次の例は、Bean を取得して使用するクラスを示しています。

import com.google.cloud.spanner.KeySet;
import com.google.cloud.spanner.Statement;
import com.google.cloud.spring.data.spanner.core.SpannerQueryOptions;
import com.google.cloud.spring.data.spanner.core.SpannerTemplate;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * A quick start code for Spring Data Cloud Spanner. It demonstrates how to use SpannerTemplate to
 * execute DML and SQL queries, save POJOs, and read entities.
 */
@Component
public class SpannerTemplateSample {

  @Autowired
  SpannerTemplate spannerTemplate;

  public void runTemplateExample(Singer singer) {
    // Delete all of the rows in the Singer table.
    this.spannerTemplate.delete(Singer.class, KeySet.all());

    // Insert a singer into the Singers table.
    this.spannerTemplate.insert(singer);

    // Read all of the singers in the Singers table.
    List<Singer> allSingers = this.spannerTemplate
        .query(Singer.class, Statement.of("SELECT * FROM Singers"),
                new SpannerQueryOptions().setAllowPartialRead(true));
  }

}

SpannerTemplate Bean を使用して、読み取り専用トランザクション読み取り / 書き込みトランザクションを実行できます。さらに、@Transactional アノテーションを使用して、宣言型トランザクションを作成できます。

リポジトリ Bean の取得

SpannerRepository を使用する場合、@Autowired アノテーションを使用して、リポジトリのインターフェースを実装する Bean を取得できます。リポジトリには、Java 関数を読み取り専用トランザクション読み取り / 書き込みトランザクションとして実行するためのメソッドが含まれています。下位レベルのオペレーションでは、リポジトリで使用するテンプレート Bean を取得できます。

次の例は、Bean を取得して使用するリポジトリとクラスのインターフェースを示しています。

import com.google.cloud.spanner.Key;
import com.google.cloud.spring.data.spanner.repository.SpannerRepository;
import com.google.cloud.spring.data.spanner.repository.query.Query;
import java.util.List;
import org.springframework.data.repository.query.Param;

/**
 * An interface of various Query Methods. The behavior of the queries is defined only by
 * their names, arguments, or annotated SQL strings. The implementation of these functions
 * is generated by Spring Data Cloud Spanner.
 */
public interface SingerRepository extends SpannerRepository<Singer, Key> {
  List<Singer> findByLastName(String lastName);

  int countByFirstName(String firstName);

  int deleteByLastName(String lastName);

  List<Singer> findTop3DistinctByFirstNameAndSingerIdIgnoreCaseOrLastNameOrderByLastNameDesc(
      String firstName, String lastName, long singerId);

  @Query("SELECT * FROM Singers WHERE firstName LIKE '%@fragment';")
  List<Singer> getByQuery(@Param("fragment") String firstNameFragment);
}
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * A quick start code for Spring Data Cloud Spanner.
 * It demonstrates how to use a SpannerRepository to execute read-write queries
 * generated from interface definitions.
 *
 */
@Component
public class SpannerRepositorySample {

  @Autowired
  SingerRepository singerRepository;

  public void runRepositoryExample() {
    List<Singer> lastNameSingers = this.singerRepository.findByLastName("a last name");

    int fistNameCount = this.singerRepository.countByFirstName("a first name");

    int deletedLastNameCount = this.singerRepository.deleteByLastName("a last name");
  }

}

Spanner を管理する

Spanner データベースに関する情報を取得したり、データ定義言語(DDL)ステートメントでスキーマを更新したり、他の管理タスクを行ったりするには、SpannerDatabaseAdminTemplate Bean を取得できます。

@Autowired アノテーションを使用して、Bean を自動的に取得します。その後、クラス全体で SpannerDatabaseAdminTemplate を使用できます。

次の例は、Bean を取得して使用するクラスを示しています。

import com.google.cloud.spring.data.spanner.core.admin.SpannerDatabaseAdminTemplate;
import com.google.cloud.spring.data.spanner.core.admin.SpannerSchemaUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * This sample demonstrates how to generate schemas for interleaved tables from POJOs and how to
 * execute DDL.
 */
@Component
public class SpannerSchemaToolsSample {

  @Autowired
  SpannerDatabaseAdminTemplate spannerDatabaseAdminTemplate;

  @Autowired
  SpannerSchemaUtils spannerSchemaUtils;

  /**
   * Creates the Singers table. Also creates the Albums table, because Albums is interleaved with
   * Singers.
   */
  public void createTableIfNotExists() {
    if (!this.spannerDatabaseAdminTemplate.tableExists("Singers")) {
      this.spannerDatabaseAdminTemplate.executeDdlStrings(
          this.spannerSchemaUtils
              .getCreateTableDdlStringsForInterleavedHierarchy(Singer.class),
          true);
    }
  }

  /**
   * Drops both the Singers and Albums tables using just a reference to the Singer entity type ,
   * because they are interleaved.
   */
  public void dropTables() {
    if (this.spannerDatabaseAdminTemplate.tableExists("Singers")) {
      this.spannerDatabaseAdminTemplate.executeDdlStrings(
          this.spannerSchemaUtils.getDropTableDdlStringsForInterleavedHierarchy(Singer.class),
          false);
    }
  }
}

次のステップ