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); + } + } +}