Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
Alos opened this issue Feb 2, 2024 · 5 comments
Open

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

Alos opened this issue Feb 2, 2024 · 5 comments
Labels
priority: p3 spanner type: question Further information is requested

Comments

@Alos
Copy link
Contributor

Alos commented Feb 2, 2024

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]
@diegomarquezp diegomarquezp added type: bug Something isn't working priority: p2 labels Feb 5, 2024
@zhumin8
Copy link
Contributor

zhumin8 commented Apr 25, 2024

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.

@zhumin8 zhumin8 added type: question Further information is requested priority: p3 and removed type: bug Something isn't working priority: p2 labels Apr 25, 2024
@meltsufin
Copy link
Member

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

@Alos
Copy link
Contributor Author

Alos commented Apr 26, 2024 via email

@meltsufin
Copy link
Member

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
Copy link
Contributor

ablx commented May 3, 2024

Hello @meltsufin
please have a look: #2841

ablx added a commit to ablx/spring-cloud-gcp that referenced this issue May 3, 2024
ablx added a commit to ablx/spring-cloud-gcp that referenced this issue May 3, 2024
ablx added a commit to ablx/spring-cloud-gcp that referenced this issue May 3, 2024
ablx added a commit to ablx/spring-cloud-gcp that referenced this issue May 3, 2024
ablx added a commit to ablx/spring-cloud-gcp that referenced this issue May 6, 2024
ablx added a commit to ablx/spring-cloud-gcp that referenced this issue May 15, 2024
burkedavison added a commit that referenced this issue May 15, 2024
…ues (@ablx) (#2881)

* feat: #2576 Added typeadapter to read and write Instant values. (#2841)

* chore: additional test cases for InstantTypeAdapterTest

---------

Co-authored-by: Mirco Franzek <ablx@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
priority: p3 spanner type: question Further information is requested
Projects
None yet
Development

No branches or pull requests

5 participants