GoogleCloudPlatform / GoogleCloudPlatform/spring-cloud-gcp

@EnableSpannerAuditing fails if saved entity contains UUID

Open
#3,143 2 comments 0 reactions 0 assignees View on GitHub
priority: p3 spanner type: bug
Dominant language
Java
Stars
551
Forks
349
Avg merge
1d 13h
Merged PRs (30d)
14

Description

**Runtime**
Java 21
Spring Boot 3.3.2
spring-cloud-gcp-dependencies 5.5.0

**Describe the bug**
If a Spanner Entity contains UUID as a primary key and Spanner auditing is configured, repository save will throw a InaccessibleObjectException
Spring framework uses reflection in order to make jdk internal fields accessible. This is disabled since JDK 16 and throws this exception. Only occurs if Spanner Auditing is enabled.

**Stacktrace**
```
java.lang.reflect.InaccessibleObjectException: Unable to make field private final long java.util.UUID.mostSigBits accessible: module java.base does not "opens java.util" to unnamed module @4f9a3314
at java.base/java.lang.reflect.AccessibleObject.throwInaccessibleObjectException(AccessibleObject.java:391)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:367)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:315)
at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:183)
at java.base/java.lang.reflect.Field.setAccessible(Field.java:177)
at org.springframework.util.ReflectionUtils.makeAccessible(ReflectionUtils.java:804)
at org.springframework.data.mapping.context.AbstractMappingContext$PersistentPropertyCreator.doWith(AbstractMappingContext.java:616)
at org.springframework.util.ReflectionUtils.doWithFields(ReflectionUtils.java:728)
at org.springframework.data.mapping.context.AbstractMappingContext.doAddPersistentEntity(AbstractMappingContext.java:471)
at org.springframework.data.mapping.context.AbstractMappingContext.addPersistentEntity(AbstractMappingContext.java:424)
at org.springframework.data.mapping.context.AbstractMappingContext.getPersistentEntity(AbstractMappingContext.java:320)
at org.springframework.data.mapping.context.AbstractMappingContext.getPersistentEntity(AbstractMappingContext.java:334)
at org.springframework.data.mapping.context.AbstractMappingContext.getPersistentEntity(AbstractMappingContext.java:97)
at org.springframework.data.mapping.context.MappingContext.getRequiredPersistentEntity(MappingContext.java:147)
at org.springframework.data.mapping.context.PersistentPropertyPathFactory.lambda$from$2(PersistentPropertyPathFactory.java:259)
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:321)
at org.springframework.data.mapping.context.PersistentPropertyPathFactory.from(PersistentPropertyPathFactory.java:264)
at org.springframework.data.mapping.context.PersistentPropertyPathFactory.from(PersistentPropertyPathFactory.java:235)
at org.springframework.data.mapping.context.PersistentPropertyPathFactory.from(PersistentPropertyPathFactory.java:166)
at org.springframework.data.mapping.context.AbstractMappingContext.doFindPersistentPropertyPaths(AbstractMappingContext.java:374)
at org.springframework.data.mapping.context.AbstractMappingContext.findPersistentPropertyPaths(AbstractMappingContext.java:358)
at org.springframework.data.auditing.MappingAuditableBeanWrapperFactory$MappingAuditingMetadata.findPropertyPaths(MappingAuditableBeanWrapperFactory.java:150)
at org.springframework.data.auditing.MappingAuditableBeanWrapperFactory$MappingAuditingMetadata.(MappingAuditableBeanWrapperFactory.java:124)
at org.springframework.data.auditing.MappingAuditableBeanWrapperFactory.lambda$getBeanWrapperFor$0(MappingAuditableBeanWrapperFactory.java:85)
at java.base/java.util.concurrent.ConcurrentMap.computeIfAbsent(ConcurrentMap.java:330)
at org.springframework.data.auditing.MappingAuditableBeanWrapperFactory.lambda$getBeanWrapperFor$1(MappingAuditableBeanWrapperFactory.java:84)
at org.springframework.data.mapping.context.PersistentEntities.lambda$mapOnContext$2(PersistentEntities.java:144)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179)
at java.base/java.util.Spliterators$ArraySpliterator.tryAdvance(Spliterators.java:1034)
at java.base/java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:129)
at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:527)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:513)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:150)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:647)
at org.springframework.data.mapping.context.PersistentEntities.mapOnContext(PersistentEntities.java:145)
at org.springframework.data.auditing.MappingAuditableBeanWrapperFactory.lambda$getBeanWrapperFor$3(MappingAuditableBeanWrapperFactory.java:82)
at java.base/java.util.Optional.flatMap(Optional.java:289)
at org.springframework.data.auditing.MappingAuditableBeanWrapperFactory.getBeanWrapperFor(MappingAuditableBeanWrapperFactory.java:76)
at org.springframework.data.auditing.AuditingHandlerSupport.touch(AuditingHandlerSupport.java:132)
at org.springframework.data.auditing.AuditingHandlerSupport.markModified(AuditingHandlerSupport.java:127)
at org.springframework.data.auditing.AuditingHandler.markModified(AuditingHandler.java:98)
at java.base/java.util.Collections$SingletonList.forEach(Collections.java:5186)
at com.google.cloud.spring.data.spanner.repository.support.SpannerAuditingEventListener.onApplicationEvent(SpannerAuditingEventListener.java:43)
at com.google.cloud.spring.data.spanner.repository.support.SpannerAuditingEventListener.onApplicationEvent(SpannerAuditingEventListener.java:28)
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:185)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:178)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:156)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:452)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:385)
at com.google.cloud.spring.data.spanner.core.SpannerTemplate.maybeEmitEvent(SpannerTemplate.java:732)
at com.google.cloud.spring.data.spanner.core.SpannerTemplate.applySaveMutations(SpannerTemplate.java:380)
at com.google.cloud.spring.data.spanner.core.SpannerTemplate.upsert(SpannerTemplate.java:348)
at com.google.cloud.spring.data.spanner.repository.support.SimpleSpannerRepository.save(SimpleSpannerRepository.java:81)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:354)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:277)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158)
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:516)
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:628)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:168)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:143)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223)
at jdk.proxy2/jdk.proxy2.$Proxy115.save(Unknown Source)
```

Contributor guide

Open the contributing guide

Research direction

Start at com.google.cloud.spring.data.spanner.repository.support.SpannerAuditingEventListener and follow the save path through SpannerTemplate, then inspect the Spring mapping and auditing calls shown in the stack trace. Add regression coverage for saving an entity with a UUID primary key while auditing is enabled, and verify that the save no longer throws InaccessibleObjectException.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring-boot
Domain
databases
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.