0
votes

I am attempting to migrate an existing spring boot application from using Postgres to GCP Spanner.

I am using the following Cloud Spanner JDBC driver and Hibernate dialect:

<dependency>
    <groupId>com.google.cloud</groupId>
    <artifactId>google-cloud-spanner-hibernate-dialect</artifactId>
    <version>0.1.0</version>
</dependency>
<dependency>
    <groupId>com.google.cloud</groupId>
    <artifactId>google-cloud-spanner-jdbc</artifactId>
    <version>1.7.0</version>
</dependency>

Also I have configured the following properties:

spring.datasource.url=jdbc:cloudspanner:/projects/YOUR_PROJECT_ID/instances/demo/databases/demo
spring.datasource.driver-class-name=com.google.cloud.spanner.jdbc.JdbcDriver
spring.jpa.database-platform=com.google.cloud.spanner.hibernate.SpannerDialect

However when the running the application it hangs attempting to obtain pool connection:

o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 2729 ms
com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
com.zaxxer.hikari.pool.PoolBase          : HikariPool-1 - Driver does not support get/set network timeout for connections. (Network timeout is not supported)

Does HikariCP support GCP Spanner ?

1

1 Answers

1
votes

It should work out of the box and I can't see anything directly wrong in the settings that you have provided. So my guess is that there is something additional here that is causing the problem (dependency, other setting etc.). You are using somewhat older versions of the Hibernate dialect and the JDBC driver, but that should not be the problem.

One possible problem could be that your system is not set up with default Google Cloud credentials. I noticed that your JDBC URL does not contain any credentials, which means that it will fallback to the default of the environment. I would expect an error if it could not find any, but it could be a possible cause of problems.

I've created a very simple test project with Spring boot and tried it out, and it does work. Could you try with this simple test setup as well and try to add from there to figure out where it goes wrong? Or otherwise give some more details on any additional dependencies that you may be including in your project?

My setup that works:

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.2.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.google.cloud</groupId>
  <artifactId>spanner-example-runner</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>spanner-example-runner</name>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-spanner-jdbc</artifactId>
      <version>1.15.0</version>
    </dependency>
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-spanner-hibernate-dialect</artifactId>
      <version>1.1.0</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>

Entity:

package com.google.cloud.example;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "SINGERS")
public class Singer {

  @Id private Long singerId;

  private String firstName;

  private String lastName;

  public Singer() {}

  public Singer(long singerId, String firstName, String lastName) {
    this.singerId = singerId;
    this.firstName = firstName;
    this.lastName = lastName;
  }

  public Long getSingerId() {
    return singerId;
  }

  public void setSingerId(Long singerId) {
    this.singerId = singerId;
  }

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }
}

Repository:

package com.google.cloud.example;

import java.util.List;
import org.springframework.data.repository.CrudRepository;

public interface SingerRepository extends CrudRepository<Singer, Long> {

  List<Singer> findByLastName(String lastName);

  Singer findById(long id);
}

Spring Boot Application:

package com.google.cloud.example;

import org.hibernate.boot.model.naming.ImplicitNamingStrategy;
import org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl;
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class AccessingDataJpaApplication {
  private static final Logger log = LoggerFactory.getLogger(AccessingDataJpaApplication.class);

  public static void main(String[] args) {
    SpringApplication.run(AccessingDataJpaApplication.class, args);
  }

  @Bean
  public CommandLineRunner demo(SingerRepository repository) {
    return (args) -> {
      // save a few singers
      repository.save(new Singer(10, "Jack", "Bauer"));
      repository.save(new Singer(20, "Chloe", "O'Brian"));
      repository.save(new Singer(30, "Kim", "Bauer"));
      repository.save(new Singer(40, "David", "Palmer"));
      repository.save(new Singer(50, "Michelle", "Dessler"));

      // fetch all singers
      log.info("Customers found with findAll():");
      log.info("-------------------------------");
      for (Singer customer : repository.findAll()) {
        log.info(customer.toString());
      }
      log.info("");

      // fetch an individual singer by ID
      Singer customer = repository.findById(1L);
      log.info("Customer found with findById(1L):");
      log.info("--------------------------------");
      log.info(customer.toString());
      log.info("");

      // fetch singers by last name
      log.info("Customer found with findByLastName('Bauer'):");
      log.info("--------------------------------------------");
      repository.findByLastName("Bauer").forEach(bauer -> {
        log.info(bauer.toString());
      });
      log.info("");
      // Cleanup
      repository.deleteById(10L);
      repository.deleteById(20L);
      repository.deleteById(30L);
      repository.deleteById(40L);
      repository.deleteById(50L);
    };
  }

  @Bean
  public PhysicalNamingStrategy physical() {
      return new PhysicalNamingStrategyStandardImpl();
  }

  @Bean
  public ImplicitNamingStrategy implicit() {
      return new ImplicitNamingStrategyLegacyJpaImpl();
  }
}

application.properties:

spring.datasource.url=jdbc:cloudspanner:/projects/my-project-id/instances/some-instance/databases/some-db?credentials=/path/to/credentials.json
spring.datasource.driver-class-name=com.google.cloud.spanner.jdbc.JdbcDriver
spring.jpa.database-platform=com.google.cloud.spanner.hibernate.SpannerDialect