# google-cloud-spanner-hibernate
**Repository Path**: mirrors_GoogleCloudPlatform/google-cloud-spanner-hibernate
## Basic Information
- **Project Name**: google-cloud-spanner-hibernate
- **Description**: The official Google Cloud Spanner Dialect for Hibernate ORM
- **Primary Language**: Unknown
- **License**: LGPL-2.1
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2020-08-08
- **Last Updated**: 2026-02-28
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
:toc:
:toclevels: 4
= Google Cloud Spanner Dialect for Hibernate ORM
This is a dialect compatible with https://hibernate.org/orm/releases/7.1/[Hibernate 7.1] for the https://cloud.google.com/spanner/[Google Cloud Spanner] database service.
The `SpannerDialect` produces SQL, DML, and DDL statements for most common entity types and relationships using standard Hibernate and Java Persistence annotations.
Version 3.x of this library supports Hibernate 6.6
Version 1.x and 2.x of this library supports Hibernate 5.4.
Please see the following sections for important details about dialect differences due to the unique features and limitations of Cloud Spanner.
== Quick Set-Up
See the link:google-cloud-spanner-hibernate-samples/spring-data-jpa-full-sample/[Spring Data JPA Sample Application]
for a working sample application.
First, add the Maven dependencies for the Cloud Spanner Hibernate Dialect and the Cloud Spanner JDBC driver.
Maven coordinates for the dialect:
// {x-version-update-start:google-cloud-spanner-hibernate-dialect:released}
[source,xml]
----
com.google.cloudgoogle-cloud-spanner-hibernate-dialect3.9.1
----
// {x-version-update-start:google-cloud-spanner-hibernate-dialect:released}
Maven coordinates for the official https://cloud.google.com/spanner/docs/open-source-jdbc[open source Cloud Spanner JDBC Driver].
[source,xml]
----
com.google.cloudgoogle-cloud-spanner-jdbc2.29.1
----
NOTE: Hibernate ORM with Cloud Spanner is officially supported only with the https://cloud.google.com/spanner/docs/open-source-jdbc[open source Cloud Spanner JDBC Driver].
If you're using a `SNAPSHOT` version of the dialect, please add the Sonatype Snapshots repository to your `pom.xml`:
[source,xml]
----
snapshots-repohttps://oss.sonatype.org/content/repositories/snapshotsfalsetrue
----
Configuring the `SpannerDialect` and a Cloud Spanner Driver class is typical of all Hibernate dialects in the `hibernate.properties` file:
----
hibernate.dialect=com.google.cloud.spanner.hibernate.SpannerDialect
hibernate.connection.driver_class=com.google.cloud.spanner.jdbc.JdbcDriver
hibernate.connection.url=jdbc:cloudspanner:/projects/{INSERT_PROJECT_ID}/instances/{INSERT_INSTANCE_ID}/databases/{INSERT_DATABASE_ID}
----
The https://cloud.google.com/docs/authentication/getting-started[service account JSON credentials] file location should be in the `GOOGLE_APPLICATION_CREDENTIALS` environment variable.
The driver will use default credentials set in the Google Cloud SDK `gcloud` application otherwise.
You are now ready to begin using Hibernate with Cloud Spanner.
== Examples
To see a full working sample application for using the dialect, please see our
https://github.com/GoogleCloudPlatform/google-cloud-spanner-hibernate/blob/-/google-cloud-spanner-hibernate-samples/spring-data-jpa-full-sample[Spring Data JPA sample application].
== Tracing and Latency Debugging
The Spanner JDBC driver supports tracing with OpenTelemetry. These traces give you insights into the
transactions and queries that are executed by your application, and can be helpful when debugging
slow transactions or other latency problems.
https://github.com/GoogleCloudPlatform/google-cloud-spanner-hibernate/blob/-/documentation/latency-debugging-guide.md[See this Latency Debugging Guide]
for more information on how to use OpenTelemetry with Hibernate and the Spanner JDBC driver.
https://github.com/GoogleCloudPlatform/google-cloud-spanner-hibernate/blob/-/google-cloud-spanner-hibernate-samples/spring-data-jpa-full-sample[The Spring Data JPA sample application]
also contains a working sample for setting up OpenTelemetry tracing.
== User Guide
This guide contains a variety of best practices for using Hibernate with Spanner which can significantly improve the performance of your application.
=== Schema Creation and Entity Design
Hibernate generates statements based on your Hibernate entity design. Following these practices can result in better DDL and DML statement generation which can improve performance.
==== Use Generated UUIDs for ID Generation
The Universally Unique Identifier (UUID) is the preferred ID type in Cloud Spanner because it avoids hotspots as the system divides data among servers by key ranges.
UUIDs are strongly preferred over sequentially increasing IDs for this reason.
It is also recommended to use Hibernate's `@GeneratedValue` annotation to generate this UUID automatically; this can reduce the number of statements that Hibernate generates to perform an insert because it does not need to run extra `SELECT` statements to see if the record already exists in the table.
You can configure UUID generation like below:
[source, java]
----
@Entity
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Type(type="uuid-char")
public UUID id;
}
----
The `@Type(type="uuid-char")` annotation specifies that this UUID value will be stored in Cloud Spanner as a `STRING` column.
Leaving out this annotation causes a `BYTES` column to be used.
==== Use @GeneratedValue for ID Generation
__NOTE__: Read to the end of this section to see the recommended way to set up `@GeneratedValue`.
❌ Not recommended
`GenerationType.AUTO`, `GenerationType.TABLE`, `GenerationType.SEQUENCE`: Hibernate generates sequential IDs using a table-backed sequence. This can lead to hotspots in Spanner and is therefore not recommended.
`GenerationType.IDENTITY`
- See https://cloud.google.com/spanner/docs/primary-key-default-value#identity-columns[Identity columns].
It is not recommended to use the `IDENTITY` generation strategy, as it prevents Hibernate from using JDBC batching for inserts. See https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#batch-session-batch for more information.
`@GeneratedValue` + `@SequenceGenerator` with `allocationSize = 1`
This strategy uses a https://cloud.google.com/spanner/docs/primary-key-default-value#bit-reversed-sequence[bit-reversed sequence] with an allocation size of 1. This requires Hibernate to fetch a new
sequence value for each insert, causing each insert to cause two round-trips to Spanner.
[source, java]
----
@Entity
public class Employee {
// Generates a bit-reversed sequence with an increment_size=1.
// This is not recommended!
@Id
@GeneratedValue(generator = "employee_generator")
@SequenceGenerator(
name = "employee_generator",
sequenceName = "employee_seq",
allocationSize = 1)
public Long id;
}
----
✅ Recommended:
Bit-reversed sequences do not support an increment size larger than 1. This means that entities
that use this style of identifiers by default require a round-trip to the database for each entity
that is inserted. The @com.google.cloud.spanner.hibernate.annotations.PooledBitReversedSequenceGenerator
provided in this repository fixes this problem by using the configured poolSize to fetch multiple
identifier values from the sequence in one query, instead of setting an increment on the sequence
in the database.
The poolSize for this pooled generator can not exceed 200.
This is the **recommended configuration** for bit-reversed sequences:
[source, java]
----
@jakarta.persistence.Entity
public class Employee {
// Recommended
@jakarta.persistence.Id
@com.google.cloud.spanner.hibernate.annotations.PooledBitReversedSequenceGenerator(
// Use a separate sequence name for each entity.
sequenceName = "employee_seq",
// Number of IDs fetched per round-trip.
poolSize = 200,
// Initial counter used for bit-reversal.
startWithCounter = 50000,
// Add any range that should be excluded by the generator if your table already
// contains existing values that have been generated by other generators.
excludeRange = "[1,1000]"
)
public Long id;
}
----
==== Query Hints
Spanner supports multiple https://cloud.google.com/spanner/docs/reference/standard-sql/query-syntax#statement_hints[query hints]
that can be used to optimize specific queries. You can use these with this Hibernate dialect by
adding them either as a Hibernate query hint, or by adding them as specifically formatted comments.
These specifically formatted comments are processed by this Hibernate dialect, which then modifies
the generated query before it is sent to the JDBC driver.
Simple statement hints that only need to be prepended to a query can be added as if they were a
comment:
[source,java]
----
/** Get all singers that have a last name that starts with the given prefix. */
@Query("SELECT s FROM Singer s WHERE starts_with(s.lastName, :lastName)=true")
@QueryHints(
@QueryHint(
name = AvailableHints.HINT_COMMENT,
value = "@{STATEMENT_TAG=search_singers_by_last_name_starts_with}"))
Stream searchByLastNameStartsWith(@Param("lastName") String lastName);
----
More complex hints that need to be added somewhere in the middle of the statement, such as index
hints, can be added like this:
[source,java]
----
import com.google.cloud.spanner.hibernate.hints.Hints;
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery cr = cb.createQuery(Singer.class);
Root root = cr.from(Singer.class);
root.join("albums", JoinType.LEFT);
cr.select(root);
Query query = session.createQuery(cr)
.addQueryHint(
Hints.forceIndexFrom("Singer", "idx_singer_active", ReplaceMode.ALL).toQueryHint())
.addQueryHint(
Hints.forceIndexJoin("Album", "idx_album_title", ReplaceMode.ALL).toQueryHint());
List singers = query.getResultList().size();
----
You can also add more complex hints as comments to queries that are generated by JPA:
[source,java]
----
// The hint value that is used here is generated by calling the method:
// Hints.forceIndexFrom("singer", "idx_singer_active", ReplaceMode.ALL).toComment()
// manually and then copy-paste the value to the annotation.
@QueryHints(@QueryHint(name = AvailableHints.HINT_COMMENT, value = "{\n"
+ " \"spanner_replacements\": [\n"
+ " {\n"
+ " \"regex\": \" from singer \",\n"
+ " \"replacement\": \" from singer @{FORCE_INDEX=idx_singer_active} \",\n"
+ " \"replace_mode\": \"ALL\"\n"
+ " }\n"
+ " ]\n"
+ "}"))
List findByActive(boolean active);
----
This https://github.com/GoogleCloudPlatform/google-cloud-spanner-hibernate/blob/-/google-cloud-spanner-hibernate-samples/spring-data-jpa-full-sample/src/main/java/com/google/cloud/spanner/sample/repository/SingerRepository.java[working sample application]
shows how to use the above hints.
==== DML Batching
Spanner supports executing any combination of DML statements in a single batch. Batching can
significantly reduce the number of round-trips between the application and Spanner that is needed
to insert/update/delete a large number of entities. The Spanner Hibernate dialect supports the
standard JDBC batching that is used by Hibernate. This batching is limited to DML statements that
use the same SQL string.
In addition to the standard JDBC batching, you can also use the `auto_batch_dml` flag in the Spanner
JDBC driver to group more DML statements into a single batch. This https://github.com/GoogleCloudPlatform/google-cloud-spanner-hibernate/blob/-/google-cloud-spanner-hibernate-samples/spring-data-jpa-full-sample/src/main/java/com/google/cloud/spanner/sample/services/BatchService.java[working sample application]
shows how to use the `auto_batch_dml` flag.
==== Transaction Tags
Spanner supports adding
https://cloud.google.com/spanner/docs/introspection/troubleshooting-with-tags[transaction tags]
for troubleshooting queries and transactions. You can add transaction tags to your Hibernate or
Spring Data JPA application by adding the
`com.google.cloud.spanner.hibernate.TransactionTagInterceptor` to your Hibernate configuration, and
then adding the `com.google.cloud.spanner.hibernate.TransactionTag` annotation to the method that
starts the transaction.
Example for adding the `TransactionTagInterceptor`:
[source,java]
----
package com.google.cloud.spanner.sample;
import com.google.cloud.spanner.hibernate.TransactionTagInterceptor;
import com.google.common.collect.ImmutableSet;
import java.util.Map;
import org.hibernate.cfg.AvailableSettings;
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
import org.springframework.stereotype.Component;
/** This component adds the TransactionTagInterceptor to the Hibernate configuration. */
@Component
public class TaggingHibernatePropertiesCustomizer implements HibernatePropertiesCustomizer {
@Override
public void customize(Map hibernateProperties) {
hibernateProperties.put(AvailableSettings.INTERCEPTOR, new TransactionTagInterceptor(
ImmutableSet.of(MyApplication.class.getPackageName()), false));
}
}
----
Then add the `@TransactionTag` to the methods that should be tagged:
[source,java]
----
@Service
public class VenueService {
private final VenueRepository repository;
public VenueService(VenueRepository repository) {
this.repository = repository;
}
/**
* Deletes all Venue records in the database.
*/
@Transactional
@TransactionTag("delete_all_venues")
public void deleteAllVenues() {
repository.deleteAll();
}
}
----
This https://github.com/GoogleCloudPlatform/google-cloud-spanner-hibernate/blob/-/google-cloud-spanner-hibernate-samples/spring-data-jpa-full-sample[working sample application]
shows how to use transaction tags.
==== Statement Tags
NOTE: This feature requires that you use Spanner JDBC driver version 2.16.3 or higher.
Spanner supports adding
https://cloud.google.com/spanner/docs/introspection/troubleshooting-with-tags[statement tags]
for troubleshooting queries and transactions. You can add statement tags to your Hibernate or
Spring Data JPA application by adding a hint to a query.
[source,java]
----
/** Get all singers that have a last name that starts with the given prefix. */
@Query("SELECT s FROM Singer s WHERE starts_with(s.lastName, :lastName)=true")
@QueryHints(
@QueryHint(
name = AvailableHints.HINT_COMMENT,
value = "@{STATEMENT_TAG=search_singers_by_last_name_starts_with}"))
Stream searchByLastNameStartsWith(@Param("lastName") String lastName);
----
==== Custom Spanner Column Types
This project offers the following Hibernate type mappings for specific Spanner column types:
[options="header"]
|===
| Spanner Data Type | Hibernate Type
| `ARRAY` | `com.google.cloud.spanner.hibernate.types.SpannerBoolArray`
| `ARRAY` | `com.google.cloud.spanner.hibernate.types.SpannerBytesArray`
| `ARRAY` | `com.google.cloud.spanner.hibernate.types.SpannerDateArray`
| `ARRAY` | `com.google.cloud.spanner.hibernate.types.SpannerFloat32Array`
| `ARRAY` | `com.google.cloud.spanner.hibernate.types.SpannerFloat64Array`
| `ARRAY` | `com.google.cloud.spanner.hibernate.types.SpannerInt64Array`
| `ARRAY` | `com.google.cloud.spanner.hibernate.types.SpannerJsonArray`
| `ARRAY` | `com.google.cloud.spanner.hibernate.types.SpannerNumericArray`
| `ARRAY` | `com.google.cloud.spanner.hibernate.types.SpannerStringArray`
| `ARRAY` | `com.google.cloud.spanner.hibernate.types.SpannerTimestampArray`
|===
You can use these type mappings through the Hibernate `@Type` annotation:
[source, java]
----
@Entity
public class Singer {
// Specify the custom type with the @Type annotation.
@Type(SpannerStringArray.class)
private List nickNames;
...
}
----
A working example of this feature can be found in the https://github.com/GoogleCloudPlatform/google-cloud-spanner-hibernate/tree/master/google-cloud-spanner-hibernate-samples/basic-hibernate-sample[Hibernate Basic Sample].
==== JSON Data Type
JSON data type can be used by adding a `@JdbcTypeCode(SqlTypes.JSON)` annotation to a field. The
type of the field should be a `Serializable` POJO.
[source, java]
----
/**
* {@link VenueDescription} is a POJO that is used for the JSON field 'description' of the
* {@link Venue} entity. It is automatically serialized and deserialized when an instance of the
* entity is loaded or persisted.
*/
public static class VenueDescription implements Serializable {
private int capacity;
private String type;
private String location;
public int getCapacity() {
return capacity;
}
public void setCapacity(int capacity) {
this.capacity = capacity;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
}
/**
* This field maps to a JSON column in the database. The value is automatically
* serialized/deserialized to a {@link VenueDescription} instance.
*/
@JdbcTypeCode(SqlTypes.JSON)
private VenueDescription description;
----
See https://github.com/GoogleCloudPlatform/google-cloud-spanner-hibernate/blob/-/google-cloud-spanner-hibernate-samples/spring-data-jpa-full-sample[Spring Data JPA Full Sample]
for a full working sample. The JSON field is in the `Venue` entity.
==== Auto-generate Schema for Faster Development
It is often useful to generate the schema for your database, such as during the early stages of development.
The Spanner dialect supports Hibernate's `hibernate.hbm2ddl.auto` setting which controls the framework's schema generation behavior on start-up.
The following settings are available:
- `none`: Do nothing.
- `validate`: Validate the schema, makes no changes to the database.
- `update`: Create or update the schema.
- `create`: Create the schema, destroying previous data.
- `create-drop`: Drop the schema when the SessionFactory is closed explicitly, typically when the application is stopped.
Hibernate performs schema updates on each table and entity type on startup, which can take more than several minutes if there are many tables. To avoid schema updates keeping Hibernate from starting for several minutes, you can update schemas separately and use the `none` or `validate` settings.
==== Leverage Cloud Spanner Foreign Key Constraints
The dialect supports all of the standard entity relationships:
- `@OneToOne`
- `@OneToMany`
- `@ManyToOne`
- `@ManyToMany`
These can be used via `@JoinTable` or `@JoinColumn`.
The Cloud Spanner Hibernate dialect will generate the correct foreign key DDL statements during schema generation for entities using these annotations.
The dialect also supports unique column constraints applied through `@Column(unique = true)` or `@UniqueConstraint`.
In these cases, the dialect will create a unique index to enforce uniqueness on the specified columns.
=== Advanced Cloud Spanner Features (via. JDBC)
Cloud Spanner offers several features that traditional databases typically do not offer.
These include:
* Stale Reads
* Read-only transactions
* Partitioned DML
* Mutations API (faster insert/update/delete operations)
We provide a link:google-cloud-spanner-hibernate-samples/basic-spanner-features-sample[Cloud Spanner Features Sample Application] which demonstrates best practices for accessing these features through the Cloud Spanner JDBC driver.
Please consult the https://cloud.google.com/spanner/docs/use-oss-jdbc[Cloud Spanner JDBC driver documentation] for more information.
=== Performance Optimizations
There are some practices which can improve the execution time of Hibernate operations.
==== Isolation Level
Spanner supports two isolation levels:
- `SERIALIZABLE` (default)
- `REPEATABLE READ`
Using `REPEATABLE READ` instead of `SERIALIZABLE` means that Spanner does not need to take locks on
the data that your transaction reads, unless the query includes a `FOR UPDATE` clause. Using
`REPEATABLE READ` can therefore improve the performance of your application and reduce the number of
aborted transactions.
You can set the isolation level that Spanner should use in the JDBC connection URL like this:
[source]
----
jdbc:cloudspanner:/projects/my-project/instances/my-instance/databases/my-database;default_isolation_level=REPEATABLE_READ
----
You can also use the standard
https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html#setTransactionIsolation-int-[`Connection#setTransactionIsolation(level)`]
JDBC method.
If you are using Spring, you can use this annotation:
[source, java]
----
@org.springframework.transaction.annotation.Transactional(isolation = Isolation.REPEATABLE_READ)
public List generateRandomConcerts(int count) {
List singers = singerRepository.findAll(Pageable.ofSize(20)).toList();
List venues = venueRepository.findAll();
return generateRandomConcerts(singers, venues, count);
}
----
See https://github.com/GoogleCloudPlatform/google-cloud-spanner-hibernate/blob/master/google-cloud-spanner-hibernate-samples/spring-data-jpa-full-sample/src/main/java/com/google/cloud/spanner/sample/service/ConcertService.java[ConcertService.java]
for a full working example.
==== Be Clear About Inserts or Updates
Hibernate may generate additional `SELECT` statements if it is unclear whether you are attempting to insert a new record or update an existing record. The following practices can help with this:
* Let Hibernate generate the ID by leaving the entity's `id` null and annotate the field with `@GeneratedValue`. Hibernate will know that the record did not exist prior if it generates a new ID. See the <