Spanner를 Spring Data와 통합

Spring Data Spanner 모듈은 Spring Framework로 빌드된 모든 Java 애플리케이션에서 Spanner를 사용할 수 있도록 지원합니다.

Spring Data Spanner는 모든 Spring Data 모듈과 마찬가지로 Spanner의 일관성 보장과 확장성을 유지하는 Spring 기반 프로그래밍 모델을 제공합니다. 이 모듈의 기능은 Spanner용으로 설계된 주석이 있는 Spring Data JPAHibernate ORM과 유사합니다. Spanner에서 Spring Data JPA를 사용하는 방법에 대한 자세한 내용은 Spanner와 Spring Data JPA(GoogleSQL 언어) 통합을 참조하세요.

Spring에 이미 익숙한 사용자라면 Spring Data Spanner를 통해 애플리케이션에서 Spanner 관련 작업을 간편하게 수행하고 작성해야 하는 코드의 양을 줄일 수 있습니다.

이 페이지에서는 자바 애플리케이션에 Spring Data Spanner를 추가하는 방법을 설명합니다. 이 모듈에 대한 자세한 내용은 Spring Data Spanner 참조를 확인하세요.

모듈 설치

Maven을 사용한다면 Spring Cloud GCP BOM(Bill of Materials)과 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에 인증해야 합니다.

자세한 내용은 자바 개발 환경 설정 안내를 참조하세요. 자바용 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를 사용하면 Spanner 테이블에 저장하는 데이터를 POJO(Plain Old Java Object)를 사용하여 모델링할 수 있습니다.

데이터베이스의 각 테이블에 대해 해당 테이블의 레코드를 나타내는 항목을 선언하세요. 또한 주석을 사용하여 항목 및 항목 속성을 테이블 및 테이블 열에 매핑하세요.

다음과 같은 주석을 사용하여 항목과 테이블 간의 간단한 관계를 모델링할 수 있습니다.

항목 주석
@Column(name = "columnName")

선택사항. 속성을 Spanner 테이블의 특정 열에 매핑하여 이름을 자동으로 매핑하는 이름 지정 전략을 재정의합니다.

이 속성을 생략하면 Spring Data Spanner의 기본 이름 지정 전략에 따라 자바 camelCase 속성 이름이 PascalCase 열 이름에 매핑됩니다. 예를 들어 singerId 속성은 열 이름 SingerId에 매핑됩니다.

@Embedded

속성이 기본 키의 구성요소를 포함할 수 있는 삽입된 객체임을 나타냅니다. 속성이 실제로 기본 키에 사용되는 경우에는 @PrimaryKey 주석도 포함해야 합니다.

@Interleaved

@Interleaved(lazy = true)

속성에 현재 행과 함께 인터리브 처리된 행의 목록이 포함되어 있음을 나타냅니다.

기본적으로 Spring Data Spanner는 인스턴스 생성 시 인터리브 처리된 행을 가져옵니다. 속성에 액세스할 때 행 가져오기를 지연 처리하려면 @Interleaved(lazy = true)를 사용합니다.

예: Singer 항목에 인터리브 처리된 Album 항목이 하위 요소로 포함될 수 있으면 Singer 항목에 List<Album> 속성을 추가합니다. 또한 속성에 @Interleaved 주석을 추가합니다.

@NotMapped

속성이 데이터베이스에 저장되지 않으며 무시되어야 함을 나타냅니다.

@PrimaryKey

@PrimaryKey(keyOrder = N)

속성이 기본 키의 구성요소임을 나타내며, 기본 키 내에서의 속성 위치(1부터 시작)를 식별합니다. keyOrder의 기본값은 1입니다.

예: @PrimaryKey(keyOrder = 3)

@Table(name = "TABLE_NAME")

항목이 모델링하는 테이블입니다. 항목의 각 인스턴스는 테이블의 레코드 하나를 나타냅니다. 여기서 TABLE_NAME은 테이블 이름으로 바꿉니다.

예: @Table(name = "Singers")

보다 복잡한 관계를 모델링해야 하는 경우에는 Spring Data Spanner 참조에서 이 모듈이 지원하는 다른 주석에 대한 세부정보를 확인하세요.

다음 예시에서는 SingersAlbums 테이블을 Spring Data Spanner용으로 모델링하는 한 가지 방법을 보여줍니다.

  • Singer 항목의 경우, 이 예시에는 albums 속성과 @Interleaved 주석이 포함되어 있습니다. 이 속성에는 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를 사용하여 데이터를 쿼리하고 수정하려면 SpannerOperations를 구현하는 SpannerTemplate Bean을 확보하면 됩니다. SpannerTemplate데이터 조작 언어(DML) 문으로 SQL 쿼리를 수행하고 데이터를 수정하기 위한 메서드를 제공합니다. 이 Bean을 사용하여 Spanner의 읽기 API변형 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을 확보할 수 있습니다. 저장소에는 자바 함수를 읽기 전용 트랜잭션읽기/쓰기 트랜잭션으로 실행하기 위한 메서드가 포함되어 있습니다. 하위 수준 작업의 경우, 저장소에서 사용하는 템플릿 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);
    }
  }
}

다음 단계