1. Visão geral
O Hibernate se tornou a solução ORM padrão de fato para projetos Java. Ele é compatível com os principais bancos de dados relacionais e permite ferramentas ORM ainda mais poderosas, como o Spring Data JPA. Além disso, há muitas estruturas compatíveis com Hibernate, como Spring Boot, Microprofile e Quarkus.
O Dialeto do Cloud Spanner para Hibernate ORM possibilita o uso do Hibernate com o Cloud Spanner. Com a persistência idiomática do Hibernate, você tem os benefícios do Cloud Spanner: escalonabilidade e semântica relacional. Isso pode ajudar você a migrar os aplicativos existentes para a nuvem ou escrever novos, aproveitando o aumento de produtividade do desenvolvedor proporcionado pelas tecnologias baseadas no Hibernate.
O que você vai aprender
- Como criar um aplicativo Hibernate simples que se conecta ao Cloud Spanner
- Como criar um banco de dados do Cloud Spanner
- Como usar o dialeto Cloud Spanner para Hibernate ORM
- Como implementar operações create-read-update-delete (CRUD) com o Hibernate.
O que é necessário
2. Configuração e requisitos
Configuração de ambiente autoguiada
- Faça login no Console do Cloud e crie um novo projeto ou reutilize um existente. Crie uma se você ainda não tiver uma conta do Gmail ou do G Suite.
Lembre-se do código do projeto, um nome exclusivo em todos os projetos do Google Cloud. O nome acima já foi escolhido e não servirá para você. Faremos referência a ele mais adiante neste codelab como PROJECT_ID
.
- Em seguida, será necessário ativar o faturamento no Console do Cloud para usar os recursos do Google Cloud.
A execução deste codelab não será muito cara, se for o caso. Siga todas as instruções na seção "Limpeza", que orienta você sobre como encerrar 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 US$ 300 de avaliação sem custos.
Ativar o Cloud Shell
- No Console do Cloud, clique em Ativar o Cloud Shell.
Se você nunca tiver iniciado o Cloud Shell, verá uma tela intermediária (abaixo da dobra) com a descrição do que ele é. Se esse for o caso, clique em Continuar e você não o verá novamente. Esta é a aparência dessa tela única:
Leva apenas alguns instantes para provisionar e se conectar ao Cloud Shell.
Essa máquina virtual contém todas as ferramentas de desenvolvimento necessárias. Ela oferece um diretório principal persistente de 5 GB, além de ser executada no Google Cloud. Isso aprimora o desempenho e a autenticação da rede. Praticamente todo o seu trabalho neste codelab pode ser feito em um navegador ou no seu Chromebook.
Depois de se conectar ao Cloud Shell, você já estará autenticado e o projeto já estará configurado com seu ID do projeto.
- Execute o seguinte comando no Cloud Shell para confirmar que você está autenticado:
gcloud auth list
Resposta ao 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
Resposta ao comando
[core] project = <PROJECT_ID>
Se o projeto não estiver configurado, configure-o usando este comando:
gcloud config set project <PROJECT_ID>
Resposta ao comando
Updated property [core/project].
3. Criar um banco de dados
Após a inicialização do Cloud Shell, comece 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
4. Criar um app vazio
Vamos usar o arquétipo do guia de início rápido do Maven para criar um aplicativo simples de console Java.
mvn archetype:generate \ -DgroupId=codelab \ -DartifactId=spanner-hibernate-codelab \ -DarchetypeArtifactId=maven-archetype-quickstart \ -DarchetypeVersion=1.4 \ -DinteractiveMode=false
Mude para o diretório do app.
cd spanner-hibernate-codelab
Compile e execute o app usando o Maven.
mvn compile exec:java -Dexec.mainClass=codelab.App
Você verá Hello World!
exibido no console.
5. Adicionar dependências
Para explorar o código-fonte, abra o Editor do Cloud Shell e navegue até o diretório spanner-hibernate-codelab
.
Até agora, temos apenas um app básico de console Java que mostra "Hello World!"
. No entanto, queremos muito criar um aplicativo Java que use o Hibernate para se comunicar com o Cloud Spanner. Para isso, precisamos do dialeto Cloud Spanner para Hibernate, do driver JDBC do Cloud Spanner e do núcleo do Hibernate. Então, 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>
6. Configurar o Hibernate ORM
Em seguida, criaremos os arquivos de configuração do Hibernate hibernate.cfg.xml
e hibernate.properties
. Execute o comando a seguir para criar os arquivos vazios e depois editá-los 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 informar ao Hibernate sobre as classes de entidade com anotação que vamos 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, vamos instruí-lo a usar o SpannerDialect
para a sintaxe SQL, o driver JDBC do Spanner 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 conseguir executando o seguinte comando:
gcloud config get-value project
Como não temos um esquema de banco de dados, adicionamos a propriedade hibernate.hbm2ddl.auto=update
para permitir que o Hibernate crie as duas tabelas no Cloud Spanner quando executarmos o app pela primeira vez.
Normalmente, você também precisa verificar se as credenciais de autenticação estão configuradas usando um arquivo JSON da conta de serviço na variável de ambiente GOOGLE_APPLICATION_CREDENTIALS
ou as credenciais padrão do aplicativo configuradas com o comando gcloud auth application-default login
. No entanto, como estamos em execução no Cloud Shell, as credenciais padrão do projeto já estão configuradas.
7. Criar classes de entidade com anotações
Agora estamos prontos para escrever o código.
Vamos definir dois objetos Java simples e antigos (POJOs, na sigla em inglês) que serão mapeados para tabelas no Cloud Spanner: Singer
e Album
. O Album
terá um relacionamento de @ManyToOne
com Singer
. Também poderíamos ter mapeado Singer
s para listas de Album
s com uma anotação @OneToMany
. Porém, para este exemplo, não queremos carregar todos os álbuns toda vez que precisamos buscar um cantor do banco de dados.
Adicione as classes de entidade Singer
e Album
.
Crie os arquivos da turma.
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;
}
}
Neste exemplo, usamos um UUID gerado automaticamente para a chave primária. Esse é um tipo de ID preferencial no Cloud Spanner, porque evita pontos de acesso quando o sistema divide dados entre servidores por intervalos de chaves. Uma chave de número inteiro que aumenta monotonicamente também funciona, mas pode ter um desempenho menor.
8. Salvar e consultar entidades
Com tudo configurado e os objetos de entidade definidos, podemos começar a gravar no banco de dados e a fazer consultas nele. Abra um Session
do Hibernate. Use-o para excluir 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, na sigla em inglês) no método readData()
.
Substitua os conteúdos 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. Vamos adicionar a opção -Dexec.cleanupDaemonThreads=false
para suprimir avisos sobre a limpeza de linhas de execução do daemon que o Maven vai tentar fazer.
mvn compile exec:java -Dexec.mainClass=codelab.App -Dexec.cleanupDaemonThreads=false
A saída será semelhante a esta:
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, você verá algo assim:
9. Limpar
Vamos excluir a instância do Cloud Spanner que criamos no início para garantir que ela não esteja consumindo recursos desnecessariamente.
gcloud spanner instances delete codelab-instance
10. Parabéns
Parabéns, você criou um aplicativo Java que usa o Hibernate para manter 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 do banco de dados para seu aplicativo
- Você salvou as entidades no Cloud Spanner e as consultou
Agora você sabe as principais etapas necessárias para escrever um aplicativo Hibernate com o Cloud Spanner.