Cloud Spanner com Hibernate ORM

O Hibernate se tornou a solução ORM padrão de fato para projetos Java. Ele suporta todos os principais bancos de dados relacionais e permite ferramentas ORM ainda mais poderosas, como Spring Data JPA . Além disso, existem muitos frameworks compatíveis com o Hibernate, como Spring Boot, Microprofile e Quarkus.

O dialeto do Cloud Spanner para Hibernate ORM possibilita o uso do Hibernate com o Cloud Spanner. Você obtém os benefícios do Cloud Spanner - escalabilidade e semântica relacional - com a persistência idiomática do Hibernate. Isso pode ajudá-lo a migrar aplicativos existentes para a nuvem ou escrever novos, aproveitando o aumento da produtividade do desenvolvedor proporcionado pelas tecnologias baseadas em Hibernate.

O que você aprenderá

  • Como escrever um aplicativo Hibernate simples que se conecta ao Cloud Spanner
  • Como criar um banco de dados Cloud Spanner
  • Como usar o dialeto do Cloud Spanner para Hibernate ORM
  • Como implementar operações criar-ler-atualizar-deletar (CRUD) com Hibernate

O que você precisará

  • Um projeto do Google Cloud Platform
  • Um navegador, como Chrome ou Firefox

Configuração de ambiente individualizada

  1. Faça login no Cloud Console e crie um novo projeto ou reutilize um existente. (Se você ainda não tem uma conta do Gmail ou do G Suite, deve criar uma .)

k6ai2NRmxIjV5iMFlVgA_ZyAWE4fhRrkrZZZ5mZuCas81YLgk0iwIyvgoDet4s2lMYGC5K3xLSOjIbmC9kjiezvQuxuhdYRolbv1rcaq1gzNlOmA2-JRolbv1rft1gzNlOgA2-P183Y3AqP3AqPlOm5A2P185A2A2A2A5Aq.

UtcCMcSYtCOrEWuILx3XBwb3GILPqXDd6cJiQQxmylg8GNftqlnE7u8aJLhlr1ZLRkpncKdj8ERnqcH71wab2HlfUnO9CgXKd0xt0-CAQC2Rt3CH4

KoK3nfWQ73s_x4QI69xqzqdDR4tUuNmrv4FC9Yq8vtK5IVm49h_8h6x9X281hAcJcOFDtX7g2BXPvP5O7SOR2V4UI6W8gN6cTJCVAdtWHRrS89zH-qWE0IQdjFpOs_8T-s4vQCXA6w

Lembre-se do ID do projeto, um nome exclusivo em todos os projetos do Google Cloud (o nome acima já foi escolhido e não funcionará para você, desculpe!). Ele será referido posteriormente neste codelab como PROJECT_ID .

  1. Em seguida, você precisará habilitar o faturamento no Console do Cloud para usar os recursos do Google Cloud.

Executar esse codelab não deve custar muito, se é que deve custar nada. Certifique-se de seguir todas as instruções na seção "Limpeza", que informa como encerrar os recursos para não incorrer em cobranças além deste tutorial. Novos usuários do Google Cloud estão qualificados para o programa de avaliação gratuita de US $ 300 .

Ative o Cloud Shell

  1. No Cloud Console, clique em Ativar Cloud Shell R47NpBm6yyzso5vnxnRBikeDAXxl3LsM6tip3rJxnKuS2EZdCI0h-eIOGm9aECq8JXbMFlJkd68uTutXU8gGmQUVa5iI1OdZczXP2tzqZ_mj0pR4sZ8eSwOwUlWADR7ARCqrMTQPQA .

STsYbcAtkIQyN6nL9BJhld3Fv5KxedYynpUVcRWwvIR-sYMMc4kfK-unIYgtsD4P6T0P8z-A12388tPmAh-Trsx80qobaW4KQXHJ7qbUsJ6rwurm2HiHciYsJ6rwurm2 -HiHciYs_6rwurm2-Hi-Yci_6rwurm76-Trsx80qobaW4KQXYCYs_WnxM762HiYci_Wnwurm2.

Se você nunca iniciou o Cloud Shell antes, verá uma tela intermediária (abaixo da dobra) descrevendo o que é. Se for esse o caso, clique em Continuar (e você nunca mais verá). Esta é a aparência dessa tela única:

LnGMTn1ObgwWFtWpjSdzlA9TDvSbcY76GiLQLc_f7RP1QBK1Tl4H6kLCHzsi89Lkc-serOpqNH-F2XKmV5AnBqTbPon4HvCwNSSrY_ERxUF5HeVR1QRPx5Fr1RQlRQlRUFr1Kr1RQlRUFr1KmV5AnBqTbPon5Fr1KmV2

Deve levar apenas alguns minutos para provisionar e se conectar ao Cloud Shell.

hfu9bVHmrWw01Hnrlf4MBNja6yvssDnZzN9oupcG12PU88Vvo30tTluX9IySwnu5_TG3U2UXAasX9eCwqwZtc6Yhwxri95zG82DLUcjKxrFYaZanoXnVd7

Esta máquina virtual é carregada com todas as ferramentas de desenvolvimento de que você precisa. Ele oferece um diretório inicial persistente de 5 GB e é executado no Google Cloud, melhorando muito o desempenho e a autenticação da rede. Muito, senão todo, o seu trabalho neste codelab pode ser feito simplesmente com um navegador ou seu Chromebook.

Depois de conectado ao Cloud Shell, você verá que já está autenticado e que o projeto já está definido com o ID do seu projeto.

  1. Execute o seguinte comando no Cloud Shell para confirmar que você está autenticado:
gcloud auth list

Saída de comando

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
gcloud config list project

Saída de comando

[core]
project = <PROJECT_ID>

Se não estiver, você pode defini-lo com este comando:

gcloud config set project <PROJECT_ID>

Saída de comando

Updated property [core/project].

Após o lançamento do Cloud Shell, você pode começar a usar o gcloud para interagir com seu projeto do GCP.

Primeiro, ative a API Cloud Spanner.

gcloud services enable spanner.googleapis.com

Agora, vamos criar uma instância do Cloud Spanner chamada codelab-instance .

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

Agora, precisamos adicionar um banco de dados a esta instância. Vamos chamá-lo de codelab-db .

gcloud spanner databases create codelab-db --instance=codelab-instance

Usaremos o Maven Quickstart Archetype para criar um aplicativo de console Java simples.

mvn archetype:generate \
 -DgroupId=codelab \
 -DartifactId=spanner-hibernate-codelab \
 -DarchetypeArtifactId=maven-archetype-quickstart \
 -DarchetypeVersion=1.4 \
 -DinteractiveMode=false

Mude para o diretório do aplicativo.

cd spanner-hibernate-codelab

Compile e execute o aplicativo usando o Maven.

mvn compile exec:java -Dexec.mainClass=codelab.App

Você deverá ver Hello World! impresso no console.

Vamos explorar o código-fonte abrindo o Editor do Cloud Shell e navegando dentro do diretório spanner-hibernate-codelab .

b5cb37d043d4d2b0.png

Até agora, temos apenas um aplicativo de console Java básico que imprime "Hello World!" . No entanto, realmente queremos escrever um aplicativo Java que use o Hibernate para se comunicar com o Cloud Spanner. Para isso, precisaremos do dialeto Cloud Spanner para Hibernate, do driver JDBC do Cloud Spanner e do núcleo do Hibernate. Portanto, vamos adicionar as seguintes dependências ao bloco <dependencies> dentro do arquivo pom.xml .

pom.xml

    <!-- Spanner Dialect -->
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-spanner-hibernate-dialect</artifactId>
      <version>1.5.0</version>
    </dependency>

    <!-- JDBC Driver -->
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-spanner-jdbc</artifactId>
      <version>2.0.0</version>
    </dependency>

    <!-- Hibernate -->
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-core</artifactId>
      <version>5.4.29.Final</version>
    </dependency>

A seguir, criaremos os arquivos de configuração do Hibernate hibernate.cfg.xml e hibernate.properties . Execute o seguinte comando para criar os arquivos vazios e, em seguida, edite-os usando o Editor do Cloud Shell.

mkdir src/main/resources \
 && touch src/main/resources/hibernate.cfg.xml \
 && touch src/main/resources/hibernate.properties

Então, vamos contar ao Hibernate sobre as classes de entidade anotadas que iremos mapear para o banco de dados preenchendo o hibernate.cfg.xml . (Criaremos as classes de entidade mais tarde.)

src / main / resources / hibernate.cfg.xml

<hibernate-configuration>
  <session-factory>
    <!-- Annotated entity classes -->
    <mapping class="codelab.Album"/>
    <mapping class="codelab.Singer"/>
  </session-factory>
</hibernate-configuration>

O Hibernate também precisa saber como se conectar à instância do Cloud Spanner e qual dialeto usar. Portanto, diremos a ele para usar o SpannerDialect para sintaxe SQL, o driver Spanner JDBC e a string de conexão JDBC com as coordenadas do banco de dados. Isso vai para o arquivo hibernate.properties .

src / main / resources / hibernate.properties

hibernate.dialect=com.google.cloud.spanner.hibernate.SpannerDialect
hibernate.connection.driver_class=com.google.cloud.spanner.jdbc.JdbcDriver
hibernate.connection.url=jdbc:cloudspanner:/projects/{PROJECT_ID}/instances/codelab-instance/databases/codelab-db
# auto-create or update DB schema
hibernate.hbm2ddl.auto=update
hibernate.show_sql=true

Lembre-se de substituir {PROJECT_ID} pelo ID do projeto, que você pode obter executando o seguinte comando:

gcloud config get-value project

Como não temos um esquema de banco de dados existente, adicionamos a propriedade hibernate.hbm2ddl.auto=update para permitir que o Hibernate crie as duas tabelas no Cloud Spanner quando executamos o aplicativo pela primeira vez.

Normalmente, você também deve verificar se as credenciais de autenticação estão configuradas, usando um arquivo JSON de conta de serviço na variável de ambiente GOOGLE_APPLICATION_CREDENTIALS ou as credenciais padrão do aplicativo configuradas usando o gcloud auth application-default login No entanto, como estamos executando no Cloud Shell, as credenciais padrão do projeto já estão configuradas.

Agora estamos prontos para escrever algum código.

Definiremos dois objetos Java simples (POJOs) que serão mapeados para tabelas no Cloud Spanner - Singer e Album . O Album terá um relacionamento @ManyToOne com Singer . Também poderíamos ter mapeado Singer s para listas de seus Album com uma anotação @OneToMany , mas para este exemplo, não queremos realmente carregar todos os álbuns sempre que precisarmos buscar um cantor no banco de dados.

Adicione as classes de entidade Singer e Album .

Crie os arquivos da classe.

touch src/main/java/codelab/Singer.java \
&& touch src/main/java/codelab/Album.java

Cole o conteúdo dos arquivos.

src / main / java / codelab / Singer.java

package codelab;

import java.util.Date;
import java.util.UUID;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.hibernate.annotations.Type;

@Entity
public class Singer {

  @Id
  @GeneratedValue
  @Type(type = "uuid-char")
  private UUID singerId;

  private String firstName;

  private String lastName;

  @Temporal(TemporalType.DATE)
  private Date birthDate;

  public Singer() {
  }

  public Singer(String firstName, String lastName, Date birthDate) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.birthDate = birthDate;
  }

  public UUID getSingerId() {
    return singerId;
  }

  public void setSingerId(UUID singerId) {
    this.singerId = singerId;
  }

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  public Date getBirthDate() {
    return birthDate;
  }

  public void setBirthDate(Date birthDate) {
    this.birthDate = birthDate;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (!(o instanceof Singer)) {
      return false;
    }

    Singer singer = (Singer) o;

    if (!firstName.equals(singer.firstName)) {
      return false;
    }
    if (!lastName.equals(singer.lastName)) {
      return false;
    }
    return birthDate.equals(singer.birthDate);
  }

  @Override
  public int hashCode() {
    int result = firstName.hashCode();
    result = 31 * result + lastName.hashCode();
    result = 31 * result + birthDate.hashCode();
    return result;
  }
}

src / main / java / codelab / Album.java

package codelab;

import java.util.UUID;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import org.hibernate.annotations.Type;

@Entity
public class Album {

  @Id
  @GeneratedValue
  @Type(type = "uuid-char")
  UUID albumId;

  @ManyToOne
  Singer singer;

  String albumTitle;

  public Album() {
  }

  public Album(Singer singer, String albumTitle) {
    this.singer = singer;
    this.albumTitle = albumTitle;
  }

  public UUID getAlbumId() {
    return albumId;
  }

  public void setAlbumId(UUID albumId) {
    this.albumId = albumId;
  }

  public Singer getSinger() {
    return singer;
  }

  public void setSinger(Singer singer) {
    this.singer = singer;
  }

  public String getAlbumTitle() {
    return albumTitle;
  }

  public void setAlbumTitle(String albumTitle) {
    this.albumTitle = albumTitle;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (!(o instanceof Album)) {
      return false;
    }

    Album album = (Album) o;

    if (!singer.equals(album.singer)) {
      return false;
    }
    return albumTitle.equals(album.albumTitle);
  }

  @Override
  public int hashCode() {
    int result = singer.hashCode();
    result = 31 * result + albumTitle.hashCode();
    return result;
  }
}

Observe que, para este exemplo, estamos usando um UUID gerado automaticamente para a chave primária. Este é um tipo de ID preferencial no Cloud Spanner, porque evita pontos de acesso à medida que o sistema divide os dados entre os servidores por intervalos de chaves. Uma chave inteira monotonicamente crescente também funcionaria, mas pode ter menos desempenho.

Com tudo configurado e objetos de entidade definidos, podemos começar a escrever no banco de dados e consultá-lo. Iremos abrir uma Session Hibernate e então usá-la para primeiro deletar todas as linhas da tabela no método clearData() , salvar algumas entidades no método writeData() e executar algumas consultas usando a linguagem de consulta Hibernate (HQL) no readData() método.

Substitua o conteúdo de App.java pelo seguinte:

src / main / java / codelab / App.java

package codelab;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;

public class App {

  public final static DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");

  public static void main(String[] args) {
    // create a Hibernate sessionFactory and session
    StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build();
    SessionFactory sessionFactory = new MetadataSources(registry).buildMetadata()
        .buildSessionFactory();
    Session session = sessionFactory.openSession();

    clearData(session);

    writeData(session);

    readData(session);

    // close Hibernate session and sessionFactory
    session.close();
    sessionFactory.close();
  }

  private static void clearData(Session session) {
    session.beginTransaction();

    session.createQuery("delete from Album where 1=1").executeUpdate();
    session.createQuery("delete from Singer where 1=1").executeUpdate();

    session.getTransaction().commit();
  }

  private static void writeData(Session session) {
    session.beginTransaction();

    Singer singerMelissa = new Singer("Melissa", "Garcia", makeDate("1981-03-19"));
    Album albumGoGoGo = new Album(singerMelissa, "Go, Go, Go");
    session.save(singerMelissa);
    session.save(albumGoGoGo);

    session.save(new Singer("Russell", "Morales", makeDate("1978-12-02")));
    session.save(new Singer("Jacqueline", "Long", makeDate("1990-07-29")));
    session.save(new Singer("Dylan", "Shaw", makeDate("1998-05-02")));

    session.getTransaction().commit();
  }

  private static void readData(Session session) {
    List<Singer> singers = session.createQuery("from Singer where birthDate >= '1990-01-01' order by lastName")
        .list();
    List<Album> albums = session.createQuery("from Album").list();

    System.out.println("Singers who were born in 1990 or later:");
    for (Singer singer : singers) {
      System.out.println(singer.getFirstName() + " " + singer.getLastName() + " born on "
          + DATE_FORMAT.format(singer.getBirthDate()));
    }

    System.out.println("Albums: ");
    for (Album album : albums) {
      System.out
          .println("\"" + album.getAlbumTitle() + "\" by " + album.getSinger().getFirstName() + " "
              + album.getSinger().getLastName());
    }
  }

  private static Date makeDate(String dateString) {
    try {
      return DATE_FORMAT.parse(dateString);
    } catch (ParseException e) {
      e.printStackTrace();
      return null;
    }
  }
}

Agora, vamos compilar e executar o código. Adicionaremos a opção -Dexec.cleanupDaemonThreads=false para suprimir avisos sobre a limpeza de threads de daemon que o Maven tentará fazer.

mvn compile exec:java -Dexec.mainClass=codelab.App -Dexec.cleanupDaemonThreads=false

Na saída, você deve ver algo assim:

Singers who were born in 1990 or later:
Jacqueline Long born on 1990-07-29
Dylan Shaw born on 1998-05-02
Albums: 
"Go, Go, Go" by Melissa Garcia

Neste ponto, se você acessar o console do Cloud Spanner e visualizar os dados das tabelas Singer e Album no banco de dados, verá algo assim:

f18276ea54cc266f.png

952d9450dd659e75.png

Vamos excluir a instância do Cloud Spanner que criamos no início para garantir que ela não esteja usando recursos desnecessariamente.

gcloud spanner instances delete codelab-instance

Parabéns, você construiu com sucesso um aplicativo Java que usa Hibernate para persistir dados no Cloud Spanner.

  • Você criou uma instância do Cloud Spanner e um banco de dados
  • Você configurou o aplicativo para usar o Hibernate
  • Você criou duas entidades: Artista e Álbum
  • Você gerou automaticamente o esquema de banco de dados para seu aplicativo
  • Você salvou entidades com sucesso no Cloud Spanner e as consultou

Agora você conhece as principais etapas necessárias para escrever um aplicativo Hibernate com o Cloud Spanner.

Qual é o próximo?