microsoft / spring-data-cosmosdb

Access data with Azure Cosmos DB
MIT License
93 stars 68 forks source link

NullPointerException on the save method when id is specified with optimistic concurrency #570

Closed MapFilterMagic closed 9 months ago

MapFilterMagic commented 9 months ago

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

Eliminating any one of those three resolves the error.

This appears to be the exact same issue we're running into except it was for the Azure Java SDK, and it involved @JsonInclude(Include.NON_NULL) (though the NON_EMPTY is very close to the same thing). However, it appears they were 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're not in an immediate position to bump our Java version and jump to a higher version of Spring Boot/Spring Azure Cosmos DB SDK. We'd like to be able to use all three of the listed items at the same time.

The Error

java.lang.NullPointerException: null
    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.Id;
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 {

    @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

@Repository
public interface ExampleCosmosReactiveRepository extends ReactiveCosmosRepository<ExampleEntity, String> {
    // holds a few query methods
    ...
}

Setup

MapFilterMagic commented 9 months ago

It appears that placing a @JsonInclude(Include.ALWAYS) on the _etag field on the model resolves the error. Can anyone confirm that doing this won't mess with optimistic concurrency enforcement?


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 {

    @Id
    private String id;

    @JsonInclude(Include.ALWAYS)
    @Version
    private String _etag;

    ...

}
kushagraThapar commented 9 months ago

@MapFilterMagic - this repo has been retired and out of support for a long time now, see this - https://github.com/microsoft/spring-data-cosmosdb/blob/master/README.md

Please create a new issue on the current repo for azure-spring-data-cosmos project - https://github.com/Azure/azure-sdk-for-java/issues