Description
My team is attempting to set the id
field on a document and are running into a NullPointerException
from the ReactiveCosmosTemplate
when saving the document off to the DB. We're utilizing optimistic concurrency with the use of @Version
on an _etag
field. Up until now, we had left the id
field on the model unset, so Cosmos was just generating a random GUID for us. However, we now need to set this to a different unique value at the SDK level before saving it off into the database.
What we've found is that we cannot have all of the following at the same time without running into that NullPointerException
- Enforcement of optimistic concurrency at the SDK level via the use of
@Version
on an _etag
field
- Use of
@JsonInclude(Include.NON_EMPTY)
on our persisted model
- Ability to set the
id
field from the SDK level (in our code) instead of leaving it unset and having Cosmos generate it for us
Eliminating any one of those three resolves the error.
This appears to be the exact same issue we're running into except it involved @JsonInclude(Include.NON_NULL)
(though the NON_EMPTY
is very close to the same thing). However, it appears it was closed?:
Am I understanding that correctly?
Intended Resolution
Does anyone see a path forward? Do we know what the resolution of the issue(s) was, or if it could be looked at again? We'd like to be able to use all three of the listed items at the same time.
The Error
at com.azure.spring.data.cosmos.core.ReactiveCosmosTemplate.applyVersioning(ReactiveCosmosTemplate.java:817)
at com.azure.spring.data.cosmos.core.ReactiveCosmosTemplate.upsert(ReactiveCosmosTemplate.java:470)
at com.azure.spring.data.cosmos.repository.support.SimpleReactiveCosmosRepository.save(SimpleReactiveCosmosRepository.java:102)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:289)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:137)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:121)
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:529)
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:639)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:163)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:138)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
at com.sun.proxy.$Proxy266.save(Unknown Source)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
at com.sun.proxy.$Proxy266.save(Unknown Source)
at com.example.service.dao.ExampleDaoService.saveExampleEntity(ExampleDaoService.java:123)
at com.example.service.example.ExampleService.doSomething(ExampleService.java:999)
at com.exampo$$FastClassBySpringCGLIB$$2383bf94.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:783)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753)
...
Code Snippets
Our Data Model
import org.springframework.data.annotation.Version;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.azure.spring.data.cosmos.core.mapping.Container;
@JsonInclude(Include.NON_EMPTY)
@Container(containerName = "my-container")
public class ExampleEntity implements PersistenceEntity {
java
@Id
private String id;
@Version
private String _etag;
...
}
Our Dao Layer
@Service
public class ExampleDaoService {
@Autowired
private ExampleCosmosRepository exampleCosmosRepository;
...
public void saveExampleEntity(ExampleEntity exampleEntity) {
exampleCosmosReactiveRepository.save(exampleEntity).block();
}
Our Repository Layer
public interface ExampleCosmosReactiveRepository extends ReactiveCosmosRepository<ExampleEntity, String> {
// holds a few query methods
...
}
Setup
- MacOS
- Java 11
- Spring Boot
- Azure Cosmos DB (Core API)
- Libraries:
- spring-boot-starter-parent: 2.6.6
- spring-cloud.version: 2021.0.1
- spring-cloud-azure-dependencies: 4.1.0
- Environment: Local machine