Skip to content

Latest commit

 

History

History
145 lines (118 loc) · 7.5 KB

README.md

File metadata and controls

145 lines (118 loc) · 7.5 KB

PGAdapter and Hibernate

PGAdapter can be used in combination with Hibernate, but with some limitations. This sample shows to use Hibernate with PGAdapter.

Note: This sample uses Hibernate directly. There is also a sample for using Spring Data JPA and Spring Boot with PGAdapter here.

You can run the sample on the Cloud Spanner emulator with this command:

mvn exec:java

You can also run the sample on a real Cloud Spanner database with this command:

mvn exec:java \
  -Dexec.args=" \
    -p my-project \
    -i my-instance \
    -d my-database"

PGAdapter

PGAdapter is automatically started by the sample application as an in-process dependency. Depending on the command line arguments, one of the following will be started:

  1. No command line arguments: A Docker container with both PGAdapter and the Cloud Spanner emulator is started automatically. The sample creates a database on the emulator and uses that for the sample application.
  2. With command line arguments: Use -p <project> -i <instance> -d <database> to point to an existing Cloud Spanner PostgreSQL database that should be used for the sample. The database may be empty. The sample application will automatically create the tables that are needed for the sample.

Data Types

Cloud Spanner supports the following data types in combination with Hibernate.

PostgreSQL Type Hibernate or Java
boolean boolean
bigint / int8 long
varchar String
text String
float8 / double precision double
numeric BigDecimal
timestamptz / timestamp with time zone LocalDateTime
bytea byte[]
date LocalDate
jsonb Custom Data Type

Limitations

The following limitations are currently known:

Limitation Workaround
Schema updates Cloud Spanner does not support the full PostgreSQL DDL dialect. Automated schema updates using hibernate are therefore not supported. It is recommended to set the option hibernate.hbm2ddl.auto=none (or spring.jpa.hibernate.ddl-auto=none if you are using Spring).
Pessimistic Locking Cloud Spanner does not support LockMode.UPGRADE and LockMode.UPGRADE_NOWAIT lock modes.

Schema Updates

Schema updates are not supported as Cloud Spanner does not support the full PostgreSQL DDL dialect. For this sample, the schema must be created manually. See sample-schema.sql for the data model for this example.

It is recommended to use a higher-level schema management tool like Liquibase for gradual updates of your schema in production. This gives you more control over the changes that are applied, and allows you to store schema changes in a code repository. See Spring Data JPA with PGAdapter here for an example of how to integrate Liquibase with your application.

Generated Primary Keys - UUIDs

See https://cloud.google.com/spanner/docs/schema-design#primary-key-prevent-hotspots for more information on choosing a good primary key. Most entities in this sample use UUIDs that are generated by the client for primary keys.

public class User {
	// This uses auto generated UUID.
    @Id
    @Column(columnDefinition = "varchar(36)")
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;
}

Generated Primary Keys - Bit-reversed Sequence

Using a traditional auto-increment primary key with Cloud Spanner is not recommended, because a monotonically increasing or decreasing primary key value can create a write-hotspot. This will cause all writes to be sent to one server. See https://cloud.google.com/spanner/docs/schema-design#primary-key-prevent-hotspots for more background information.

Cloud Spanner therefore supports bit-reversed sequences. These internally work as traditional sequences, but the value that is returned is bit-reversed before being returned to the user. These sequences can be used to generate primary keys with JPA / Hibernate.

Note that Hibernate requires sequences to support pooling in order to support efficient batching of multiple inserts. Pooling normally requires the sequence to support an increment size larger than 1. Bit-reversed sequences can also support pooling, but require a custom ID generator to be used. Follow these steps to define an entity that uses a bit-reversed sequence for generating a primary key that also supports batching:

  1. Add the following dependency to your project:
<!-- Add Spanner Hibernate tools for access to the batch compatible bit-reversed sequence generator. -->
<dependency>
   <groupId>com.google.cloud</groupId>
   <artifactId>google-cloud-spanner-hibernate-tools</artifactId>
   <version>3.1.0</version>
</dependency>
  1. Add the following annotations to your entity:
  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ticketSaleId")
  @GenericGenerator(
      // This is the name of the generator, not the name of the sequence. This name must correspond
      // with the name given in the @GeneratedValue above.
      name = "ticketSaleId",
      // Use this custom strategy to ensure the use of a bit-reversed sequence that is compatible
      // with batching multiple inserts. See also
      // https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#batch.
      type = PooledBitReversedSequenceStyleGenerator.class,
      parameters = {
        // Use a separate sequence name for each entity.
        // See resources/db.changelog-v1.1.sql file for the sequence definition in the database.
        @Parameter(name = SequenceStyleGenerator.SEQUENCE_PARAM, value = "ticket_sale_seq"),
        // The increment_size is not actually set on the sequence that is created, but is used to
        // generate a SELECT query that fetches this number of identifiers at once.
        @Parameter(name = SequenceStyleGenerator.INCREMENT_PARAM, value = "50"),
        @Parameter(name = SequenceStyleGenerator.INITIAL_PARAM, value = "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.
        @Parameter(
            name = PooledBitReversedSequenceStyleGenerator.EXCLUDE_RANGE_PARAM,
            value = "[1,1000]"),
      })
  public Long id;

See TicketSale.java for a working example.