1. Descripción general
Hibernate se convirtió en la solución ORM estándar de facto para proyectos de Java. Es compatible con todas las bases de datos relacionales principales y permite herramientas de ORM aún más potentes, como Spring Data JPA. Además, hay muchos frameworks compatibles con Hibernate, como Spring Boot, Microprofile y Quarkus.
El dialecto de Cloud Spanner para el ORM de Hibernate permite usar Hibernate con Cloud Spanner. Obtienes los beneficios de Cloud Spanner: escalabilidad y semántica relacional, con la persistencia idiomática de Hibernate. Esto puede ayudarte a migrar aplicaciones existentes a la nube o escribir otras nuevas aprovechando la productividad de los desarrolladores que ofrecen las tecnologías basadas en Hibernate.
Qué aprenderás
- Cómo escribir una aplicación de Hibernate simple que se conecte a Cloud Spanner
- Cómo crear una base de datos de Cloud Spanner
- Cómo usar el dialecto de Cloud Spanner para el ORM de Hibernate
- Cómo implementar operaciones create-read-update-delete (CRUD) con Hibernate
Requisitos
2. Configuración y requisitos
Configuración del entorno de autoaprendizaje
- Accede a la consola de Cloud y crea un proyecto nuevo o reutiliza uno existente. (Si todavía no tienes una cuenta de Gmail o de G Suite, debes crear una).
Recuerde el ID de proyecto, un nombre único en todos los proyectos de Google Cloud (el nombre anterior ya se encuentra en uso y no lo podrá usar). Se mencionará más adelante en este codelab como PROJECT_ID
.
- A continuación, deberás habilitar la facturación en la consola de Cloud para usar los recursos de Google Cloud recursos.
Ejecutar este codelab no debería costar mucho, tal vez nada. Asegúrate de seguir las instrucciones de la sección “Realiza una limpieza” en la que se aconseja cómo cerrar recursos para no incurrir en facturación más allá de este instructivo. Los usuarios nuevos de Google Cloud son aptos para participar en el programa Prueba gratuita de $300.
Activar Cloud Shell
- En la consola de Cloud, haz clic en Activar Cloud Shell.
Si nunca ha iniciado Cloud Shell, aparecerá una pantalla intermedia (debajo de la mitad inferior de la página) que describe qué es. Si ese es el caso, haz clic en Continuar (y no volverás a verlo). Así es como se ve la pantalla única:
El aprovisionamiento y la conexión a Cloud Shell solo tomará unos minutos.
Esta máquina virtual está cargada con todas las herramientas de desarrollo que necesitarás. Ofrece un directorio principal persistente de 5 GB y se ejecuta en Google Cloud, lo que permite mejorar considerablemente el rendimiento de la red y la autenticación. Gran parte de tu trabajo en este codelab, si no todo, se puede hacer simplemente con un navegador o tu Chromebook.
Una vez conectado a Cloud Shell, debería ver que ya se autenticó y que el proyecto ya se configuró con tu ID del proyecto.
- En Cloud Shell, ejecuta el siguiente comando para confirmar que está autenticado:
gcloud auth list
Resultado del 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
Resultado del comando
[core] project = <PROJECT_ID>
De lo contrario, puedes configurarlo con el siguiente comando:
gcloud config set project <PROJECT_ID>
Resultado del comando
Updated property [core/project].
3. Crea una base de datos
Después de que se inicie Cloud Shell, puedes comenzar a usar gcloud
para interactuar con tu proyecto de GCP.
Primero, habilita la API de Cloud Spanner.
gcloud services enable spanner.googleapis.com
Ahora, creemos una instancia de Cloud Spanner llamada codelab-instance
.
gcloud spanner instances create codelab-instance \ --config=regional-us-central1 \ --description="Codelab Instance" --nodes=1
Ahora, debemos agregar una base de datos a esta instancia. La llamaremos codelab-db
.
gcloud spanner databases create codelab-db --instance=codelab-instance
4. Crea una app vacía
Usaremos el arquetipo de la guía de inicio rápido de Maven para crear una aplicación de consola de Java simple.
mvn archetype:generate \ -DgroupId=codelab \ -DartifactId=spanner-hibernate-codelab \ -DarchetypeArtifactId=maven-archetype-quickstart \ -DarchetypeVersion=1.4 \ -DinteractiveMode=false
Cambia al directorio de la app.
cd spanner-hibernate-codelab
Compila y ejecuta la app con Maven.
mvn compile exec:java -Dexec.mainClass=codelab.App
Deberías ver Hello World!
impreso en la consola.
5. Cómo agregar dependencias
Exploremos el código fuente abriendo el editor de Cloud Shell y navegando dentro del directorio spanner-hibernate-codelab
.
Hasta ahora, tenemos una app básica de la consola de Java que imprime "Hello World!"
. Sin embargo, queremos escribir una aplicación de Java que use Hibernate para comunicarse con Cloud Spanner. Para ello, necesitaremos Cloud Spanner Dialect for Hibernate, el controlador JDBC de Cloud Spanner y el núcleo de Hibernate. Por lo tanto, agreguemos las siguientes dependencias al bloque <dependencies>
dentro del archivo 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. Configura Hibernate ORM
A continuación, crearemos los archivos de configuración de Hibernate hibernate.cfg.xml
y hibernate.properties
. Ejecuta el siguiente comando para crear los archivos vacíos y, luego, edítalos con el editor de Cloud Shell.
mkdir src/main/resources \ && touch src/main/resources/hibernate.cfg.xml \ && touch src/main/resources/hibernate.properties
Por lo tanto, le informaremos a Hibernate sobre las clases de entidades anotadas que asignaremos a la base de datos completando hibernate.cfg.xml
. Crearemos las clases de entidad más adelante.
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>
Hibernate también necesita saber cómo conectarse a la instancia de Cloud Spanner y qué dialecto usar. Por lo tanto, le indicaremos que use SpannerDialect
para la sintaxis de SQL, el controlador de JDBC de Spanner y la string de conexión de JDBC con las coordenadas de la base de datos. Esto se incluye en el archivo 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
Recuerda reemplazar {PROJECT_ID}
por el ID del proyecto, que puedes obtener mediante la ejecución del siguiente comando:
gcloud config get-value project
Como no tenemos un esquema de base de datos existente, agregamos la propiedad hibernate.hbm2ddl.auto=update
para permitir que Hibernate cree las dos tablas en Cloud Spanner cuando ejecutamos la app por primera vez.
Por lo general, también debes asegurarte de que las credenciales de autenticación estén configuradas mediante un archivo JSON de cuenta de servicio en la variable de entorno GOOGLE_APPLICATION_CREDENTIALS
o las credenciales predeterminadas de la aplicación configuradas con el comando gcloud auth application-default login
. Sin embargo, dado que lo estamos ejecutando en Cloud Shell, las credenciales predeterminadas del proyecto ya están configuradas.
7. Crea clases de entidades anotadas
Ahora estamos listos para escribir algo de código.
Definiremos dos objetos antiguos sin formato de Java (POJO) que se asignarán a tablas en Cloud Spanner: Singer
y Album
. Album
tendrá una relación @ManyToOne
con Singer
. También podríamos haber asignado Singer
a listas de sus Album
con una anotación @OneToMany
, pero, en este ejemplo, no queremos cargar todos los álbumes cada vez que necesitamos recuperar un cantante de la base de datos.
Agrega las clases de entidad Singer
y Album
.
Crea los archivos de la clase.
touch src/main/java/codelab/Singer.java \ && touch src/main/java/codelab/Album.java
Pega el contenido de los archivos.
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;
}
}
Ten en cuenta que, en este ejemplo, usaremos un UUID generado automáticamente para la clave primaria. Este es un tipo de ID preferido en Cloud Spanner porque evita los hotspots ya que el sistema divide los datos entre los servidores por rangos de claves. Una clave de número entero que aumenta de forma monótona también funcionaría, pero puede ser menos eficaz.
8. Guarda y consulta entidades
Con todo configurado y los objetos de entidad definidos, podemos comenzar a escribir en la base de datos y consultarla. Abriremos un Session
de Hibernate y lo usaremos para borrar primero todas las filas de la tabla en el método clearData()
, guardar algunas entidades en el método writeData()
y ejecutar algunas consultas con el lenguaje de consulta Hibernate (HQL) en el método readData()
.
Reemplaza el contenido de App.java
por lo siguiente:
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;
}
}
}
Ahora, compilemos y ejecutemos el código. Agregaremos la opción -Dexec.cleanupDaemonThreads=false
para suprimir las advertencias sobre la limpieza de los subprocesos del daemon que intentará hacer Maven.
mvn compile exec:java -Dexec.mainClass=codelab.App -Dexec.cleanupDaemonThreads=false
En el resultado, deberías ver algo como esto:
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
En este punto, si vas a la consola de Cloud Spanner y consultas los datos de las tablas Singer y Album en la base de datos, verás algo como lo siguiente:
9. Limpia
Borremos la instancia de Cloud Spanner que creamos al principio para asegurarnos de que no está usando los recursos innecesariamente.
gcloud spanner instances delete codelab-instance
10. Felicitaciones
Felicitaciones, compilaste correctamente una aplicación de Java que usa Hibernate para conservar datos en Cloud Spanner.
- Creaste una instancia y una base de datos de Cloud Spanner
- Configuraste la aplicación para que use Hibernate
- Creaste dos entidades: Artista y Álbum.
- Generaste automáticamente el esquema de base de datos de tu aplicación.
- Guardaste correctamente las entidades en Cloud Spanner y las consultaste
Ahora conoces los pasos clave necesarios para escribir una aplicación de Hibernate con Cloud Spanner.