diff --git a/google-cloud-spanner-hibernate-dialect/src/main/java/com/google/cloud/spanner/hibernate/SpannerDialect.java b/google-cloud-spanner-hibernate-dialect/src/main/java/com/google/cloud/spanner/hibernate/SpannerDialect.java index 53f1437e..520b3b6d 100644 --- a/google-cloud-spanner-hibernate-dialect/src/main/java/com/google/cloud/spanner/hibernate/SpannerDialect.java +++ b/google-cloud-spanner-hibernate-dialect/src/main/java/com/google/cloud/spanner/hibernate/SpannerDialect.java @@ -19,6 +19,7 @@ package com.google.cloud.spanner.hibernate; import static org.hibernate.type.SqlTypes.DECIMAL; +import static org.hibernate.type.SqlTypes.JSON; import static org.hibernate.type.SqlTypes.NUMERIC; import com.google.cloud.spanner.hibernate.hints.ReplaceQueryPartsHint; @@ -65,6 +66,8 @@ import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.JsonAsStringJdbcType; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; +import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl; +import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry; import org.jboss.logging.Logger; /** Hibernate 6.x dialect for Cloud Spanner. */ @@ -86,7 +89,7 @@ public int executeInsert( private static class SpannerJsonJdbcType extends JsonAsStringJdbcType { private SpannerJsonJdbcType() { - super(SqlTypes.LONG32VARCHAR, null); + super(JSON, null); } @Override @@ -176,6 +179,9 @@ protected String columnType(int sqlTypeCode) { if (sqlTypeCode == DECIMAL || sqlTypeCode == NUMERIC) { return "numeric"; } + if (sqlTypeCode == JSON) { + return "json"; + } return super.columnType(sqlTypeCode); } @@ -186,6 +192,14 @@ protected void registerColumnTypes( JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration().getJdbcTypeRegistry(); jdbcTypeRegistry.addDescriptorIfAbsent(new SpannerJsonJdbcType()); + final DdlTypeRegistry ddlTypeRegistry = + typeContributions.getTypeConfiguration().getDdlTypeRegistry(); + ddlTypeRegistry.addDescriptor( + new DdlTypeImpl( + SqlTypes.JSON, + columnType(SqlTypes.JSON), + castType(SqlTypes.JSON), + this)); } @Override diff --git a/google-cloud-spanner-hibernate-samples/spring-data-jpa-full-sample/pom.xml b/google-cloud-spanner-hibernate-samples/spring-data-jpa-full-sample/pom.xml index 47a6ea51..567a0685 100644 --- a/google-cloud-spanner-hibernate-samples/spring-data-jpa-full-sample/pom.xml +++ b/google-cloud-spanner-hibernate-samples/spring-data-jpa-full-sample/pom.xml @@ -75,6 +75,11 @@ junit test + + org.testcontainers + testcontainers + test + diff --git a/google-cloud-spanner-hibernate-samples/spring-data-jpa-full-sample/src/main/java/com/google/cloud/spanner/sample/entities/Singer.java b/google-cloud-spanner-hibernate-samples/spring-data-jpa-full-sample/src/main/java/com/google/cloud/spanner/sample/entities/Singer.java index 25aaa0b2..cef1ffc3 100644 --- a/google-cloud-spanner-hibernate-samples/spring-data-jpa-full-sample/src/main/java/com/google/cloud/spanner/sample/entities/Singer.java +++ b/google-cloud-spanner-hibernate-samples/spring-data-jpa-full-sample/src/main/java/com/google/cloud/spanner/sample/entities/Singer.java @@ -21,12 +21,15 @@ import com.google.cloud.spanner.hibernate.types.SpannerStringArray; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.Index; import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; import java.util.List; import org.hibernate.annotations.Type; @Entity +@Table(indexes = {@Index(name = "idx_singer_active", columnList = "active")}) public class Singer extends AbstractNonInterleavedEntity { @Column(length = 100) diff --git a/google-cloud-spanner-hibernate-samples/spring-data-jpa-full-sample/src/main/java/com/google/cloud/spanner/sample/service/VenueService.java b/google-cloud-spanner-hibernate-samples/spring-data-jpa-full-sample/src/main/java/com/google/cloud/spanner/sample/service/VenueService.java index 479d8292..c1e3e957 100644 --- a/google-cloud-spanner-hibernate-samples/spring-data-jpa-full-sample/src/main/java/com/google/cloud/spanner/sample/service/VenueService.java +++ b/google-cloud-spanner-hibernate-samples/spring-data-jpa-full-sample/src/main/java/com/google/cloud/spanner/sample/service/VenueService.java @@ -66,11 +66,15 @@ public List generateRandomVenues(int count) { for (int i = 0; i < count; i++) { Venue venue = new Venue(); venue.setName(randomDataService.getRandomVenueName()); - VenueDescription description = new VenueDescription(); - description.setCapacity(random.nextInt(100_000)); - description.setType(randomDataService.getRandomVenueType()); - description.setLocation(randomDataService.getRandomVenueLocation()); - venue.setDescription(description); + if (random.nextBoolean()) { + VenueDescription description = new VenueDescription(); + description.setCapacity(random.nextInt(100_000)); + description.setType(randomDataService.getRandomVenueType()); + description.setLocation(randomDataService.getRandomVenueLocation()); + venue.setDescription(description); + } else { + venue.setDescription(null); + } venues.add(venue); } return repository.saveAll(venues); diff --git a/google-cloud-spanner-hibernate-samples/spring-data-jpa-full-sample/src/test/java/com/google/cloud/spanner/sample/SampleApplicationEmulatorTest.java b/google-cloud-spanner-hibernate-samples/spring-data-jpa-full-sample/src/test/java/com/google/cloud/spanner/sample/SampleApplicationEmulatorTest.java new file mode 100644 index 00000000..261ce7cb --- /dev/null +++ b/google-cloud-spanner-hibernate-samples/spring-data-jpa-full-sample/src/test/java/com/google/cloud/spanner/sample/SampleApplicationEmulatorTest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2019-2024 Google LLC + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package com.google.cloud.spanner.sample; + +import com.google.cloud.spanner.connection.SpannerPool; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.springframework.boot.SpringApplication; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.DockerImageName; + +/** Runs the sample application on the emulator. */ +@RunWith(JUnit4.class) +public class SampleApplicationEmulatorTest { + private static GenericContainer emulator; + + /** Starts the emulator in a test container. */ + @BeforeClass + public static void setup() { + emulator = + new GenericContainer<>( + DockerImageName.parse("gcr.io/cloud-spanner-emulator/emulator:latest")) + .withExposedPorts(9010) + .waitingFor(Wait.forListeningPort()); + emulator.start(); + } + + /** Stops the emulator. */ + @AfterClass + public static void cleanup() { + SpannerPool.closeSpannerPool(); + if (emulator != null) { + emulator.stop(); + } + } + + @Test + public void testRunApplication() { + System.setProperty("spanner.emulator", "true"); + System.setProperty("spanner.host", "//localhost:" + emulator.getMappedPort(9010)); + SpringApplication.run(SampleApplication.class).close(); + } + +}