Spring boot Mongo on update: E11000 duplicate key error with #Version annotation - spring

I have a Spring boot (v2.3.0.RELEASE) application with spring-data-mongodb (v3.0.0).
Basically when I add #Version annotation in my document, the application fails when I am trying to update an existing document and the code is as follows:
My document is as follows:
#Id
private String paymentId;
private String transactionId;
private String transactionTypeCode;
#Version
private Integer version;
My repository is as follows:
public interface PaymentRepository extends MongoRepository<Payment, String>, PaymentRepositoryCustom {
}
The update that fails is as follows:
Optional<Payment> paymentSaved = Optional.ofNullable(paymentRepository.findMaxIdByRefer(request.getAdd().getRefer()));
populatePayment(paymentSaved, payment);
paymentRepository.save(payment);
public static void populatePayment( Optional<Payment> paymentSaved, Payment payment) {
if (paymentSaved.isPresent() && !ObjectUtils.isEmpty(payment)) {
payment.setTransactionId(paymentSaved.get().getTransactionId());
payment.setCreationDate(paymentSaved.get().getCreationDate());
payment.setPaymentId(paymentSaved.get().getPaymentId());
}
}
The application fails on paymentRepository.save(payment) method with the following exception:
org.springframework.dao.DuplicateKeyException: E11000 duplicate key error collection: test.payment index: _id_ dup key: { _id: ObjectId('627a35b7f1ea29549008487e') }; nested exception is com.mongodb.MongoWriteException: E11000 duplicate key error collection: test.payment index: _id_ dup key: { _id: ObjectId('627a35b7f1ea29549008487e') }
at org.springframework.data.mongodb.core.MongoExceptionTranslator.translateExceptionIfPossible(MongoExceptionTranslator.java:99)
at org.springframework.data.mongodb.core.MongoTemplate.potentiallyConvertRuntimeException(MongoTemplate.java:2863)
at org.springframework.data.mongodb.core.MongoTemplate.execute(MongoTemplate.java:568)
at org.springframework.data.mongodb.core.MongoTemplate.insertDocument(MongoTemplate.java:1436)
at org.springframework.data.mongodb.core.MongoTemplate.doInsert(MongoTemplate.java:1236)
at org.springframework.data.mongodb.core.MongoTemplate.insert(MongoTemplate.java:1168)
at org.springframework.data.mongodb.repository.support.SimpleMongoRepository.save(SimpleMongoRepository.java:84)
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:564)
at org.springframework.data.repository.core.support.ImplementationInvocationMetadata.invoke(ImplementationInvocationMetadata.java:72)
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:382)
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:205)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:549)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:155)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:130)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke
It looks like it is trying to do an insert instead of update when I add the #Version annotation, any idea how i can resolve this issue please?
However, I cannot remove the #Version annotation.

I have found the issue basically on update the version was null that is why it was trying to create a new payment instead of update.I have retrieved the version from DB and now the update works fine.

Related

Saving a record using JPA in a Spring Boot Scheduler

I'm using a Spring Boot Scheduler to run a query on the DB daily to find some records based on a condition and update the records returned. Fetching the records using JPA works fine, but when I loop through them, update them, and try to save each updated record I get the following error:
Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction
Caused by: javax.persistence.RollbackException: Error while committing the transaction at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:81) at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:104) at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:562) ... 30 more Caused by: java.lang.NullPointerException at com.xxx.yyy.config.JpaAuditingConfiguration.auditorProvider$lambda-0(JpaAuditingConfiguration.kt:15) 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.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208) at com.sun.proxy.$Proxy168.getCurrentAuditor(Unknown Source) at java.base/java.util.Optional.map(Optional.java:265) at org.springframework.data.auditing.AuditingHandler.getAuditor(AuditingHandler.java:109) at org.springframework.data.auditing.AuditingHandler.markModified(AuditingHandler.java:104) at org.springframework.data.jpa.domain.support.AuditingEntityListener.touchForUpdate(AuditingEntityListener.java:112).
Here is the scheduler code I have. If I run the same code inside my service and call it using an endpoint everything works fine:
#Component
class Scheduler(
private val repository: Repository
) {
#Scheduled(cron = "0 0 2 * * *")
fun expire() {
val records = repository.findRecords()
for (record in records) {
try {
// Call some external API using record.id but this part is commented out for now until the saving works
record.active = false
repository.save(record)
} catch (ex: Exception) {
logger.error("Error expiring record " + record.id)
logger.error("Exception: ${ex.printStackTrace()}")
continue
}
}
}
}
the null pointer exception happens in the JpaAuditingConfiguration config I use for storing the created_at and last_modified_at dates. Here is the code I have for that class:
#Configuration
#EnableJpaAuditing(auditorAwareRef = "auditorProvider")
class JpaAuditingConfiguration {
#Bean
fun auditorProvider(): AuditorAware<String> {
return AuditorAware { Optional.of(SecurityContextHolder.getContext().authentication.name) }
}
}
Your JpaAuditingConfiguration requires the security context to be non null when you make modifications. When you're running your task in a scheduler there is no active request, so no active session, and therefore your authentication is null.
Usually, this is solved by making a special app user and manually authenticating them in your scheduled task.

Javers - PROPERTY_NOT_FOUND: Property 'id' not found in class 'EntityName$HibernateProxy$Azi44O4n'

I am trying to audit my entities using Javers (6.5.2). When I am doing .save() or .saveAndFlush() operation in Spring Boot (2.5.5), I randomly get the following exception:
org.javers.common.exception.JaversException: PROPERTY_NOT_FOUND: Property 'id' not found in class package.to.entity.EntityName$HibernateProxy$Azi44O4n'. If the name is correct - check annotations. Properties with #DiffIgnore or #Transient are not visible for JaVers.
at org.javers.core.metamodel.type.ManagedClass.getProperty(ManagedClass.java:83)
at org.javers.core.metamodel.type.ManagedClass.lambda$getProperties$1(ManagedClass.java:93)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1654)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
at org.javers.core.metamodel.type.ManagedClass.getProperties(ManagedClass.java:93)
at org.javers.core.metamodel.type.EntityType.spawn(EntityType.java:60)
at org.javers.core.metamodel.type.EntityType.spawn(EntityType.java:44)
at org.javers.core.metamodel.type.TypeFactory.spawnFromPrototype(TypeFactory.java:145)
at org.javers.core.metamodel.type.TypeFactory.infer(TypeFactory.java:98)
at org.javers.core.metamodel.type.TypeMapper.lambda$getJaversType$0(TypeMapper.java:99)
at org.javers.core.metamodel.type.TypeMapperEngine.computeIfAbsent(TypeMapperEngine.java:110)
at org.javers.core.metamodel.type.TypeMapper.getJaversType(TypeMapper.java:99)
at org.javers.core.JaversCore.assertJaversTypeNotValueTypeOrPrimitiveType(JaversCore.java:98)
at org.javers.core.JaversCore.commit(JaversCore.java:85)
at org.javers.spring.transactions.JaversTransactionalDecorator.commit(JaversTransactionalDecorator.java:68)
at org.javers.spring.jpa.JaversTransactionalJpaDecorator.commit(JaversTransactionalJpaDecorator.java:50)
at org.javers.spring.jpa.JaversTransactionalJpaDecorator$$FastClassBySpringCGLIB$$8b0feaeb.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)
at org.javers.spring.jpa.JaversTransactionalJpaDecorator$$EnhancerBySpringCGLIB$$3ff29c58.commit(<generated>)
at org.javers.spring.auditable.aspect.JaversCommitAdvice.commitObject(JaversCommitAdvice.java:93)
at java.base/java.util.Arrays$ArrayList.forEach(Arrays.java:4390)
at java.base/java.util.Collections$UnmodifiableCollection.forEach(Collections.java:1085)
at org.javers.spring.auditable.aspect.springdata.AbstractSpringAuditableRepositoryAspect.lambda$onSave$0(AbstractSpringAuditableRepositoryAspect.java:28)
at java.base/java.util.Optional.ifPresent(Optional.java:183)
at org.javers.spring.auditable.aspect.springdata.AbstractSpringAuditableRepositoryAspect.onSave(AbstractSpringAuditableRepositoryAspect.java:27)
at org.javers.spring.auditable.aspect.springdatajpa.JaversSpringDataJpaAuditableRepositoryAspect.onSaveAndFlushExecuted(JaversSpringDataJpaAuditableRepositoryAspect.java:51)
at jdk.internal.reflect.GeneratedMethodAccessor541.invoke(Unknown Source)
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.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:634)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:617)
at org.springframework.aop.aspectj.AspectJAfterReturningAdvice.afterReturning(AspectJAfterReturningAdvice.java:66)
at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:58)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:175)
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.$Proxy313.saveAndFlush(Unknown Source)
at package.to.entity.EntityService.add(EntityName.java:139)
at package.to.entity.EntityService$$FastClassBySpringCGLIB$$4a066350.invoke(<generated>)...
This happens randomly (at least on my local backend). Yesterday, at first, everything was working fine, then after application restart, I just couldn't save my entity (I am not aware of changing anything important in my code). Today (still not aware of changing anything), it just suddenly started working locally and I can not simulate this problem anymore. I could only simulate in on environment from frontend deployed branch, so I could get at least exception from OpenShift console.
How id attribute in entity is defined:
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
How I save entity:
public EntityName add(Dto dto) {
EntityName entity = dto.mapToEntity();
entity = repository.saveAndFlush(entity);
repository.refresh(entity); // In debug it wasn't even able to make it here
return entity;
}
I have #TypeName in my entity defined.
Javers configuration:
#Bean(name = "JaversFromStarter")
public Javers javers(
JaversSqlRepository sqlRepository, PlatformTransactionManager txManager,
JaversSqlProperties sqlProperties
) {
return TransactionalJpaJaversBuilder.javers()
.withTxManager(txManager)
.registerJaversRepository(sqlRepository)
.withObjectAccessHook(sqlProperties.createObjectAccessHookInstance())
.withProperties(sqlProperties)
.registerValueTypeAdapter(new JsonNodeTypeAdapter())
.registerValueTypeAdapter(new InstantTypeAdapter())
.build();
}
When I checked JaversSqlProperties, it is using HibernateUnproxyObjectAccessHook so I think it should be able to unproxy the entity properly before auditing it.
Javers configuration in application.yml:
javers:
mappingStyle: FIELD
algorithm: SIMPLE
commitIdGenerator: synchronized_sequence
prettyPrint: true
typeSafeValues: false
newObjectSnapshot: false
auditableAspectEnabled: true
springDataAuditableRepositoryAspectEnabled: true
sqlSchemaManagementEnabled: true
sqlGlobalIdCacheDisabled: false
prettyPrintDateFormats:
localDateTime: "dd MMM yyyy, HH:mm:ss"
zonedDateTime: "dd MMM yyyy, HH:mm:ssZ"
localDate: "dd MMM yyyy"
localTime: "HH:mm:ss"
PS: This entity has a reference to other audited entity (which is saved before this one).
Does anyone please have any idea, what could be wrong?
As a rule of thumb, you should newer treat Hibernate proxies as your Entities.
Do you use HibernateUnproxyHook? I recommend using the javers' starter. It sets up all javers' beans (including this hook).

Azure Blob Storage Connection

I have problems with Azure Blob Storage, when i want to upload image there is an error
Caused by: java.lang.IllegalArgumentException: prefetch > 0 required but it was 0
at reactor.core.publisher.FluxConcatMap.<init>(FluxConcatMap.java:101) ~[reactor-core-3.3.12.RELEASE.jar:3.3.12.RELEASE]
at reactor.core.publisher.Flux.concatMap(Flux.java:3673) ~[reactor-core-3.3.12.RELEASE.jar:3.3.12.RELEASE]
at com.azure.storage.common.implementation.UploadUtils.uploadFullOrChunked(UploadUtils.java:52) ~[azure-storage-common-12.14.0.jar:12.14.0]
at com.azure.storage.blob.BlobAsyncClient.uploadWithResponse(BlobAsyncClient.java:600) ~[azure-storage-blob-12.14.1.jar:12.14.1]
at com.azure.storage.blob.BlobAsyncClient.uploadWithResponse(BlobAsyncClient.java:491) ~[azure-storage-blob-12.14.1.jar:12.14.1]
at com.azure.storage.blob.BlobAsyncClient.upload(BlobAsyncClient.java:381) ~[azure-storage-blob-12.14.1.jar:12.14.1]
at com.kypnt.edutest.testcentreservice.service.ImageService.upload(ImageService.java:48) ~[main/:na]
at com.kypnt.edutest.testcentreservice.controller.image.ImageController.uploadImage(ImageController.java:19) ~[main/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[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:566) ~[na:na]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.2.12.RELEASE.jar:5.2.12.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.2.12.RELEASE.jar:5.2.12.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105) ~[spring-webmvc-5.2.12.RELEASE.jar:5.2.12.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878) ~[spring-webmvc-5.2.12.RELEASE.jar:5.2.12.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792) ~[spring-webmvc-5.2.12.RELEASE.jar:5.2.12.RELEASE]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.12.RELEASE.jar:5.2.12.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.12.RELEASE.jar:5.2.12.RELEASE]
... 80 common frames omitted
Suppressed: java.lang.Exception: #block terminated with an error
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:99) ~[reactor-core-3.3.12.RELEASE.jar:3.3.12.RELEASE]
at reactor.core.publisher.Mono.block(Mono.java:1685) ~[reactor-core-3.3.12.RELEASE.jar:3.3.12.RELEASE]
... 93 common frames omitted
I googled this but can't find the reason.
My configuration:
#Configuration
public class AzureStorageBlobClientConfig {
#Value("${azure.storage.connection-string}")
private String connectionString;
#Value("${azure.storage.container-name}")
private String containerName;
#Bean
public BlobServiceAsyncClient blobServiceClient() {
return new BlobServiceClientBuilder().connectionString(connectionString).buildAsyncClient();
}
#Bean
public BlobContainerAsyncClient blobAsyncClient() {
return blobServiceClient().getBlobContainerAsyncClient(containerName);
}
}
My Service:
#Slf4j
#Service
public class ImageService {
#Autowired
private BlobContainerAsyncClient blobContainerAsyncClient;
private int blockSize = 10 * 1024;
private int numBuffers = 5;
public MyResponse<?> upload(MultipartFile file) {
if (file.isEmpty())
throw new BadRequestException("No File");
String fileName = UUID.randomUUID() + file.getOriginalFilename();
try {
BlobAsyncClient blobAsyncClient = blobContainerAsyncClient.getBlobAsyncClient(fileName);
Flux<ByteBuffer> data = Flux.just(ByteBuffer.wrap(file.getInputStream().readAllBytes()));
ParallelTransferOptions parallelTransferOptions = new ParallelTransferOptions(numBuffers, blockSize, null);
blobAsyncClient.upload(data, parallelTransferOptions, true).block();
// BlobContainerClient container = new BlobContainerClientBuilder()
// .connectionString(connectionString)
// .containerName(containerName)
// .buildClient();
//
// BlobClient blobClient = container.getBlobClient(fileName);
//
// blobClient.upload(file.getInputStream(), file.getSize(), true);
// blobClient
// .blobName(fileName)
// .buildClient()
// .upload(file.getInputStream(), file.getSize());
} catch (IOException e) {
log.warn(e.getMessage());
// throw new EdutestException(ErrorType.INTERNAL_ERROR);
}
return new OKResponse<>("Good");
}
}
Those commented codes, tutorial that I have tried but all of them returning this exception.
There I get my connection string and created container and get its name:
My gradle:
plugins {
id 'org.springframework.boot' version '2.3.7.RELEASE'
id 'io.spring.dependency-management' version '1.0.10.RELEASE'
id 'java'
}
// some code
dependencies {
implementation 'com.azure:azure-storage-blob:12.14.1'
implementation group: 'com.azure', name: 'azure-identity', version: '1.4.1'
}
// some code
Finally I found that problem was in new versions of "azure storage blob"
It is using new 'reactor-netty-core' and 'reactor-netty-http' but with old codes, or something like that, read about it here
To fix it use versions older:
implementation group: 'com.azure.spring', name: 'azure-spring-boot-starter-storage', version: '3.1.0'
You can use this version. When problem exist, I used version '3.10.0'. Didn't test other versions.

Kotlin based Spring RabbitListener produces endlessloop trying to send back `kotlin.Unit`

We have a RabbitListener implemented in Kotlin. The method returns Unit, so I dont expect Spring to send a result but we are getting the following error message. It looks like Spring tries to iterprete Unit as the result and send a rabbit message back:
2021-04-16 16:39:21
msg="Execution of Rabbit message listener failed." thread="org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-2" level=WARN logger="org.springframework.amqp.rabbit.listener.ConditionalRejectingErrorHandler" exception="org.springframework.amqp.rabbit.support.ListenerExecutionFailedException: Listener threw exception
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.wrapToListenerExecutionFailedExceptionIfNeeded(AbstractMessageListenerContainer.java:1746)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1636)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.actualInvokeListener(AbstractMessageListenerContainer.java:1551)
at jdk.internal.reflect.GeneratedMethodAccessor64.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
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 brave.spring.rabbit.TracingRabbitListenerAdvice.invoke(TracingRabbitListenerAdvice.java:108)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
at org.springframework.amqp.rabbit.listener.$Proxy146.invokeListener(Unknown Source)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:1539)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:1530)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:1474)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:967)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:913)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$1600(SimpleMessageListenerContainer.java:83)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.mainLoop(SimpleMessageListenerContainer.java:1288)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1194)
at java.base/java.lang.Thread.run(Unknown Source)
Caused by: org.springframework.amqp.rabbit.listener.adapter.ReplyFailureException: Failed to send reply with payload 'InvocationResult [returnValue=kotlin.Unit, returnType=class java.lang.Object, bean=com.xxx.EventHandler#4aaf6902, method=public java.lang.Object com.xxx.EventHandler.receiveMessage(org.springframework.amqp.core.Message)]'
at org.springframework.amqp.rabbit.listener.adapter.AbstractAdaptableMessageListener.doHandleResult(AbstractAdaptableMessageListener.java:476)
at org.springframework.amqp.rabbit.listener.adapter.AbstractAdaptableMessageListener.handleResult(AbstractAdaptableMessageListener.java:400)
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandlerAndProcessResult(MessagingMessageListenerAdapter.java:152)
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:135)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1632)
... 20 common frames omitted
Caused by: java.lang.IllegalArgumentException: SimpleMessageConverter only supports String, byte[] and Serializable payloads, received: kotlin.Unit
at org.springframework.amqp.support.converter.SimpleMessageConverter.createMessage(SimpleMessageConverter.java:164)
at org.springframework.amqp.support.converter.AbstractMessageConverter.createMessage(AbstractMessageConverter.java:88)
at org.springframework.amqp.support.converter.AbstractMessageConverter.toMessage(AbstractMessageConverter.java:70)
at org.springframework.amqp.rabbit.listener.adapter.AbstractAdaptableMessageListener.convert(AbstractAdaptableMessageListener.java:519)
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.buildMessage(MessagingMessageListenerAdapter.java:257)
at org.springframework.amqp.rabbit.listener.adapter.AbstractAdaptableMessageListener.doHandleResult(AbstractAdaptableMessageListener.java:464)
... 24 common frames omitted
" time="2021-04-16 14:39:21,726"
here a simplified version of the RabbitLister:
#Component
class EventHandler {
#RabbitListener(queues = ["someQueue"])
fun receiveMessage(message: Message): Unit {
log.debug("Received message")
}
}
We use Spring Boot 2.4.4, Spring Rabbit 2.3.6 and Spring Framework 5.3.6
debugging the issue, I got the following result:
What version are you using?
This works fine for me (with and without the Unit return type)...
#RabbitListener(queues = ["foo"])
open fun listen(data: String?): Unit {
println(data)
}
Even with : Unit, I see void as the return type and null:

Spring data Cassandra Rest Id must be assignable to Serializable!: null

Given below Entity and Repository, I get Id must be assignable to Serializable!: null error when I access rest resource for repository.
curl -H 'Accept: application/json' http://localhost:8080/properties
{"cause":null,"message":"Id must be assignable to Serializable!: null"}
Groovy code
#Component
interface PropertyRepository extends CassandraRepository<Property, String> {
}
#Table("property_v1")
#Canonical
class Property {
#PrimaryKeyColumn(value = "name", type = PARTITIONED)
String name
#PrimaryKeyColumn(value = "environment", type = CLUSTERED)
String environment
#Column("value")
String value
}
I tried adding #Id annotation to primary key field but spring does not allow #Id and #PrimaryKeyColumn annotations on the same entity.
I get #Table types must not define both #Id and #PrimaryKeyColumn properties error.
How do I access spring data Cassandra entities over rest?
I tried using RepositoryRestResource annotation as well on Repository class but received same error.
#RepositoryRestResource(path = "/properties", collectionResourceRel = "properties")
Versions:
Spring boot: 2.0.1.RELEASE
Uses spring-boot-starter-data-cassandra, spring-boot-starter-data-rest moduldes
Exception Stacktrace:
java.lang.IllegalArgumentException: Id must be assignable to Serializable!: null
at org.springframework.util.Assert.instanceCheckFailed(Assert.java:637)
at org.springframework.util.Assert.isInstanceOf(Assert.java:537)
at org.springframework.data.rest.webmvc.support.RepositoryEntityLinks.linkToSingleResource(RepositoryEntityLinks.java:135)
at org.springframework.data.rest.core.support.DefaultSelfLinkProvider.createSelfLinkFor(DefaultSelfLinkProvider.java:68)
at org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler.getSelfLinkFor(PersistentEntityResourceAssembler.java:99)
at org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler.wrap(PersistentEntityResourceAssembler.java:76)
at org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler.toResource(PersistentEntityResourceAssembler.java:55)
at org.springframework.data.rest.webmvc.AbstractRepositoryRestController.entitiesToResources(AbstractRepositoryRestController.java:110)
at org.springframework.data.rest.webmvc.AbstractRepositoryRestController.toResources(AbstractRepositoryRestController.java:80)
at org.springframework.data.rest.webmvc.RepositoryEntityController.getCollectionResource(RepositoryEntityController.java:209)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:209)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:877)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:783)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:866)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
Figured out the issue.
If an entity class has a composite key, spring data rest works only if I have a dedicated class for Primary Key columns.
Changing the class structure to below, enabled rest resources for spring data entities. I used a nested static class for key. But it could be very well a public class of its own.
I feel this boiler plate should be removed from developers and instead spring could look into partition key column and use it as Id.
#Component
interface PropertyRepository extends CassandraRepository<Property, Property.PropertyKey> {
}
#Table("property_v1")
#Canonical
class Property {
#PrimaryKey
PropertyKey key
#Column("value")
String value
#PrimaryKeyClass
#Canonical
static class PropertyKey implements Serializable {
#PrimaryKeyColumn(value = "name", type = PARTITIONED)
String name
#PrimaryKeyColumn(value = "environment", type = CLUSTERED)
String environment
}
}

Resources