GoogleCloudPlatform / spring-cloud-gcp

New home for Spring Cloud GCP development starting with version 2.0.
Apache License 2.0
410 stars 298 forks source link

Spring Cloud GCP for Spanner fails to use Instant in JSON column #2576

Open Alos opened 6 months ago

Alos commented 6 months ago

Spring Boot 3.2.2 Spring Cloud GCP 5.0.1 Spring Cloud 2023.0.0 Java 21

If you have a model that looks like this:

The parent animal:

@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@With
public class Animal {
  @PrimaryKey
  private String id;
  private String displayName;
  /** Metadata for the cat. */
  @Column(spannerType = TypeCode.JSON)
  private AnimalMetadata metadata;
}

The column:

@Data
@Accessors(chain = true)
public class AnimalMetadata {
  private String someMetadata;
  private Instant time;
}

A Cat that inherits from Animal:

@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@Table(name = Cat.TABLE_NAME)
public class Cat extends Animal{
  public static final String TABLE_NAME = "CATS";
  private String name;
  private String lastName;
  private int age;
  private String somethingNew;
}

Trying to create a Cat and saving it:

Cat cat = new Cat();
cat.setId(newIdentifier);
cat.setName("bob");
cat.setMetadata(new CatMetadata().setSomeMetadata("Some interesting metadata 2")
    .setTime(Instant.now()));
catRepository.save(cat);

Causes an issue:

java.lang.reflect.InaccessibleObjectException: Unable to make field private final long java.time.Instant.seconds accessible: module java.base does not "opens java.time" to unnamed module @7ae0a9ec
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354) ~[na:na]
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297) ~[na:na]
    at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:178) ~[na:na]
    at java.base/java.lang.reflect.Field.setAccessible(Field.java:172) ~[na:na]
    at com.google.gson.internal.reflect.ReflectionHelper.makeAccessible(ReflectionHelper.java:35) ~[gson-2.10.1.jar:na]
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:286) ~[gson-2.10.1.jar:na]
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:130) ~[gson-2.10.1.jar:na]
    at com.google.gson.Gson.getAdapter(Gson.java:556) ~[gson-2.10.1.jar:na]
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:160) ~[gson-2.10.1.jar:na]
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:294) ~[gson-2.10.1.jar:na]
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:130) ~[gson-2.10.1.jar:na]
    at com.google.gson.Gson.getAdapter(Gson.java:556) ~[gson-2.10.1.jar:na]
    at com.google.gson.Gson.toJson(Gson.java:834) ~[gson-2.10.1.jar:na]
    at com.google.gson.Gson.toJson(Gson.java:812) ~[gson-2.10.1.jar:na]
    at com.google.gson.Gson.toJson(Gson.java:759) ~[gson-2.10.1.jar:na]
    at com.google.gson.Gson.toJson(Gson.java:736) ~[gson-2.10.1.jar:na]
    at com.google.cloud.spring.data.spanner.core.convert.ConverterAwareMappingSpannerEntityWriter.convertJsonToValue(ConverterAwareMappingSpannerEntityWriter.java:375) ~[spring-cloud-gcp-data-spanner-5.0.1.jar:5.0.1]
    at com.google.cloud.spring.data.spanner.core.convert.ConverterAwareMappingSpannerEntityWriter.writeProperty(ConverterAwareMappingSpannerEntityWriter.java:448) ~[spring-cloud-gcp-data-spanner-5.0.1.jar:5.0.1]
    at com.google.cloud.spring.data.spanner.core.convert.ConverterAwareMappingSpannerEntityWriter.lambda$write$0(ConverterAwareMappingSpannerEntityWriter.java:164) ~[spring-cloud-gcp-data-spanner-5.0.1.jar:5.0.1]
    at com.google.cloud.spring.data.spanner.core.mapping.SpannerPersistentEntityImpl.lambda$doWithColumnBackedProperties$2(SpannerPersistentEntityImpl.java:207) ~[spring-cloud-gcp-data-spanner-5.0.1.jar:5.0.1]
    at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:298) ~[spring-data-commons-3.2.2.jar:3.2.2]
    at com.google.cloud.spring.data.spanner.core.mapping.SpannerPersistentEntityImpl.doWithColumnBackedProperties(SpannerPersistentEntityImpl.java:203) ~[spring-cloud-gcp-data-spanner-5.0.1.jar:5.0.1]
    at com.google.cloud.spring.data.spanner.core.convert.ConverterAwareMappingSpannerEntityWriter.write(ConverterAwareMappingSpannerEntityWriter.java:155) ~[spring-cloud-gcp-data-spanner-5.0.1.jar:5.0.1]
    at com.google.cloud.spring.data.spanner.core.convert.ConverterAwareMappingSpannerEntityProcessor.write(ConverterAwareMappingSpannerEntityProcessor.java:169) ~[spring-cloud-gcp-data-spanner-5.0.1.jar:5.0.1]
    at com.google.cloud.spring.data.spanner.core.SpannerMutationFactoryImpl.saveObject(SpannerMutationFactoryImpl.java:127) ~[spring-cloud-gcp-data-spanner-5.0.1.jar:5.0.1]
    at com.google.cloud.spring.data.spanner.core.SpannerMutationFactoryImpl.upsert(SpannerMutationFactoryImpl.java:79) ~[spring-cloud-gcp-data-spanner-5.0.1.jar:5.0.1]
    at com.google.cloud.spring.data.spanner.core.SpannerTemplate.lambda$upsert$15(SpannerTemplate.java:349) ~[spring-cloud-gcp-data-spanner-5.0.1.jar:5.0.1]
    at com.google.cloud.spring.data.spanner.core.SpannerTemplate.applySaveMutations(SpannerTemplate.java:381) ~[spring-cloud-gcp-data-spanner-5.0.1.jar:5.0.1]
    at com.google.cloud.spring.data.spanner.core.SpannerTemplate.upsert(SpannerTemplate.java:348) ~[spring-cloud-gcp-data-spanner-5.0.1.jar:5.0.1]
    at com.google.cloud.spring.data.spanner.repository.support.SimpleSpannerRepository.save(SimpleSpannerRepository.java:81) ~[spring-cloud-gcp-data-spanner-5.0.1.jar:5.0.1]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:351) ~[spring-aop-6.1.3.jar:6.1.3]
    at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:277) ~[spring-data-commons-3.2.2.jar:3.2.2]
    at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170) ~[spring-data-commons-3.2.2.jar:3.2.2]
    at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158) ~[spring-data-commons-3.2.2.jar:3.2.2]
    at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:516) ~[spring-data-commons-3.2.2.jar:3.2.2]
    at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285) ~[spring-data-commons-3.2.2.jar:3.2.2]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:628) ~[spring-data-commons-3.2.2.jar:3.2.2]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.3.jar:6.1.3]
    at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:168) ~[spring-data-commons-3.2.2.jar:3.2.2]
    at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:143) ~[spring-data-commons-3.2.2.jar:3.2.2]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.3.jar:6.1.3]
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) ~[spring-aop-6.1.3.jar:6.1.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.3.jar:6.1.3]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:220) ~[spring-aop-6.1.3.jar:6.1.3]
    at jdk.proxy2/jdk.proxy2.$Proxy80.save(Unknown Source) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:351) ~[spring-aop-6.1.3.jar:6.1.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) ~[spring-aop-6.1.3.jar:6.1.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-6.1.3.jar:6.1.3]
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137) ~[spring-tx-6.1.3.jar:6.1.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.3.jar:6.1.3]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:220) ~[spring-aop-6.1.3.jar:6.1.3]
    at jdk.proxy2/jdk.proxy2.$Proxy80.save(Unknown Source) ~[na:na]
    at com.example.spannercatservice.SpannerCatServiceApplication.testFetching(SpannerCatServiceApplication.java:43) ~[classes/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:261) ~[spring-web-6.1.3.jar:6.1.3]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:189) ~[spring-web-6.1.3.jar:6.1.3]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) ~[spring-webmvc-6.1.3.jar:6.1.3]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:917) ~[spring-webmvc-6.1.3.jar:6.1.3]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:829) ~[spring-webmvc-6.1.3.jar:6.1.3]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.1.3.jar:6.1.3]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) ~[spring-webmvc-6.1.3.jar:6.1.3]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) ~[spring-webmvc-6.1.3.jar:6.1.3]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-6.1.3.jar:6.1.3]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) ~[spring-webmvc-6.1.3.jar:6.1.3]
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) ~[tomcat-embed-core-10.1.18.jar:6.0]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.1.3.jar:6.1.3]
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[tomcat-embed-core-10.1.18.jar:6.0]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[tomcat-embed-websocket-10.1.18.jar:10.1.18]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.1.3.jar:6.1.3]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.3.jar:6.1.3]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.1.3.jar:6.1.3]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.3.jar:6.1.3]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.1.3.jar:6.1.3]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.3.jar:6.1.3]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:340) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:896) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1744) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.18.jar:10.1.18]
    at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
zhumin8 commented 4 months ago

This may not be a bug on spring-cloud-gcp.

From your stacktrace, looks like issue is with Gson not be able do conversion to json correctly. One option is to customize the Gson bean. You might want to try customization options via configuration properties or provide a customized bean of type Gson. Refer to this section in documentation.

meltsufin commented 4 months ago

You might need to add --add-opens java.base/java.time=ALL-UNNAMED the jvm args.

Alos commented 4 months ago

Feels like this should be setup by default if you are using Spring Cloud GCP and Spanner

On Thu, Apr 25, 2024, 7:22 PM Mike Eltsufin @.***> wrote:

You might need to add --add-opens java.base/java.time=ALL-UNNAMED the jvm args.

— Reply to this email directly, view it on GitHub https://github.com/GoogleCloudPlatform/spring-cloud-gcp/issues/2576#issuecomment-2078514506, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAGG5GY6WGHMVKGQV6NZW3Y7G26PAVCNFSM6AAAAABCXHH4EOVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANZYGUYTINJQGY . You are receiving this because you authored the thread.Message ID: @.***>

meltsufin commented 4 months ago

Maybe we can configure a Gson type adapter for java.time.Instant by default. It's worth looking into. We're open to contributions for this.

ablx commented 3 months ago

Hello @meltsufin please have a look: https://github.com/GoogleCloudPlatform/spring-cloud-gcp/pull/2841