Google Cloud Platform released an Hibernate dialect for the Cloud Spanner database! You can now use Google Cloud’s fully-managed multi-regional, horizontally scalable, relational database with Hibernate ORM and any Hibernate-backed JPA applications.

What is Cloud Spanner?

Cloud Spanner is horizontally scalable - so that if your business data grows or transaction load increases, you can easily add a new Cloud Spanner node to accommodate the growth. Cloud Spanner will automatically shard your data and increase capacity without any manual operations or system downtime.

Most horizontally scalable databases are NoSQL and eventually consistent. Cloud Spanner is strongly consistent and relational. That means Cloud Spanner is good for transaction data whether it’s banking, e-commerce, or any other workload that is best represented with strongly consistent relational data.

For data locality, you can use a regional Cloud Spanner instance, where data gets automatically distributed and replicated across multiple availability zones within the region for high availability. Or, you can easily create a multi-regional instance that has the highest 99.999% availability and having data distributed across multiple regions.

Cloud Spanner can achieve such feats by leveraging Google’s internal infrastructure, network, and use of GPS synchronized atomic clocks! With the new Cloud Spanner Dialect for Hibernate, you can leverage Cloud Spanner easily with Hibernate ORM.

Cloud Spanner with Hibernate ORM and Quarkus

This blog will show how to use Cloud Spanner, with Hibernate ORM, and Quarkus (in JVM mode). The complete example is on GitHub.

If you don’t already have a Google Cloud Platform account, you can sign up for free and receive $300 credit.

Create a Cloud Spanner Instance

Currently, there is no official Cloud Spanner emulator for local development. In order to try out Cloud Spanner, you’ll need to create a server instance. To complete this tutorial, you’ll need:

Make sure Cloud Spanner API is enabled:

gcloud services enable spanner.googleapis.com

Create a new single-node Cloud Spanner instance in the US Central1 region. (You can find other regions to use):

gcloud spanner instances create sample --nodes=1 --config=regional-us-central1 \
  --description="Sample Database"

If you already have a database schema, you can import it via the command line too. In this example, we’ll let Hibernate create the schema instead.

A Cloud Spanner instance can host multiple databases. Create a database to store the tables that we’ll generate through Hibernate. Create a database called people:

gcloud spanner databases create people --instance=sample

Create a new Quarkus application

Use Maven to create a new Quarkus application:

mvn io.quarkus:quarkus-maven-plugin:1.0.1.Final:create \
    -DprojectGroupId=com.example \
    -DprojectArtifactId=spanner-example \
    -DclassName="com.example.PersonResource"
    -Dpath="/person"

Build the application:

cd spanner-example
./mvnw package

Add Hibernate ORM extension to the project:

./mvnw quarkus:add-extension -Dextension=quarkus-hibernate-orm

This will add the quarkus-hibernate-orm dependency in the pom.xml:

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-hibernate-orm</artifactId>
</dependency>

Add both the Cloud Spanner JDBC driver and the Cloud Spanner Dialect dependencies to the pom.xml:

<!-- The Hibernate dialect for Spanner dependency -->
<dependency>
  <groupId>com.google.cloud</groupId>
  <artifactId>google-cloud-spanner-hibernate-dialect</artifactId>
  <version>1.0.0</version>
</dependency>

<!-- The Spanner JDBC driver dependency -->
<dependency>
  <groupId>com.google.cloud</groupId>
  <artifactId>google-cloud-spanner-jdbc</artifactId>
  <version>1.11.0</version>
</dependency>

Configure Quarkus to use the JDBC driver, and also the Cloud Spanner Hibernate Dialect:

src/main/resources/application.properties
quarkus.datasource.driver=com.google.cloud.spanner.jdbc.JdbcDriver
quarkus.datasource.url=jdbc:cloudspanner:/projects/${PROJECT_ID}/instances/sample/databases/people
quarkus.hibernate-orm.dialect=com.google.cloud.spanner.hibernate.SpannerDialect
quarkus.hibernate-orm.database.generation=drop-and-create

Replace the ${PROJECT_ID} with your Google Cloud project ID.

When running the application locally (not running on Google Cloud), to have permissions to connect to your Spanner database you need to set the GOOGLE_APPLICATION_CREDENTIALS environment variable.

You can now create new JPA entities. Typically an entity uses an auto-increment numeric ID as a surrogate key. However, this is not the best choice when using Cloud Spanner. Cloud Spanner automatically organizes and distributes data across nodes based on the primary key in alphanumeric order, so you should not use any monotonically increasing/decreasing key (most commonly the auto-increment IDs). Such a key would create hotspots in Cloud Spanner, and introduce unnecessary bottlenecks during both data writes and ID generation, which ultimately will slow down performance and limit scalability. Cloud Spanner recommends the use of UUIDv4 as a primary key, so we’ll use that for the entity too.

Let’s create a Person entity:

src/main/java/com/example/Person.java
package com.example;

import java.util.UUID;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Person {
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Type(type = "uuid-char")
  @Id
  private UUID id;

  private String name;

  public UUID getId() {
    return id;
  }

  public void setId(UUID id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
}

Once the entity is created, then the rest is easy! Simply use a JPA Entity Manager perform CRUD operations with Cloud Spanner! Create a JAX-RS REST Resource to use JPA Entity Manager to save a new entry:

src/main/java/com/example/PersonResource.java
package com.example;

import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.transaction.Transactional;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/person")
public class PersonResource {
    @Inject EntityManager entityManager;

    @POST
    @Transactional
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public Person create(Person person) {
        entityManager.persist(person);
        return person;
    }

}

Notice that the create method is annotated with @Transactional. This is because there is no auto-commit and writes must participate in a transaction. Cloud Spanner is fully transactional, and the Cloud Spanner JDBC driver exposes JTA semantics. Transaction annotations will work as JPA users would expect.

The create method also expects to receive a Person object as a JSON payload. You need to add RestEasy JSONB extension so Quarkus can convert JSON payloads to POJO:

./mvnw quarkus:add-extension -Dextension=quarkus-resteasy-jsonb

Run this application in development mode:

./mvnw quarkus:dev

Once the application is up and running (at Quarkus' supersonic atomic speed!), use curl to post a JSON payload to the application:

curl -XPOST -H"Content-type: application/json" -d'{"name": "Ray"}' \
  http://localhost:8080/person

To validate that the data was written into Cloud Spanner, you can navigate to the Google Cloud Platform console, and browse to the Cloud Spanner database instance, and view the rows in table.

Using Hibernate ORM Panache

Hibernate ORM Panache is a really easy way to create DAO (Data Access Object) to encapsulate more complex queries and operations beyond what EntityManager provides. You can use Panache with Cloud Spanner since it simply constructs the same Hibernate queries behind the scenes. So, whatever Cloud Spanner Dialect supports for Hibernate ORM, you can also use it with Panache.

First, add Hibernate ORM Panache extension to your Quarkus application:

./mvnw quarkus:add-extension -Dextension=quarkus-hibernate-orm-panache

Then, create the DAO repository, named PersonRepository:

src/main/java/com/example/PersonRepository.java
package com.example;

import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
import java.util.UUID;
import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class PersonRepository implements PanacheRepositoryBase<Person, UUID> {
  public Person findByName(String name){
       return find("name", name).firstResult();
   }
}

The PanacheRepositoryBase expects 2 generic type parameters. The first parameter is the entity type, which is Person. The second parameter is the primary key type, which we are using UUID.

Now you can inject the repository for CRUD operations, plus you can implement additional operations (such as findByName) in the repository class too.

In the PersonResource class, add a new REST API to find by name:

src/main/java/com/example/PersonResource.java
package com.example;

import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;

import javax.inject.Inject;
import javax.transaction.Transactional;
import javax.persistence.EntityManager;

import java.util.UUID;

@Path("/person")
public class PersonResource {
    @Inject EntityManager entityManager;

    @Inject PersonRepository personRepository;

    @GET
    @Path("/")
    @Produces(MediaType.APPLICATION_JSON)
    public Person getByName(@QueryParam("name") String name) {
      return personRepository.findByName(name);
    }

    @GET
    @Path("/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Person getById(@PathParam("id") String id) {
      return personRepository.findById(UUID.fromString(id));
    }

    @POST
    @Transactional
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public Person create(Person person) {
      Person p = new Person();
      p.setName(person.getName());
      entityManager.persist(p);
      return p;
    }
}

Bonus - Deploy to Google App Engine!

You can use Cloud Spanner from anywhere - whether your application is on-premise, on Google Cloud Platform, or on another cloud. If you do want to run this in Google Cloud Platform, you can try it in the new App Engine Standard for Java 11 runtime. This App Engine runtime allows you to run any JAR-based services without any proprietary API but in a fully-managed serverless platform. You can deploy a Quarkus application (in JVM mode) easily with few commands.

First, build the Quarkus JAR:

./mvnw package

And simply deploy it!

gcloud app deploy target/spanner-example-1.0-SNAPSHOT-runner.jar

Clean Up

Cloud Spanner is billed based on provisioned nodes and data stored. If you followed along to try the above instructions in your own Google Cloud Platform account, please clean up and remove the Cloud Spanner instance when you are done to avoid unintended charges!

gcloud spanner instances delete sample

But don’t worry, creating a new instance is super fast, so if you need to test this again in the future, just create another instance.

If you deployed to App Engine, follow the App Engine Disabling an Application documentation to fully stop your App Engine application.

What’s Next?

You can find the official documentation on Google Cloud Platform documentation site. The Cloud Spanner Hibernate Dialect engineering team would love to get your feedback. If you have any comments, ideas, or discovered a bug, please reach out using GitHub issues.


Back to top