diff --git a/.gitignore b/.gitignore
index 2cc0e3c45..d0ffb7224 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
target/
/target
+.DS_STORE
.idea/
*.iml
*.class
diff --git a/samples/java/hibernate/README.md b/samples/java/hibernate/README.md
new file mode 100644
index 000000000..1807768b2
--- /dev/null
+++ b/samples/java/hibernate/README.md
@@ -0,0 +1,82 @@
+# PGAdapter and Hibernate
+
+PGAdapter can be used in combination with Hibernate, but with a number of limitations. This sample
+shows the command line arguments and configuration that is needed in order to use Hibernate with
+PGAdapter.
+
+## Start PGAdapter
+You must start PGAdapter before you can run the sample. The following command shows how to start PGAdapter using the
+pre-built Docker image. See [Running PGAdapter](../../../README.md#usage) for more information on other options for how
+to run PGAdapter.
+
+```shell
+export GOOGLE_APPLICATION_CREDENTIALS=/path/to/credentials.json
+docker pull gcr.io/cloud-spanner-pg-adapter/pgadapter
+docker run \
+ -d -p 5432:5432 \
+ -v ${GOOGLE_APPLICATION_CREDENTIALS}:${GOOGLE_APPLICATION_CREDENTIALS}:ro \
+ -e GOOGLE_APPLICATION_CREDENTIALS \
+ -v /tmp:/tmp \
+ gcr.io/cloud-spanner-pg-adapter/pgadapter \
+ -p my-project -i my-instance \
+ -x
+```
+
+## Creating the Sample Data Model
+Run the following command in this directory. Replace the host, port and database name with the actual host, port and
+database name for your PGAdapter and database setup.
+
+```shell
+psql -h localhost -p 5432 -d my-database -f sample-schema.sql
+```
+
+You can also drop an existing data model using the `drop_data_model.sql` script:
+
+```shell
+psql -h localhost -p 5432 -d my-database -f drop-data-model.sql
+```
+
+## 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 |
+
+## 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). |
+| Generated primary keys | Cloud Spanner does not support `sequences`. Auto-increment primary key is not supported. Remove auto increment annotation for primary key columns. The recommended type of primary key is a client side generated `UUID` stored as a string. |
+
+
+### Schema Updates
+Schema updates are not supported as Cloud Spanner does not support the full PostgreSQL DDL dialect. It is recommended to
+create the schema manually. Note that PGAdapter does support `create table if not exists` / `drop table if exists`.
+See [sample-schema.sql](src/main/resources/sample-schema-sql) for the data model for this example.
+
+### Generated Primary Keys
+`Sequences` are not supported. Hence, auto increment primary key is not supported and should be replaced with primary key definitions that
+are manually assigned. See https://cloud.google.com/spanner/docs/schema-design#primary-key-prevent-hotspots
+for more information on choosing a good primary key. This sample uses UUIDs that are generated by the client for primary
+keys.
+
+```java
+public class User {
+ // This uses auto generated UUID.
+ @Id
+ @Column(columnDefinition = "varchar", nullable = false)
+ @GeneratedValue
+ private UUID id;
+}
+```
\ No newline at end of file
diff --git a/samples/java/hibernate/pom.xml b/samples/java/hibernate/pom.xml
new file mode 100644
index 000000000..2997e2fab
--- /dev/null
+++ b/samples/java/hibernate/pom.xml
@@ -0,0 +1,109 @@
+
+
+ 4.0.0
+
+ org.example
+ hibernate
+ 1.0-SNAPSHOT
+
+
+ 1.8
+ 1.8
+ 1.8
+ 5.2.13.Final
+ false
+ ${skipTests}
+ true
+ 8
+ true
+ UTF-8
+ UTF-8
+ 6.31.1
+ 2.5.1
+
+
+
+ org.hibernate
+ hibernate-entitymanager
+ ${hibernate.version}
+
+
+
+ org.hibernate
+ hibernate-core
+ ${hibernate.version}
+
+
+
+ org.postgresql
+ postgresql
+ 42.4.2
+
+
+ javax.xml.bind
+ jaxb-api
+ 2.3.1
+
+
+ com.sun.xml.bind
+ jaxb-impl
+ 2.3.4
+
+
+ org.javassist
+ javassist
+ 3.25.0-GA
+
+
+
+
+
+
+ org.codehaus.mojo
+ clirr-maven-plugin
+
+
+
+ none
+
+
+
+ ${clirr.skip}
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ 3
+ 3600
+
+ ${surefire.jacoco.args}
+ ${excludedTests}
+ sponge_log
+ ${skipUnits}
+
+
+ java.util.logging.config.file
+ src/test/resources/logging.properties
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+
+ true
+ true
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/java/hibernate/src/main/java/com/google/cloud/postgres/CurrentLocalDateTimeGenerator.java b/samples/java/hibernate/src/main/java/com/google/cloud/postgres/CurrentLocalDateTimeGenerator.java
new file mode 100644
index 000000000..24d8a7c47
--- /dev/null
+++ b/samples/java/hibernate/src/main/java/com/google/cloud/postgres/CurrentLocalDateTimeGenerator.java
@@ -0,0 +1,27 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.cloud.postgres;
+
+import java.time.LocalDateTime;
+import org.hibernate.Session;
+import org.hibernate.tuple.ValueGenerator;
+
+public class CurrentLocalDateTimeGenerator implements ValueGenerator {
+
+ @Override
+ public LocalDateTime generateValue(Session session, Object entity) {
+ return LocalDateTime.now();
+ }
+}
diff --git a/samples/java/hibernate/src/main/java/com/google/cloud/postgres/HibernateSampleTest.java b/samples/java/hibernate/src/main/java/com/google/cloud/postgres/HibernateSampleTest.java
new file mode 100644
index 000000000..5dbead90d
--- /dev/null
+++ b/samples/java/hibernate/src/main/java/com/google/cloud/postgres/HibernateSampleTest.java
@@ -0,0 +1,310 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.cloud.postgres;
+
+import com.google.cloud.postgres.models.Albums;
+import com.google.cloud.postgres.models.Concerts;
+import com.google.cloud.postgres.models.HibernateConfiguration;
+import com.google.cloud.postgres.models.Singers;
+import com.google.cloud.postgres.models.Tracks;
+import com.google.cloud.postgres.models.Venues;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaDelete;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.CriteriaUpdate;
+import javax.persistence.criteria.Root;
+import org.hibernate.Session;
+import org.hibernate.Transaction;
+import org.hibernate.query.Query;
+
+public class HibernateSampleTest {
+
+ private static final Logger logger = Logger.getLogger(HibernateSampleTest.class.getName());
+
+ private HibernateConfiguration hibernateConfiguration;
+
+ private List singersId = new ArrayList<>();
+ private List albumsId = new ArrayList<>();
+ private List tracksId = new ArrayList<>();
+ private List venuesId = new ArrayList<>();
+ private List concertsId = new ArrayList<>();
+
+ public HibernateSampleTest(HibernateConfiguration hibernateConfiguration) {
+ this.hibernateConfiguration = hibernateConfiguration;
+ }
+
+ public void testJPACriteriaDelete() {
+ try (Session s = hibernateConfiguration.openSession()) {
+ final Singers singers = Utils.createSingers();
+ final Albums albums = Utils.createAlbums(singers);
+ s.getTransaction().begin();
+ s.saveOrUpdate(singers);
+ s.saveOrUpdate(albums);
+ final Tracks tracks1 = Utils.createTracks(albums.getId());
+ s.saveOrUpdate(tracks1);
+ final Tracks tracks2 = Utils.createTracks(albums.getId());
+ s.saveOrUpdate(tracks2);
+ s.getTransaction().commit();
+ s.clear();
+
+ CriteriaBuilder cb = s.getCriteriaBuilder();
+ CriteriaDelete albumsCriteriaDelete = cb.createCriteriaDelete(Albums.class);
+ Root albumsRoot = albumsCriteriaDelete.from(Albums.class);
+ albumsCriteriaDelete.where(cb.equal(albumsRoot.get("id"), albums.getId()));
+ Transaction transaction = s.beginTransaction();
+ s.createQuery(albumsCriteriaDelete).executeUpdate();
+ transaction.commit();
+ }
+ }
+
+ public void testJPACriteria() {
+ try (Session s = hibernateConfiguration.openSession()) {
+ CriteriaBuilder cb = s.getCriteriaBuilder();
+ CriteriaQuery singersCriteriaQuery = cb.createQuery(Singers.class);
+ Root singersRoot = singersCriteriaQuery.from(Singers.class);
+ singersCriteriaQuery
+ .select(singersRoot)
+ .where(
+ cb.and(
+ cb.equal(singersRoot.get("firstName"), "David"),
+ cb.equal(singersRoot.get("lastName"), "Lee")));
+
+ Query singersQuery = s.createQuery(singersCriteriaQuery);
+ List singers = singersQuery.getResultList();
+
+ System.out.println("Listed singer: " + singers.size());
+
+ CriteriaUpdate albumsCriteriaUpdate = cb.createCriteriaUpdate(Albums.class);
+ Root albumsRoot = albumsCriteriaUpdate.from(Albums.class);
+ albumsCriteriaUpdate.set("marketingBudget", new BigDecimal("5.0"));
+ albumsCriteriaUpdate.where(cb.equal(albumsRoot.get("id"), UUID.fromString(albumsId.get(0))));
+ Transaction transaction = s.beginTransaction();
+ s.createQuery(albumsCriteriaUpdate).executeUpdate();
+ transaction.commit();
+ }
+ }
+
+ public void testHqlUpdate() {
+ try (Session s = hibernateConfiguration.openSession()) {
+ Singers singers = Utils.createSingers();
+ singers.setLastName("Cord");
+ s.getTransaction().begin();
+ s.saveOrUpdate(singers);
+ s.getTransaction().commit();
+
+ s.getTransaction().begin();
+ Query query =
+ s.createQuery(
+ "update Singers set active=:active "
+ + "where lastName=:lastName and firstName=:firstName");
+ query.setParameter("active", false);
+ query.setParameter("lastName", "Cord");
+ query.setParameter("firstName", "David");
+ query.executeUpdate();
+ s.getTransaction().commit();
+
+ System.out.println("Updated singer: " + s.get(Singers.class, singers.getId()));
+ }
+ }
+
+ public void testHqlList() {
+ try (Session s = hibernateConfiguration.openSession()) {
+ Query query = s.createQuery("from Singers");
+ List list = query.list();
+ System.out.println("Singers list size: " + list.size());
+
+ query = s.createQuery("from Singers order by fullName");
+ query.setFirstResult(2);
+ list = query.list();
+ System.out.println("Singers list size with first result: " + list.size());
+
+ /* Current Limit is not supported. */
+ // query = s.createQuery("from Singers");
+ // query.setMaxResults(2);
+ // list = query.list();
+ // System.out.println("Singers list size with first result: " + list.size());
+
+ query = s.createQuery("select sum(sampleRate) from Tracks");
+ list = query.list();
+ System.out.println("Sample rate sum: " + list);
+ }
+ }
+
+ public void testOneToManyData() {
+ try (Session s = hibernateConfiguration.openSession()) {
+ Venues venues = s.get(Venues.class, UUID.fromString(venuesId.get(0)));
+ if (venues == null) {
+ logger.log(Level.SEVERE, "Previously Added Venues Not Found.");
+ }
+ if (venues.getConcerts().size() <= 1) {
+ logger.log(Level.SEVERE, "Previously Added Concerts Not Found.");
+ }
+
+ System.out.println("Venues fetched: " + venues);
+ }
+ }
+
+ public void testDeletingData() {
+ try (Session s = hibernateConfiguration.openSession()) {
+ Singers singers = Utils.createSingers();
+ s.getTransaction().begin();
+ s.saveOrUpdate(singers);
+ s.getTransaction().commit();
+
+ singers = s.get(Singers.class, singers.getId());
+ if (singers == null) {
+ logger.log(Level.SEVERE, "Added singers not found.");
+ }
+
+ s.getTransaction().begin();
+ s.delete(singers);
+ s.getTransaction().commit();
+
+ singers = s.get(Singers.class, singers.getId());
+ if (singers != null) {
+ logger.log(Level.SEVERE, "Deleted singers found.");
+ }
+ }
+ }
+
+ public void testAddingData() {
+ try (Session s = hibernateConfiguration.openSession()) {
+ final Singers singers = Utils.createSingers();
+ final Albums albums = Utils.createAlbums(singers);
+ final Venues venues = Utils.createVenue();
+ final Concerts concerts1 = Utils.createConcerts(singers, venues);
+ final Concerts concerts2 = Utils.createConcerts(singers, venues);
+ final Concerts concerts3 = Utils.createConcerts(singers, venues);
+ s.getTransaction().begin();
+ s.saveOrUpdate(singers);
+ s.saveOrUpdate(albums);
+ s.saveOrUpdate(venues);
+ s.persist(concerts1);
+ s.persist(concerts2);
+ final Tracks tracks1 = Utils.createTracks(albums.getId());
+ s.saveOrUpdate(tracks1);
+ final Tracks tracks2 = Utils.createTracks(albums.getId());
+ s.saveOrUpdate(tracks2);
+ s.persist(concerts3);
+ s.getTransaction().commit();
+
+ singersId.add(singers.getId().toString());
+ albumsId.add(albums.getId().toString());
+ venuesId.add(venues.getId().toString());
+ concertsId.add(concerts1.getId().toString());
+ concertsId.add(concerts2.getId().toString());
+ concertsId.add(concerts3.getId().toString());
+ tracksId.add(tracks1.getId().getTrackNumber());
+ tracksId.add(tracks2.getId().getTrackNumber());
+
+ System.out.println("Created Singer: " + singers.getId());
+ System.out.println("Created Albums: " + albums.getId());
+ System.out.println("Created Venues: " + venues.getId());
+ System.out.println("Created Concerts: " + concerts1.getId());
+ System.out.println("Created Concerts: " + concerts2.getId());
+ System.out.println("Created Concerts: " + concerts3.getId());
+ System.out.println("Created Tracks: " + tracks1.getId());
+ System.out.println("Created Tracks: " + tracks2.getId());
+ }
+ }
+
+ public void testSessionRollback() {
+ try (Session s = hibernateConfiguration.openSession()) {
+ final Singers singers = Utils.createSingers();
+ s.getTransaction().begin();
+ s.saveOrUpdate(singers);
+ s.getTransaction().rollback();
+
+ System.out.println("Singers that was saved: " + singers.getId());
+ Singers singersFromDb = s.get(Singers.class, singers.getId());
+ if (singersFromDb == null) {
+ System.out.println("Singers not found as expected.");
+ } else {
+ logger.log(Level.SEVERE, "Singers found. Lookout for the error.");
+ }
+ }
+ }
+
+ public void testForeignKey() {
+ try (Session s = hibernateConfiguration.openSession()) {
+ final Singers singers = Utils.createSingers();
+ final Albums albums = Utils.createAlbums(singers);
+
+ s.getTransaction().begin();
+ s.saveOrUpdate(singers);
+ s.persist(albums);
+ s.getTransaction().commit();
+
+ singersId.add(singers.getId().toString());
+ albumsId.add(albums.getId().toString());
+ System.out.println("Created Singer: " + singers.getId());
+ System.out.println("Created Albums: " + albums.getId());
+ }
+ }
+
+ public void executeTest() {
+ System.out.println("Testing Foreign Key");
+ testForeignKey();
+ System.out.println("Foreign Key Test Completed");
+
+ System.out.println("Testing Session Rollback");
+ testSessionRollback();
+ System.out.println("Session Rollback Test Completed");
+
+ System.out.println("Testing Data Insert");
+ testAddingData();
+ System.out.println("Data Insert Test Completed");
+
+ System.out.println("Testing Data Delete");
+ testDeletingData();
+ System.out.println("Data Delete Test Completed");
+
+ System.out.println("Testing One to Many Fetch");
+ testOneToManyData();
+ System.out.println("One To Many Fetch Test Completed");
+
+ System.out.println("Testing HQL List");
+ testHqlList();
+ System.out.println("HQL List Test Completed");
+
+ System.out.println("Testing HQL Update");
+ testHqlUpdate();
+ System.out.println("HQL Update Test Completed");
+
+ System.out.println("Testing JPA List and Update");
+ testJPACriteria();
+ System.out.println("JPA List and Update Test Completed");
+
+ System.out.println("Testing JPA Delete");
+ testJPACriteriaDelete();
+ System.out.println("JPA Delete Test Completed");
+
+ hibernateConfiguration.closeSessionFactory();
+ }
+
+ public static void main(String[] args) {
+ System.out.println("Starting Hibrnate Test");
+ HibernateSampleTest hibernateSampleTest =
+ new HibernateSampleTest(HibernateConfiguration.createHibernateConfiguration());
+ hibernateSampleTest.executeTest();
+ System.out.println("Hibernate Test Ended Successfully");
+ }
+}
diff --git a/samples/java/hibernate/src/main/java/com/google/cloud/postgres/Utils.java b/samples/java/hibernate/src/main/java/com/google/cloud/postgres/Utils.java
new file mode 100644
index 000000000..f243a3cee
--- /dev/null
+++ b/samples/java/hibernate/src/main/java/com/google/cloud/postgres/Utils.java
@@ -0,0 +1,83 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.cloud.postgres;
+
+import com.google.cloud.postgres.models.Albums;
+import com.google.cloud.postgres.models.Concerts;
+import com.google.cloud.postgres.models.Singers;
+import com.google.cloud.postgres.models.Tracks;
+import com.google.cloud.postgres.models.TracksId;
+import com.google.cloud.postgres.models.Venues;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.Random;
+import java.util.UUID;
+
+public class Utils {
+
+ private static Random random = new Random();
+
+ public static Singers createSingers() {
+ final Singers singers = new Singers();
+ singers.setActive(true);
+ singers.setFirstName("David");
+ singers.setLastName("Lee");
+ singers.setCreatedAt(LocalDateTime.now());
+ return singers;
+ }
+
+ public static Albums createAlbums(Singers singers) {
+ final Albums albums = new Albums();
+ albums.setTitle("Perfect");
+ albums.setMarketingBudget(new BigDecimal("1.00"));
+ albums.setReleaseDate(LocalDate.now());
+ albums.setCreatedAt(LocalDateTime.now());
+ albums.setSingers(singers);
+ return albums;
+ }
+
+ public static Concerts createConcerts(Singers singers, Venues venues) {
+ final Concerts concerts = new Concerts();
+ concerts.setCreatedAt(LocalDateTime.now());
+ concerts.setEndTime(LocalDateTime.now().plusHours(1));
+ concerts.setStartTime(LocalDateTime.now());
+ concerts.setName("Sunburn");
+ concerts.setSingers(singers);
+ concerts.setVenues(venues);
+ return concerts;
+ }
+
+ public static Tracks createTracks(UUID albumId) {
+ final Tracks tracks = new Tracks();
+ tracks.setCreatedAt(LocalDateTime.now());
+ tracks.setTitle("Perfect");
+ tracks.setSampleRate(random.nextInt());
+ TracksId tracksId = new TracksId();
+ tracksId.setTrackNumber(random.nextInt());
+ tracksId.setId(albumId);
+ tracks.setId(tracksId);
+ return tracks;
+ }
+
+ public static Venues createVenue() {
+ final Venues venues = new Venues();
+ venues.setCreatedAt(LocalDateTime.now());
+ venues.setName("Hall");
+ venues.setDescription("Theater");
+
+ return venues;
+ }
+}
diff --git a/samples/java/hibernate/src/main/java/com/google/cloud/postgres/models/Albums.java b/samples/java/hibernate/src/main/java/com/google/cloud/postgres/models/Albums.java
new file mode 100644
index 000000000..33ca7eb01
--- /dev/null
+++ b/samples/java/hibernate/src/main/java/com/google/cloud/postgres/models/Albums.java
@@ -0,0 +1,139 @@
+package com.google.cloud.postgres.models;
+
+import com.google.cloud.postgres.CurrentLocalDateTimeGenerator;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.UUID;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.Lob;
+import javax.persistence.ManyToOne;
+import org.hibernate.annotations.GenerationTime;
+import org.hibernate.annotations.GeneratorType;
+import org.hibernate.annotations.Type;
+
+@Entity
+public class Albums {
+
+ @Id
+ @Column(columnDefinition = "varchar", nullable = false)
+ @GeneratedValue
+ private UUID id;
+
+ private String title;
+
+ @Column(name = "marketing_budget")
+ private BigDecimal marketingBudget;
+
+ @Column(name = "release_date", columnDefinition = "date")
+ private LocalDate releaseDate;
+
+ @Lob
+ @Type(type = "org.hibernate.type.BinaryType")
+ @Column(name = "cover_picture")
+ private byte[] coverPicture;
+
+ @ManyToOne
+ @JoinColumn(name = "singer_id")
+ private Singers singers;
+
+ @Column(name = "created_at", columnDefinition = "timestamptz")
+ private LocalDateTime createdAt;
+
+ @GeneratorType(type = CurrentLocalDateTimeGenerator.class, when = GenerationTime.ALWAYS)
+ @Column(name = "updated_at", columnDefinition = "timestamptz")
+ private LocalDateTime updatedAt;
+
+ public UUID getId() {
+ return id;
+ }
+
+ public void setId(UUID id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public BigDecimal getMarketingBudget() {
+ return marketingBudget;
+ }
+
+ public void setMarketingBudget(BigDecimal marketingBudget) {
+ this.marketingBudget = marketingBudget;
+ }
+
+ public byte[] getCoverPicture() {
+ return coverPicture;
+ }
+
+ public void setCoverPicture(byte[] coverPicture) {
+ this.coverPicture = coverPicture;
+ }
+
+ public Singers getSingers() {
+ return singers;
+ }
+
+ public void setSingers(Singers singers) {
+ this.singers = singers;
+ }
+
+ public LocalDate getReleaseDate() {
+ return releaseDate;
+ }
+
+ public void setReleaseDate(LocalDate releaseDate) {
+ this.releaseDate = releaseDate;
+ }
+
+ public LocalDateTime getCreatedAt() {
+ return createdAt;
+ }
+
+ public void setCreatedAt(LocalDateTime createdAt) {
+ this.createdAt = createdAt;
+ }
+
+ public LocalDateTime getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public void setUpdatedAt(LocalDateTime updatedAt) {
+ this.updatedAt = updatedAt;
+ }
+
+ @Override
+ public String toString() {
+ return "Albums{"
+ + "id='"
+ + id
+ + '\''
+ + ", title='"
+ + title
+ + '\''
+ + ", marketingBudget="
+ + marketingBudget
+ + ", releaseDate="
+ + releaseDate
+ + ", coverPicture="
+ + Arrays.toString(coverPicture)
+ + ", singers="
+ + singers
+ + ", createdAt="
+ + createdAt
+ + ", updatedAt="
+ + updatedAt
+ + '}';
+ }
+}
diff --git a/samples/java/hibernate/src/main/java/com/google/cloud/postgres/models/Concerts.java b/samples/java/hibernate/src/main/java/com/google/cloud/postgres/models/Concerts.java
new file mode 100644
index 000000000..10bef124a
--- /dev/null
+++ b/samples/java/hibernate/src/main/java/com/google/cloud/postgres/models/Concerts.java
@@ -0,0 +1,145 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.cloud.postgres.models;
+
+import com.google.cloud.postgres.CurrentLocalDateTimeGenerator;
+import java.time.LocalDateTime;
+import java.util.UUID;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import org.hibernate.annotations.GenerationTime;
+import org.hibernate.annotations.GeneratorType;
+
+@Entity
+public class Concerts {
+
+ @Id
+ @Column(columnDefinition = "varchar", nullable = false)
+ @GeneratedValue
+ private UUID id;
+
+ @Column(name = "name", nullable = false)
+ private String name;
+
+ @Column(name = "start_time", columnDefinition = "timestamptz")
+ private LocalDateTime startTime;
+
+ @Column(name = "end_time", columnDefinition = "timestamptz")
+ private LocalDateTime endTime;
+
+ @ManyToOne
+ @JoinColumn(name = "singer_id")
+ private Singers singers;
+
+ @ManyToOne
+ @JoinColumn(name = "venue_id")
+ private Venues venues;
+
+ @Column(name = "created_at", columnDefinition = "timestamptz")
+ private LocalDateTime createdAt;
+
+ @GeneratorType(type = CurrentLocalDateTimeGenerator.class, when = GenerationTime.ALWAYS)
+ @Column(name = "updated_at", columnDefinition = "timestamptz")
+ private LocalDateTime updatedAt;
+
+ 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;
+ }
+
+ public LocalDateTime getStartTime() {
+ return startTime;
+ }
+
+ public void setStartTime(LocalDateTime startTime) {
+ this.startTime = startTime;
+ }
+
+ public LocalDateTime getEndTime() {
+ return endTime;
+ }
+
+ public void setEndTime(LocalDateTime endTime) {
+ this.endTime = endTime;
+ }
+
+ public Singers getSingers() {
+ return singers;
+ }
+
+ public void setSingers(Singers singers) {
+ this.singers = singers;
+ }
+
+ public Venues getVenues() {
+ return venues;
+ }
+
+ public void setVenues(Venues venues) {
+ this.venues = venues;
+ }
+
+ public LocalDateTime getCreatedAt() {
+ return createdAt;
+ }
+
+ public void setCreatedAt(LocalDateTime createdAt) {
+ this.createdAt = createdAt;
+ }
+
+ public LocalDateTime getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public void setUpdatedAt(LocalDateTime updatedAt) {
+ this.updatedAt = updatedAt;
+ }
+
+ @Override
+ public String toString() {
+ return "Concerts{"
+ + "id="
+ + id
+ + ", name='"
+ + name
+ + '\''
+ + ", startTime="
+ + startTime
+ + ", endTime="
+ + endTime
+ + ", singers="
+ + singers
+ + ", createdAt="
+ + createdAt
+ + ", updatedAt="
+ + updatedAt
+ + '}';
+ }
+}
diff --git a/samples/java/hibernate/src/main/java/com/google/cloud/postgres/models/HibernateConfiguration.java b/samples/java/hibernate/src/main/java/com/google/cloud/postgres/models/HibernateConfiguration.java
new file mode 100644
index 000000000..324998295
--- /dev/null
+++ b/samples/java/hibernate/src/main/java/com/google/cloud/postgres/models/HibernateConfiguration.java
@@ -0,0 +1,54 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.cloud.postgres.models;
+
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
+import org.hibernate.cfg.Configuration;
+
+public class HibernateConfiguration {
+
+ private Configuration configuration;
+ private SessionFactory sessionFactory;
+
+ private HibernateConfiguration(Configuration configuration, SessionFactory sessionFactory) {
+ this.configuration = configuration;
+ this.sessionFactory = sessionFactory;
+ }
+
+ public Session openSession() {
+ return sessionFactory.openSession();
+ }
+
+ public void closeSessionFactory() {
+ sessionFactory.close();
+ }
+
+ public static HibernateConfiguration createHibernateConfiguration() {
+ final Configuration configuration = new Configuration();
+ configuration.addAnnotatedClass(Albums.class);
+ configuration.addAnnotatedClass(Concerts.class);
+ configuration.addAnnotatedClass(Singers.class);
+ configuration.addAnnotatedClass(Venues.class);
+ configuration.addAnnotatedClass(Tracks.class);
+ configuration.addAnnotatedClass(TracksId.class);
+
+ final SessionFactory sessionFactory =
+ configuration.buildSessionFactory(new StandardServiceRegistryBuilder().build());
+
+ return new HibernateConfiguration(configuration, sessionFactory);
+ }
+}
diff --git a/samples/java/hibernate/src/main/java/com/google/cloud/postgres/models/Singers.java b/samples/java/hibernate/src/main/java/com/google/cloud/postgres/models/Singers.java
new file mode 100644
index 000000000..7158c9bec
--- /dev/null
+++ b/samples/java/hibernate/src/main/java/com/google/cloud/postgres/models/Singers.java
@@ -0,0 +1,130 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.cloud.postgres.models;
+
+import com.google.cloud.postgres.CurrentLocalDateTimeGenerator;
+import java.time.LocalDateTime;
+import java.util.UUID;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import org.hibernate.annotations.Generated;
+import org.hibernate.annotations.GenerationTime;
+import org.hibernate.annotations.GeneratorType;
+
+@Entity
+public class Singers {
+
+ @Id
+ @Column(columnDefinition = "varchar", nullable = false)
+ @GeneratedValue
+ private UUID id;
+
+ @Column(name = "first_name")
+ private String firstName;
+
+ @Column(name = "last_name", nullable = false)
+ private String lastName;
+
+ @Column(name = "full_name", insertable = false)
+ @Generated(GenerationTime.ALWAYS)
+ private String fullName;
+
+ private boolean active;
+
+ @Column(name = "created_at", columnDefinition = "timestamptz")
+ private LocalDateTime createdAt;
+
+ @GeneratorType(type = CurrentLocalDateTimeGenerator.class, when = GenerationTime.ALWAYS)
+ @Column(name = "updated_at", columnDefinition = "timestamptz")
+ private LocalDateTime updatedAt;
+
+ public UUID getId() {
+ return id;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public String getFullName() {
+ return fullName;
+ }
+
+ public boolean isActive() {
+ return active;
+ }
+
+ public void setId(UUID id) {
+ this.id = id;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public void setActive(boolean active) {
+ this.active = active;
+ }
+
+ public LocalDateTime getCreatedAt() {
+ return createdAt;
+ }
+
+ public void setCreatedAt(LocalDateTime createdAt) {
+ this.createdAt = createdAt;
+ }
+
+ public LocalDateTime getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public void setUpdatedAt(LocalDateTime updatedAt) {
+ this.updatedAt = updatedAt;
+ }
+
+ @Override
+ public String toString() {
+ return "Singers{"
+ + "id='"
+ + id
+ + '\''
+ + ", firstName='"
+ + firstName
+ + '\''
+ + ", lastName='"
+ + lastName
+ + '\''
+ + ", fullName='"
+ + fullName
+ + '\''
+ + ", active="
+ + active
+ + ", createdAt="
+ + createdAt
+ + ", updatedAt="
+ + updatedAt
+ + '}';
+ }
+}
diff --git a/samples/java/hibernate/src/main/java/com/google/cloud/postgres/models/Tracks.java b/samples/java/hibernate/src/main/java/com/google/cloud/postgres/models/Tracks.java
new file mode 100644
index 000000000..f11886ea6
--- /dev/null
+++ b/samples/java/hibernate/src/main/java/com/google/cloud/postgres/models/Tracks.java
@@ -0,0 +1,100 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.cloud.postgres.models;
+
+import com.google.cloud.postgres.CurrentLocalDateTimeGenerator;
+import java.time.LocalDateTime;
+import javax.persistence.Column;
+import javax.persistence.EmbeddedId;
+import javax.persistence.Entity;
+import org.hibernate.annotations.GenerationTime;
+import org.hibernate.annotations.GeneratorType;
+
+@Entity
+public class Tracks {
+
+ // For composite primary keys, @EmbeddedId will have to be used.
+ @EmbeddedId private TracksId id;
+
+ @Column(name = "title", nullable = false)
+ private String title;
+
+ @Column(name = "sample_rate")
+ private double sampleRate;
+
+ @Column(name = "created_at", columnDefinition = "timestamptz")
+ private LocalDateTime createdAt;
+
+ @GeneratorType(type = CurrentLocalDateTimeGenerator.class, when = GenerationTime.ALWAYS)
+ @Column(name = "updated_at", columnDefinition = "timestamptz")
+ private LocalDateTime updatedAt;
+
+ public TracksId getId() {
+ return id;
+ }
+
+ public void setId(TracksId id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public double getSampleRate() {
+ return sampleRate;
+ }
+
+ public void setSampleRate(double sampleRate) {
+ this.sampleRate = sampleRate;
+ }
+
+ public LocalDateTime getCreatedAt() {
+ return createdAt;
+ }
+
+ public void setCreatedAt(LocalDateTime createdAt) {
+ this.createdAt = createdAt;
+ }
+
+ public LocalDateTime getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public void setUpdatedAt(LocalDateTime updatedAt) {
+ this.updatedAt = updatedAt;
+ }
+
+ @Override
+ public String toString() {
+ return "Tracks{"
+ + "id="
+ + id
+ + ", title='"
+ + title
+ + '\''
+ + ", sampleRate="
+ + sampleRate
+ + ", createdAt="
+ + createdAt
+ + ", updatedAt="
+ + updatedAt
+ + '}';
+ }
+}
diff --git a/samples/java/hibernate/src/main/java/com/google/cloud/postgres/models/TracksId.java b/samples/java/hibernate/src/main/java/com/google/cloud/postgres/models/TracksId.java
new file mode 100644
index 000000000..87c079d58
--- /dev/null
+++ b/samples/java/hibernate/src/main/java/com/google/cloud/postgres/models/TracksId.java
@@ -0,0 +1,61 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.cloud.postgres.models;
+
+import java.io.Serializable;
+import java.util.UUID;
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+
+/**
+ * @Embeddable is to be used for composite primary key.
+ */
+@Embeddable
+public class TracksId implements Serializable {
+
+ @Column(columnDefinition = "varchar", nullable = false)
+ private UUID id;
+
+ @Column(name = "track_number", nullable = false)
+ private long trackNumber;
+
+ public TracksId() {}
+
+ public TracksId(UUID id, long trackNumber) {
+ this.id = id;
+ this.trackNumber = trackNumber;
+ }
+
+ public UUID getId() {
+ return id;
+ }
+
+ public void setId(UUID id) {
+ this.id = id;
+ }
+
+ public long getTrackNumber() {
+ return trackNumber;
+ }
+
+ public void setTrackNumber(long trackNumber) {
+ this.trackNumber = trackNumber;
+ }
+
+ @Override
+ public String toString() {
+ return "TracksId{" + "id=" + id + ", trackNumber=" + trackNumber + '}';
+ }
+}
diff --git a/samples/java/hibernate/src/main/java/com/google/cloud/postgres/models/Venues.java b/samples/java/hibernate/src/main/java/com/google/cloud/postgres/models/Venues.java
new file mode 100644
index 000000000..22b16a61e
--- /dev/null
+++ b/samples/java/hibernate/src/main/java/com/google/cloud/postgres/models/Venues.java
@@ -0,0 +1,123 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.cloud.postgres.models;
+
+import com.google.cloud.postgres.CurrentLocalDateTimeGenerator;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.UUID;
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToMany;
+import org.hibernate.annotations.GenerationTime;
+import org.hibernate.annotations.GeneratorType;
+
+@Entity
+public class Venues {
+
+ @Id
+ @Column(columnDefinition = "varchar", nullable = false)
+ @GeneratedValue
+ private UUID id;
+
+ @Column(name = "name", nullable = false)
+ private String name;
+
+ @Column(name = "description", nullable = false)
+ private String description;
+
+ @Column(name = "created_at", columnDefinition = "timestamptz")
+ private LocalDateTime createdAt;
+
+ @GeneratorType(type = CurrentLocalDateTimeGenerator.class, when = GenerationTime.ALWAYS)
+ @Column(name = "updated_at", columnDefinition = "timestamptz")
+ private LocalDateTime updatedAt;
+
+ @OneToMany(cascade = CascadeType.ALL)
+ @JoinColumn(name = "venue_id")
+ private List concerts;
+
+ 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;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public LocalDateTime getCreatedAt() {
+ return createdAt;
+ }
+
+ public void setCreatedAt(LocalDateTime createdAt) {
+ this.createdAt = createdAt;
+ }
+
+ public LocalDateTime getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public void setUpdatedAt(LocalDateTime updatedAt) {
+ this.updatedAt = updatedAt;
+ }
+
+ public List getConcerts() {
+ return concerts;
+ }
+
+ public void setConcerts(List concerts) {
+ this.concerts = concerts;
+ }
+
+ @Override
+ public String toString() {
+ return "Venues{"
+ + "id="
+ + id
+ + ", name='"
+ + name
+ + '\''
+ + ", description='"
+ + description
+ + '\''
+ + ", createdAt="
+ + createdAt
+ + ", updatedAt="
+ + updatedAt
+ + ", concerts="
+ + concerts
+ + '}';
+ }
+}
diff --git a/samples/java/hibernate/src/main/resources/drop-data-model.sql b/samples/java/hibernate/src/main/resources/drop-data-model.sql
new file mode 100644
index 000000000..977cebd9b
--- /dev/null
+++ b/samples/java/hibernate/src/main/resources/drop-data-model.sql
@@ -0,0 +1,10 @@
+-- Executing the schema drop in a batch will improve execution speed.
+start batch ddl;
+
+drop table if exists concerts;
+drop table if exists venues;
+drop table if exists tracks;
+drop table if exists albums;
+drop table if exists singers;
+
+run batch;
\ No newline at end of file
diff --git a/samples/java/hibernate/src/main/resources/hibernate.properties b/samples/java/hibernate/src/main/resources/hibernate.properties
new file mode 100644
index 000000000..ac4ae4383
--- /dev/null
+++ b/samples/java/hibernate/src/main/resources/hibernate.properties
@@ -0,0 +1,11 @@
+hibernate.dialect org.hibernate.dialect.PostgreSQLDialect
+hibernate.connection.driver_class org.postgresql.Driver
+hibernate.connection.url jdbc:postgresql://localhost:5432/test-database
+hibernate.connection.username pratick
+
+hibernate.connection.pool_size 5
+
+hibernate.show_sql true
+hibernate.format_sql true
+
+hibernate.hbm2ddl.auto validate
diff --git a/samples/java/hibernate/src/main/resources/log4f.properties b/samples/java/hibernate/src/main/resources/log4f.properties
new file mode 100644
index 000000000..a4e96acc5
--- /dev/null
+++ b/samples/java/hibernate/src/main/resources/log4f.properties
@@ -0,0 +1,14 @@
+# Direct log messages to stdout
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.Target=System.out
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
+
+# Root logger option
+log4j.rootLogger=INFO, stdout
+
+# Log everything. Good for troubleshooting
+log4j.logger.org.hibernate=INFO
+
+# Log all JDBC parameters
+log4j.logger.org.hibernate.type=ALL
diff --git a/samples/java/hibernate/src/main/resources/sample-schema-sql b/samples/java/hibernate/src/main/resources/sample-schema-sql
new file mode 100644
index 000000000..1ae32e684
--- /dev/null
+++ b/samples/java/hibernate/src/main/resources/sample-schema-sql
@@ -0,0 +1,58 @@
+-- Executing the schema creation in a batch will improve execution speed.
+start batch ddl;
+
+create table if not exists singers (
+ id varchar not null primary key,
+ first_name varchar,
+ last_name varchar not null,
+ full_name varchar generated always as (coalesce(concat(first_name, ' '::varchar, last_name), last_name)) stored,
+ active boolean,
+ created_at timestamptz,
+ updated_at timestamptz
+);
+
+create table if not exists albums (
+ id varchar not null primary key,
+ title varchar not null,
+ marketing_budget numeric,
+ release_date date,
+ cover_picture bytea,
+ singer_id varchar not null,
+ created_at timestamptz,
+ updated_at timestamptz,
+ constraint fk_albums_singers foreign key (singer_id) references singers (id)
+);
+
+create table if not exists tracks (
+ id varchar not null,
+ track_number bigint not null,
+ title varchar not null,
+ sample_rate float8 not null,
+ created_at timestamptz,
+ updated_at timestamptz,
+ primary key (id, track_number)
+) interleave in parent albums on delete cascade;
+
+create table if not exists venues (
+ id varchar not null primary key,
+ name varchar not null,
+ description varchar not null,
+ created_at timestamptz,
+ updated_at timestamptz
+);
+
+create table if not exists concerts (
+ id varchar not null primary key,
+ venue_id varchar not null,
+ singer_id varchar not null,
+ name varchar not null,
+ start_time timestamptz not null,
+ end_time timestamptz not null,
+ created_at timestamptz,
+ updated_at timestamptz,
+ constraint fk_concerts_venues foreign key (venue_id) references venues (id),
+ constraint fk_concerts_singers foreign key (singer_id) references singers (id),
+ constraint chk_end_time_after_start_time check (end_time > start_time)
+);
+
+run batch;
\ No newline at end of file
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/hibernate/ITHibernateTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/hibernate/ITHibernateTest.java
new file mode 100644
index 000000000..dbdc03a7a
--- /dev/null
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/hibernate/ITHibernateTest.java
@@ -0,0 +1,151 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.cloud.spanner.pgadapter.hibernate;
+
+import com.google.cloud.spanner.Database;
+import com.google.cloud.spanner.pgadapter.PgAdapterTestEnv;
+import com.google.common.collect.ImmutableList;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Scanner;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ITHibernateTest {
+ private static final Logger LOGGER = Logger.getLogger(ITHibernateTest.class.getName());
+ private static final String HIBERNATE_SAMPLE_DIRECTORY = "samples/java/hibernate";
+ private static final String HIBERNATE_PROPERTIES_FILE =
+ HIBERNATE_SAMPLE_DIRECTORY + "/src/main/resources/hibernate.properties";
+ private static final String HIBERNATE_DB_CHANGELOG_DDL_FILE =
+ HIBERNATE_SAMPLE_DIRECTORY + "/src/main/resources/sample-schema-sql";
+ private static final String HIBERNATE_DEFAULT_URL =
+ "jdbc:postgresql://localhost:5432/test-database";
+ private static final PgAdapterTestEnv testEnv = new PgAdapterTestEnv();
+ private static Database database;
+ private static String originalHibernateProperties;
+
+ @BeforeClass
+ public static void setup()
+ throws ClassNotFoundException, IOException, SQLException, InterruptedException {
+ // Make sure the PG JDBC driver is loaded.
+ Class.forName("org.postgresql.Driver");
+
+ testEnv.setUp();
+ database = testEnv.createDatabase(ImmutableList.of());
+ testEnv.startPGAdapterServer(ImmutableList.of());
+ // Create databasechangelog and databasechangeloglock tables.
+ StringBuilder builder = new StringBuilder();
+ try (Scanner scanner = new Scanner(new FileReader(HIBERNATE_DB_CHANGELOG_DDL_FILE))) {
+ while (scanner.hasNextLine()) {
+ builder.append(scanner.nextLine()).append("\n");
+ }
+ }
+ // Note: We know that all semicolons in this file are outside of literals etc.
+ String[] ddl = builder.toString().split(";");
+ String url =
+ String.format(
+ "jdbc:postgresql://localhost:%d/%s",
+ testEnv.getPGAdapterPort(), database.getId().getDatabase());
+ try (Connection connection = DriverManager.getConnection(url)) {
+ try (Statement statement = connection.createStatement()) {
+ for (String sql : ddl) {
+ LOGGER.info("Executing " + sql);
+ statement.execute(sql);
+ }
+ }
+ }
+
+ // Write hibernate.properties
+ StringBuilder original = new StringBuilder();
+ try (Scanner scanner = new Scanner(new FileReader(HIBERNATE_PROPERTIES_FILE))) {
+ while (scanner.hasNextLine()) {
+ original.append(scanner.nextLine()).append("\n");
+ }
+ }
+ originalHibernateProperties = original.toString();
+ String updatesHibernateProperties = original.toString().replace(HIBERNATE_DEFAULT_URL, url);
+ try (FileWriter writer = new FileWriter(HIBERNATE_PROPERTIES_FILE)) {
+ LOGGER.info("Using Hibernate properties:\n" + updatesHibernateProperties);
+ writer.write(updatesHibernateProperties);
+ }
+ buildHibernateSample();
+ }
+
+ @AfterClass
+ public static void teardown() throws IOException {
+ try (FileWriter writer = new FileWriter(HIBERNATE_PROPERTIES_FILE)) {
+ writer.write(originalHibernateProperties);
+ }
+ testEnv.stopPGAdapterServer();
+ testEnv.cleanUp();
+ }
+
+ @Test
+ public void testHibernateUpdate() throws IOException, InterruptedException, SQLException {
+ System.out.println("Running hibernate test");
+ ImmutableList hibernateCommand =
+ ImmutableList.builder()
+ .add(
+ "mvn",
+ "exec:java",
+ "-Dexec.mainClass=com.google.cloud.postgres.HibernateSampleTest")
+ .build();
+ runCommand(hibernateCommand);
+ System.out.println("Hibernate Test Ended");
+ }
+
+ static void buildHibernateSample() throws IOException, InterruptedException {
+ System.out.println("Building Hibernate Sample.");
+ ImmutableList hibernateCommand =
+ ImmutableList.builder().add("mvn", "clean", "package").build();
+ runCommand(hibernateCommand);
+ System.out.println("Hibernate Sample build complete.");
+ }
+
+ static void runCommand(ImmutableList commands) throws IOException, InterruptedException {
+ System.out.println(
+ "Executing commands: " + commands + ". Sample Directory: " + HIBERNATE_SAMPLE_DIRECTORY);
+ ProcessBuilder builder = new ProcessBuilder();
+ builder.command(commands);
+ builder.directory(new File(HIBERNATE_SAMPLE_DIRECTORY));
+ Process process = builder.start();
+
+ String errors;
+ String output;
+ try (BufferedReader reader =
+ new BufferedReader(new InputStreamReader(process.getInputStream()));
+ BufferedReader errorReader =
+ new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
+ System.out.println("Printing hibernate loadings");
+ output = reader.lines().collect(Collectors.joining("\n"));
+ errors = errorReader.lines().collect(Collectors.joining("\n"));
+ System.out.println("Hibernate Command. Output: " + output + ". Error: " + errors);
+ }
+ }
+}