'Spring data crud repository save method not able to save Java model in Redis cache

We are trying to save a model into the Redis cache using the spring data crud repository. This model has one property which is a map like below.

private Map<String, StudentInfo> studentData = new HashMap<String, StudentInfo>();

And this StudentInfo model has another Map as a property as below:-

private Map<String, Set<Books>> stBooks = new HashMap<String, Set<Books>>();

So overall structure is like this:- Student.java

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
@JsonRootName(value = "student")
@JsonInclude(Include.NON_NULL)
public class Student implements Serializable {

private static final long serialVersionUID = -2421290151039598746L;
private Map<String, StudentInfo> studentData = new HashMap<String, StudentInfo>();
@JsonCreator
public Student(@JsonProperty("studentData") Map<String, StudentInfo> aStudentData) 
{
    super();
    this.setStudentData(aStudentData);
}
public void studentData(String aId, StudentInfo astudentData) 
{
    this.studentData.put(aId, astudentData);
}
public Map<String, StudentInfo> getStudentData() 
{
    return this.studentData;
}
private void setStudentData(Map<String, StudentInfo> aStudentData) 
{
    this.studentData = aStudentData;
}}

StudentInfo.java

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;
import com.fasterxml.jackson.annotation.JsonInclude.Include;

@JsonRootName(value = "studentInfo")
@JsonInclude(Include.NON_NULL)
public class StudentInfo implements Serializable {

private static final long serialVersionUID = 6873987079436896955L;
String stName = null;;
private Map<String, Set<Books>> stBooks = new HashMap<String, Set<Books>>();
public StudentInfo(String stName) 
{
    super();
    this.setDealerCode(stName);
}
@JsonCreator
public StudentInfo(@JsonProperty("stName") String aStName, 
                                   @JsonProperty("stBooks") Map<String, Set<Books>> aStBooks) 
{
    super();
    this.setDealerCode(aStName);
    this.setStBooks(aStBooks);
}
private void setStBooks(Map<String, Set<Books>> aStBooks) 
{
    this.stBooks = aStBooks;
}
public Map<String, Set<Books>> getStBooks()
{
    return this.stBooks;
}
private void setDealerCode(String astName) 
{
    this.stName = astName;
}
public String getStName()
{
    return this.stName;
}
@Override
public String toString() {
    return "Student [StudentName=" + stName + "]";
}}

Books.java

import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;
import com.fasterxml.jackson.annotation.JsonInclude.Include;

@JsonRootName(value = "books")
@JsonInclude(Include.NON_NULL)
public class Books implements Serializable {

private static final long serialVersionUID = 1759477433483466736L;

private String bookName = null;

@JsonCreator
public Books(@JsonProperty("bookName") String aBookName) 
{
    super();
    this.setBookName(aBookName);
}

public String getBookName() {
    return bookName;
}

private void setBookName(String aBookName) {
    this.bookName = aBookName;
}

public String toString()
{
    return this.getBookName();
}}

StudenCacheModel.java

import java.io.Serializable;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;

 import lombok.Getter;
 import lombok.Setter;

@Getter
 @Setter
@RedisHash(value = "StudentCache")
public class StudenCacheModel implements Serializable {
   private static final long serialVersionUID = 9174813592532123048L;

    @Id
   String userId;
   Student student;
}

Spring Crud Repository

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface StudenCacheRepository extends CrudRepository<StudenCacheModel, String> {

}

Saving the model using Crud Repo

StudenCacheModel studentCacheModel = new StudenCacheModel();    
studentCacheModel.setSrudent(st); //student model set
studentCacheModel.setUserId(userId);
studentCacheRepository.save(studentCacheModel);

Exception:-

enter code hereorg.springframework.data.mapping.MappingException: Couldn't find 
PersistentEntity for type class java.lang.Object!

Complete Stacktrace:-

org.springframework.data.mapping.MappingException: Couldn't find PersistentEntity for type class java.lang.Object! at org.springframework.data.mapping.context.MappingContext.getRequiredPersistentEntity(MappingContext.java:79) ~[spring-data-commons-2.6.1.jar:2.6.1] at org.springframework.data.redis.core.convert.MappingRedisConverter.writeInternal(MappingRedisConverter.java:621) ~[spring-data-redis-2.6.1.jar:2.6.1] at org.springframework.data.redis.core.convert.MappingRedisConverter.writeMap(MappingRedisConverter.java:867) ~[spring-data-redis-2.6.1.jar:2.6.1] at org.springframework.data.redis.core.convert.MappingRedisConverter.lambda$writeInternal$2(MappingRedisConverter.java:640) ~[spring-data-redis-2.6.1.jar:2.6.1] at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:360) ~[spring-data-commons-2.6.1.jar:2.6.1] at org.springframework.data.redis.core.convert.MappingRedisConverter.writeInternal(MappingRedisConverter.java:624) ~[spring-data-redis-2.6.1.jar:2.6.1] at org.springframework.data.redis.core.convert.MappingRedisConverter.writeMap(MappingRedisConverter.java:867) ~[spring-data-redis-2.6.1.jar:2.6.1] at org.springframework.data.redis.core.convert.MappingRedisConverter.lambda$writeInternal$2(MappingRedisConverter.java:640) ~[spring-data-redis-2.6.1.jar:2.6.1] at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:360) ~[spring-data-commons-2.6.1.jar:2.6.1] at org.springframework.data.redis.core.convert.MappingRedisConverter.writeInternal(MappingRedisConverter.java:624) ~[spring-data-redis-2.6.1.jar:2.6.1] at org.springframework.data.redis.core.convert.MappingRedisConverter.writeMap(MappingRedisConverter.java:867) ~[spring-data-redis-2.6.1.jar:2.6.1] at org.springframework.data.redis.core.convert.MappingRedisConverter.lambda$writeInternal$2(MappingRedisConverter.java:640) ~[spring-data-redis-2.6.1.jar:2.6.1] at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:360) ~[spring-data-commons-2.6.1.jar:2.6.1] at org.springframework.data.redis.core.convert.MappingRedisConverter.writeInternal(MappingRedisConverter.java:624) ~[spring-data-redis-2.6.1.jar:2.6.1] at org.springframework.data.redis.core.convert.MappingRedisConverter.lambda$writeInternal$2(MappingRedisConverter.java:666) ~[spring-data-redis-2.6.1.jar:2.6.1] at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:360) ~[spring-data-commons-2.6.1.jar:2.6.1] at org.springframework.data.redis.core.convert.MappingRedisConverter.writeInternal(MappingRedisConverter.java:624) ~[spring-data-redis-2.6.1.jar:2.6.1] at org.springframework.data.redis.core.convert.MappingRedisConverter.write(MappingRedisConverter.java:424) ~[spring-data-redis-2.6.1.jar:2.6.1] at org.springframework.data.redis.core.convert.MappingRedisConverter.write(MappingRedisConverter.java:114) ~[spring-data-redis-2.6.1.jar:2.6.1] at org.springframework.data.redis.core.RedisKeyValueAdapter.put(RedisKeyValueAdapter.java:215) ~[spring-data-redis-2.6.1.jar:2.6.1] at org.springframework.data.keyvalue.core.KeyValueTemplate.lambda$update$1(KeyValueTemplate.java:221) ~[spring-data-keyvalue-2.6.1.jar:2.6.1] at org.springframework.data.keyvalue.core.KeyValueTemplate.execute(KeyValueTemplate.java:362) ~[spring-data-keyvalue-2.6.1.jar:2.6.1] at org.springframework.data.keyvalue.core.KeyValueTemplate.update(KeyValueTemplate.java:221) ~[spring-data-keyvalue-2.6.1.jar:2.6.1] at org.springframework.data.redis.core.RedisKeyValueTemplate.update(RedisKeyValueTemplate.java:178) ~[spring-data-redis-2.6.1.jar:2.6.1] at org.springframework.data.keyvalue.repository.support.SimpleKeyValueRepository.save(SimpleKeyValueRepository.java:80) ~[spring-data-keyvalue-2.6.1.jar:2.6.1] 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.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:289) ~[spring-data-commons-2.6.1.jar:2.6.1] at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:137) ~[spring-data-commons-2.6.1.jar:2.6.1] at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:121) ~[spring-data-commons-2.6.1.jar:2.6.1] at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:529) ~[spring-data-commons-2.6.1.jar:2.6.1] at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285) ~[spring-data-commons-2.6.1.jar:2.6.1] at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:639) ~[spring-data-commons-2.6.1.jar:2.6.1] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.15.jar:5.3.15] at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:163) ~[spring-data-commons-2.6.1.jar:2.6.1] at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:138) ~[spring-data-commons-2.6.1.jar:2.6.1] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.15.jar:5.3.15] at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) ~[spring-aop-5.3.15.jar:5.3.15] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.15.jar:5.3.15] at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215) ~[spring-aop-5.3.15.jar:5.3.15] at com.sun.proxy.$Proxy163.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: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.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344) ~[spring-aop-5.3.15.jar:5.3.15] at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198) ~[spring-aop-5.3.15.jar:5.3.15] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.15.jar:5.3.15] at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137) ~[spring-tx-5.3.15.jar:5.3.15] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.15.jar:5.3.15] at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215) ~[spring-aop-5.3.15.jar:5.3.15] at com.sun.proxy.$Proxy163.save(Unknown Source) ~[na:na]

Q1 - Is it the limitation of Spring data redis as we are able to save the same model using JEDIS library.

Any help/input will be highly apperciated !!



Solution 1:[1]

With a few small changes I got your example working:

In the POM used Spring Boot 2.6.4 and Java 11:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <optional>true</optional>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-annotations</artifactId>
</dependency>
  • Added a no-args constructor to StudentInfo
  • Added Lombok @ToString1 to StudenCacheModel
  • Modified Student to have method params that Jackson reflection could use:
@JsonRootName(value = "student")
@JsonInclude(Include.NON_NULL)
public class Student implements Serializable {

  private static final long serialVersionUID = -2421290151039598746L;
  private Map<String, StudentInfo> studentData = new HashMap<String, StudentInfo>();

  @JsonCreator
  public Student(@JsonProperty("studentData") Map<String, StudentInfo> studentData) {
    super();
    this.setStudentData(studentData);
  }

  public void studentData(String aId, StudentInfo studentData) {
    this.studentData.put(aId, studentData);
  }

  public Map<String, StudentInfo> getStudentData() {
    return this.studentData;
  }

  private void setStudentData(Map<String, StudentInfo> studentData) {
    this.studentData = studentData;
  }
}

Added a simple test with a runner on the main app:

@SpringBootApplication
public class SodemoApplication {
  
  @Bean
  CommandLineRunner testRepo(StudenCacheRepository studentCacheRepository) {
    return args -> {
      StudenCacheModel studentCacheModel = new StudenCacheModel();    
      StudentInfo si = new StudentInfo("bsb");
      Student st = new Student(Map.of("bsb", si));
      studentCacheModel.setStudent(st); //student model set
      studentCacheRepository.save(studentCacheModel);
      
      studentCacheRepository.findAll().forEach(System.out::println);
    };
  }

    public static void main(String[] args) {
        SpringApplication.run(SodemoApplication.class, args);
    }

}

Running it you get the console output:

[2m2022-03-09 10:07:19.087[0;39m [32m INFO[0;39m [35m19669[0;39m [2m---[0;39m [2m[           main][0;39m [36mcom.example.sodemo.SodemoApplication    [0;39m [2m:[0;39m Started SodemoApplication in 1.134 seconds (JVM running for 1.734)
StudenCacheModel(userId=19028f97-3e10-4776-8777-4a8cee224baf, student=com.example.sodemo.Student@24e5389c)

And on the Redis CLI you can see:

127.0.0.1:6379> keys *
1) "StudentCache"
2) "StudentCache:19028f97-3e10-4776-8777-4a8cee224baf"
127.0.0.1:6379> TYPE "StudentCache:19028f97-3e10-4776-8777-4a8cee224baf"
hash
127.0.0.1:6379> HGETALL "StudentCache:19028f97-3e10-4776-8777-4a8cee224baf"
1) "_class"
2) "com.example.sodemo.StudenCacheModel"
3) "student.studentData.[bsb].stName"
4) "bsb"
5) "userId"
6) "19028f97-3e10-4776-8777-4a8cee224baf"
127.0.0.1:6379>

I've created a repo with the full running example at https://github.com/bsbodden/sodemo

  • BTW Spring Data Redis uses Lettuce by default unless you configure it to use Jedis; both drivers can be used to save @RedisHash annotated entities

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 BSB