Spring data redis returns null on deserializing List type - redis

I am using spring data redis and one of my entity has a list as below.
#RedisHash("person")
#Data
#Builder
public class Person implements Serializable {
#Id
private String name;
private List<Address> addressList;
}
public class Address implements Serializable {
private String postCode;
private String country;
}
The serialisation works fine and the address is stored as
HGETALL person:123456
"name"
"blabla"
"address[0].postCode"
"1111XX"
"address[1].country"
"IN"
but while getting the person back the List is always null ? could someone point out what I am doing wrong here.
My Redis configuration looks as below.
#Configuration
#EnableRedisRepositories
public class RedisConfiguration {
#Bean
public JedisConnectionFactory jedisConnectionFactory() {
return new JedisConnectionFactory();
}
#Bean
public RedisTemplate<String, String> redisTemplate() {
final RedisTemplate<String, String> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
mapper.findAndRegisterModules();
template.setKeySerializer(redisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.setConnectionFactory(jedisConnectionFactory());
return template;
}
}

I have some suspicion but can you share a bit more code snippet on how you use redisTemplate to save the data into redis?
But most likely its because you set up both Key and Values initialization of RedisTemplate
RedisTemplate<K, V> as <String, String>
Which is why even with Jackson2JsonSerializer it can't serialize and deserialize the List<Address> class properly.
A possible solution would be:
#Bean
public RedisTemplate<String, Person> redisTemplate(JedisConnectionFactory jedisConnectionFactory) {
RedisTemplate<String, Person> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(jedisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
And then you store the whole Person object into Redis
When you need it then you get the whole object out and implement your getter and setter.
Hope this helps

Related

How to get hash value using key on spring data redis?

On redis I have added key S000, and I want load hash object with that key.
I always get NULL from even I passed the correct key.
How can I do that, please find screenshot below for more detail.
Thank!
#Configuration
#EnableRedisRepositories
public class RedisConfig {
#Bean
public JedisConnectionFactory connectionFactory() {
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
configuration.setHostName("localhost");
configuration.setPort(9000);
return new JedisConnectionFactory(configuration);
}
#Bean
public RedisTemplate<String, User> redisTemplate() {
RedisTemplate<String, User> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setEnableTransactionSupport(true);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
#Repository
public class UserDaoImpl implements UserDao {
#Autowire
private final RedisTemplate redisTemplate;
public Object fetchUserById(String key) {
return redisTemplate.opsForHash().entries(key);
}
}

Spring WebSession Redis Exception

I want to store my WebSession in Redis. There is no problem at put operation, but it throws exception when retrieving stored record. Here is my example stack trace
Caused by:
com.fasterxml.jackson.databind.exc.MismatchedInputException:
Unexpected token (START_OBJECT), expected START_ARRAY: need JSON Array
to contain As.WRAPPER_ARRAY type information for class
java.lang.Object at [Source:
(byte[])"{"attributes":["org.springframework.session.web.server.session.SpringSessionWebSessionStore$SpringSessionMap",{}],"id":"2a5c3d9b-3557-4bd6-bca8-9e221c3a5b41","lastAccessTime":{"nano":800305900,"epochSecond":1605268779},"creationTime":{"nano":800305900,"epochSecond":1605268779},"expired":false,"maxIdleTime":{"seconds":5400,"nano":0,"negative":false,"zero":false,"units":["java.util.ImmutableCollections$List12",[["java.time.temporal.ChronoUnit","SECONDS"],["java.time.temporal.ChronoUnit","NANOS"]]]"[truncated
18 bytes]; line: 1, column: 1]
How could I solve this problem? I don't understand why it is happening? Thanks.
Here is my session service.
#Component
#RequiredArgsConstructor
#Slf4j
public class SessionMappingStorage {
private static final String USR_TO_SESSION___KEY = "USR_SESSION_MAP";
private final ReactiveHashOperations<String, String, Object> hashOps;
public Mono<Boolean> addSession(String username, WebSession session) {
return hashOps.put(USR_TO_SESSION___KEY, username, session);
}
public Mono<WebSession> getSessionByUserId(String username) {
return hashOps.get(USR_TO_SESSION___KEY, username).cast(WebSession.class);
}
}
Here is my redis configuration.
#Bean
public ReactiveRedisTemplate<String, String> reactiveRedisTemplate() {
RedisSerializer keySerializer, hashKeySerializer, hashValueSerializer;
keySerializer = hashKeySerializer = new StringRedisSerializer();
hashValueSerializer = new GenericJackson2JsonRedisSerializer(objectMapper());
RedisSerializationContext.RedisSerializationContextBuilder<String, String> builder =
RedisSerializationContext.newSerializationContext(keySerializer);
RedisSerializationContext<String, String> context =
builder.hashKey(hashKeySerializer).hashValue(hashValueSerializer).build();
return new ReactiveRedisTemplate<>(reactiveRedisConnectionFactory(), context);
}
#Bean
public ReactiveHashOperations<String, String, Object> hashOps() {
return reactiveRedisTemplate().opsForHash();
}
private ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
return mapper;
}

RabbitMQ not serialize message, error convert

I've seen some related questions here, but none worked for me, the rabbit will not serialize my message coming from another application.
Caused by: org.springframework.amqp.AmqpException: No method found for class [B
Below my configuration class to receive the messages.
#Configuration
public class RabbitConfiguration implements RabbitListenerConfigurer{
public final static String EXCHANGE_NAME = "wallet-accounts";
public final static String QUEUE_PAYMENT = "wallet-accounts.payment";
public final static String QUEUE_RECHARGE = "wallet-accounts.recharge";
#Bean
public List<Declarable> ds() {
return queues(QUEUE_PAYMENT, QUEUE_RECHARGE);
}
#Autowired
private ConnectionFactory rabbitConnectionFactory;
#Bean
public AmqpAdmin amqpAdmin() {
return new RabbitAdmin(rabbitConnectionFactory);
}
#Bean
public TopicExchange exchange() {
return new TopicExchange(EXCHANGE_NAME);
}
private List<Declarable> queues(String ... names){
List<Declarable> result = new ArrayList<>();
for (int i = 0; i < names.length; i++) {
result.add(makeQueue(names[i]));
result.add(makeBinding(names[i]));
}
return result;
}
private static Binding makeBinding(String queueName){
return new Binding(queueName, DestinationType.QUEUE, EXCHANGE_NAME, queueName, null);
}
private static Queue makeQueue(String name){
return new Queue(name);
}
#Bean
public MappingJackson2MessageConverter jackson2Converter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
return converter;
}
#Bean
public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setMessageConverter(jackson2Converter());
return factory;
}
#Override
public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
registrar.setMessageHandlerMethodFactory(myHandlerMethodFactory());
}
}
Using this other configuration, the error is almost the same:
Caused by: org.springframework.amqp.support.converter.MessageConversionException: failed to resolve class name. Class not found [br.com.beblue.wallet.payment.application.accounts.PaymentEntryCommand]
Configuration:
#Configuration
public class RabbitConfiguration {
public final static String EXCHANGE_NAME = "wallet-accounts";
public final static String QUEUE_PAYMENT = "wallet-accounts.payment";
public final static String QUEUE_RECHARGE = "wallet-accounts.recharge";
#Bean
public List<Declarable> ds() {
return queues(QUEUE_PAYMENT, QUEUE_RECHARGE);
}
#Autowired
private ConnectionFactory rabbitConnectionFactory;
#Bean
public AmqpAdmin amqpAdmin() {
return new RabbitAdmin(rabbitConnectionFactory);
}
#Bean
public TopicExchange exchange() {
return new TopicExchange(EXCHANGE_NAME);
}
#Bean
public MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
private List<Declarable> queues(String ... names){
List<Declarable> result = new ArrayList<>();
for (int i = 0; i < names.length; i++) {
result.add(makeQueue(names[i]));
result.add(makeBinding(names[i]));
}
return result;
}
private static Binding makeBinding(String queueName){
return new Binding(queueName, DestinationType.QUEUE, EXCHANGE_NAME, queueName, null);
}
private static Queue makeQueue(String name){
return new Queue(name);
}
}
Can anyone tell me what's wrong with these settings, or what's missing?
No method found for class [B
Means there is a default SimpleMessageConverter which can't convert your incoming application/json. It is just not aware of that content-type and just falls back to the byte[] to return.
Class not found [br.com.beblue.wallet.payment.application.accounts.PaymentEntryCommand]
Means that Jackson2JsonMessageConverter can't convert your application/json because the incoming __TypeId__ header, representing class of the content, cannot be found in the local classpath.
Well, definitely your configuration for the DefaultMessageHandlerMethodFactory does not make sense for the AMQP conversion. You should consider to use SimpleRabbitListenerContainerFactory bean definition and its setMessageConverter. And yes, consider to inject the proper org.springframework.amqp.support.converter.MessageConverter implementation.
https://docs.spring.io/spring-amqp/docs/1.7.3.RELEASE/reference/html/_reference.html#async-annotation-conversion
From the Spring Boot perspective there is SimpleRabbitListenerContainerFactoryConfigurer to configure on the matter:
https://docs.spring.io/spring-boot/docs/1.5.6.RELEASE/reference/htmlsingle/#boot-features-using-amqp-receiving

why does redis-cli get the value of an object set by programm always nil

i have used spring-data-redis and set the template as follow:
#Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(jedisConnectionFactory());
//template.setDefaultSerializer(new StringRedisSerializer());
return template;
}
#Autowired
private RedisTemplate<String, Object> redisTemplate;
private ValueOperations<String, Object> valueOps;
public static final String CATEGORY="QUOTE";
public void save(String key, String value) {
valueOps.set(key, value);
logger.info("redis get key={}, value={}", key, get(key));
}
when i run the code ,it can display the value as the String,
Aug 02, 2017 5:00:52 下午 com.web.service.QuoteRepo save
INFO: redis get key=QUOTE:932:324371:rem, value=4
when i go to redis client
$ ./redis-cli
127.0.0.1:6379>get QUOTE:932:324371:rem
(nil)
Watch out this code line :
private ValueOperations<String, Object> valueOps;
You should inject the template as a ValueOperations. You forgot something like this:
#resource(name="redisTemplate")
private ValueOperations<String, Object> valueOps;
Refer to this for more: https://docs.spring.io/spring-data/redis/docs/1.8.6.RELEASE/reference/html/
Section5.5 is helpful for you.

Deserialize JSON array into Map using Jackson in Java

I'm using fasterXML's Jackson (v2.3.3) library to deserialize and serialize a custom class. The class is defined as following:
public class Person {
private String name;
private Map<String, Person> children;
// lots of other fields of different types with no issues
}
the keys of map children are the name fields. I receive data in JSON with each person object structured as following (I have omitted the other fields):
{"name":"Bob", "children":[{"name":"Jimmmy"},{"name":"Judy"}]}
(Many Fields such as children are optional and aren't serialized when null)
I have been storing children in a List<Person> so far with no issues, but many new use cases need to have access to the set of names or to a specific Person using his name as key. This is why I have decided to store them using a Map.
After some research, I think the best way is to use Annotations #JsonDeserialize and #JsonSerialize with a JsonDeserializer and JsonSerializer as parameter respectively for the field children:
public class Person {
private String id;
#JsonSerialize(using=MySerializer.class)
#JsonDeserialize(using=MyDeserializer.class)
private Map<String, Person> friends;
// lots of other fields
}
My question is: Does such a JsonSerializer/JsonDeserializer exist and if not, how do I define one?
edit: I have started implementing a custom Deserializer, but I get this exception:
com.fasterxml.jackson.databind.JsonMappingException: Class has no default (no arg) constructor
which is weird because I have defined a default constructor. Here is my custom Deserializer:
public class MyDeserializer extends JsonDeserializer<Map<String, Person>> {
public MyDeserializer() {
}
#Override
public Map<String, Person> deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode personsNodeArray = jp.getCodec().readTree(jp);
Map<String, Person> newChildren = null;
if (personsNodeArray.isArray() && personsNodeArray.size() > 0) {
newChildren = new HashMap<String, Person>();
for (JsonNode personNode : personsNodeArray) {
String id = personNode.get("name").asText();
// jsonMapper is a default ObjectMapper
newChildren.put(id, jsonMapper.readValue(personNode.toString(), Person.class));
}
}
return newChildren;
}
}
You can also consider reading children information as a collection of persons with subsequent conversion into a map. You can define a setter method (or a constructor parameter) to accept a List<Person> and then put each element into the Map<String, Person> children field. That would avoid unnecessary complexity of custom serialisation.
Here is an example:
public class JacksonChildren {
public static final String JSON = "{\"name\":\"Bob\", \"children\":[{\"name\":\"Jimmmy\"}," +
"{\"name\":\"Judy\"}]}";
public static class Person {
public String name;
private Map<String, Person> children = new HashMap<>();
public void setChildren(final List<Person> children) {
for (Person p : children) {
this.children.put(p.name, p);
}
}
#Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", children=" + children +
'}';
}
}
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(JSON, Person.class));
}
}
Output:
Person{name='Bob', children={Judy=Person{name='Judy', children={}}, Jimmmy=Person{name='Jimmmy', children={}}}}