Use redis to get data and transfer it to JSON to solve the problem of dynamic generic parameter transfer

Time:2020-10-16

Scene:

There are two roles in the project that require different login permissions. Redis is used as the user login information cache database. Code is a method to obtain the corresponding data according to different user entity types. The user entities are: sessionentity < user1 >, sessionentity < user2 >. JSON uses fastjson.

First of all, this paper expounds the problems encountered

1. After the data obtained by redis is serialized, it is transferred to JSON, and it often prompts conversion exceptions (not every time, but often).

2. I don’t want every user to write a redis operation method (it looks like Tai low).

solve:

1. After the data obtained by redis is serialized, it is transferred to JSON, and a conversion exception is often prompted:

First of all, there are two ways to obtain redis.

1)

redisTemplate.opsForValue().get(key);

2)


SessionEntity result = redisTemplate.execute(new RedisCallback<SessionEntity>() {
   public SessionEntity doInRedis(RedisConnection connection)
     throws DataAccessException {
    RedisSerializer<String> serializer = getRedisSerializer();
    byte[] key = serializer.serialize(s);
    byte[] value = connection.get(key);
    if (value == null) {
     return null;
    }
    String json = serializer.deserialize(value);
    return JSONObject.parseObject(json,SessionEntity.class);
   }
  });

Obviously, the first method is relatively simple. Check the source code and find that the first method is called by the underlying layer redisTemplate.execute Method, so it should be regarded as a kind of encapsulation. We’ve been using the second approach. (the first method has been tried, and the JSON strong transition exception will also appear). There is a JSON exception here, which is suspected to be related to generics. The generic deserialization type is specified manually here.

After revision:


SessionEntity result = redisTemplate.execute(new RedisCallback<SessionEntity>() {
   public SessionEntity doInRedis(RedisConnection connection)
     throws DataAccessException {
    RedisSerializer<String> serializer = getRedisSerializer();
    byte[] key = serializer.serialize(s);
    byte[] value = connection.get(key);
    if (value == null) {
     return null;
    }
    String json = serializer.deserialize(value);
    return JSONObject.parseObject(json, new TypeReference<SessionEntity<User>>(){});
   }
  });

Perfect ~, really solved the JSON strong transition exception.

Then there is a problem. For the typereference here, you need to specify an explicit entity type manually. Try to add a generic type:


SessionEntity<T> result = redisTemplate.execute(new RedisCallback<SessionEntity<T>>() {
   public SessionEntity<T> doInRedis(RedisConnection connection)
     throws DataAccessException {
    RedisSerializer<String> serializer = getRedisSerializer();
    byte[] key = serializer.serialize(s);
    byte[] value = connection.get(key);
    if (value == null) {
     return null;
    }
    String json = serializer.deserialize(value);
    return JSONObject.parseObject(json, new TypeReference<SessionEntity<T>>(){});
   }
  });

It seems that there is no problem, and generics are recognized. But it still can’t pass.

2. Do not want each user to write a redis operation method:

As mentioned above, even if you add generics, you still can’t pass. After many attempts, you can still do this. Baidu once said that typereference was used to solve this problem, but it did not mention the problem of dynamic generics. I happened to see an article saying fastjson doesn’t support it, so I tried to replace it with Jackson.

Code after replacement:


SessionEntity<T> result = redisTemplate.execute(new RedisCallback<SessionEntity<T>>() {
   public SessionEntity<T> doInRedis(RedisConnection connection)
     throws DataAccessException {
    RedisSerializer<String> serializer = getRedisSerializer();
    byte[] key = serializer.serialize(s);
    byte[] value = connection.get(key);
    if (value == null) {
     return null;
    }
    String json = serializer.deserialize(value);
    ObjectMapper om = new ObjectMapper();
    JavaType javatype = om.getTypeFactory().constructParametricType(SessionEntity.class, clazz);
    try {
     return om.readValue(json, javatype);
    } catch (IOException e) {
     e.printStackTrace();
    }
    return null;
//    return JSONObject.parseObject(json, new TypeReference<SessionEntity<T>>(){});
   }
  });

Jackson’s objectmapper is used here. The objectmapper class is the main class of the Jackson library. It provides some functionality that will be converted into Java objects to match the JSON structure, and vice versa. It uses jsonparser and jsongenerator instances to implement the actual read / write of JSON. (copied) found the problem solved.

The abstract methods provided are:

public <T> SessionEntity<T> get(final String s, Class<T> clazz);

The call mode is as follows:

sessionEntityDao.get (key, user1. Class); and sessionEntityDao.get (key, User2.class);

Since the jackson-databind-2.6.0 library is used here, the method of constructparametrictype in this version is going to be out of date and will be used in higher versions

constructParametrizedType

Replace. I haven’t tried this yet. I’ll play again when I’m free.

Here the problem has been solved, just take a note for your convenience in the future. Here only provides one of the solutions encountered in the project, I believe there should be other ways to solve. If there are any mistakes, please point out and forgive me.

Supplementary knowledge:Redis crawling pit — redis implements universal serializer & solving the problem of redis deserialization failure

Redis’s default serialization is jdkserializationredisserializer


public void afterPropertiesSet() {
 super.afterPropertiesSet();
 boolean defaultUsed = false;
 if (this.defaultSerializer == null) {
  this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
 }

 if (this.enableDefaultSerializer) {
  if (this.keySerializer == null) {
   this.keySerializer = this.defaultSerializer;
   defaultUsed = true;
  }

  if (this.valueSerializer == null) {
   this.valueSerializer = this.defaultSerializer;
   defaultUsed = true;
  }

  if (this.hashKeySerializer == null) {
   this.hashKeySerializer = this.defaultSerializer;
   defaultUsed = true;
  }

  if (this.hashValueSerializer == null) {
   this.hashValueSerializer = this.defaultSerializer;
   defaultUsed = true;
  }
 }

 if (this.enableDefaultSerializer && defaultUsed) {
  Assert.notNull(this.defaultSerializer, "default serializer null and not all serializers initialized");
 }

 if (this.scriptExecutor == null) {
  this.scriptExecutor = new DefaultScriptExecutor(this);
 }

 this.initialized = true;
}

This is because our project needs to change the default sequence policy to Jackson 2jsonredisserializer to serialize it into a visual * * JSON * * statement

First of all, we define our own redistemplate. Here, we do not define a serializer for each class, but we define a unified serializer. So the generic type here is < string, Object >, key. We use stringredisserializer, and value uses Jackson 2jsonredisserializer

The comment code is the code that fixes the deserialization bug

@Bean
 public RedisTemplate<String, Object> objectRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
  RedisTemplate<String, Object> template = new RedisTemplate();
  Jackson2JsonRedisSerializer<Object> jsonSerial = new 		 Jackson2JsonRedisSerializer(Object.class);
//// fix the deserialization bug
//  ObjectMapper om = new ObjectMapper();
//  om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//  om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
//  jsonSerial.setObjectMapper(om);
  template.setDefaultSerializer(jsonSerial);
  template.setKeySerializer(RedisSerializer.string());
  template.setConnectionFactory(redisConnectionFactory);
  template.afterPropertiesSet();
  return template;
 }

The test code is


@Test
public void redisSaveObject(){

 UserDO ob = new UserDO();
 ob.setName("name");
 ob.setCity("city");
 objectRedisTemplate.opsForValue().set("ob1",ob);
 Object ob2 = objectRedisTemplate.opsForValue().get("ob1");
 UserDO ob1 = (UserDO)ob2;
 System.out.println(ob1);

}

The running result is


java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.hcy.core.model.UserDO
at com.hcy.core.redistest.RedisTest.redisSaveObject(RedisTest.java:42)
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.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at 

It’s obviously an object cast error

This is because of the generics. Redis serializes it as an object during serialization. Therefore, it is OK to deserialize it as an object. However, because this object has no type definition, it cannot be forced.

terms of settlement

Modify the serializer Jackson 2jsonredisserializer in redistemplate and add the following code, as noted above

//Fix deserialization bug
  ObjectMapper om = new ObjectMapper();
  om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  jsonSerial.setObjectMapper(om);

Through objectMapper.enableDefaultTyping () method settings

Even with Object.class As jcom.fasterxml.jackson . databind.JavaType You can also implement serialization and deserialization of the corresponding types

Benefit: define only one serializer (generic)

Here we also do a test to see if the generated value is different from that without modifying objectmapper or modifying objectmapper

Operation results:

ob1: [“com.hcy.core.model.UserDO”,{“userid”:null,“openid”:null,“name”:“name”,“city”:“city”}]

ob2: {“userid”:null,“openid”:null,“name”:“name”,“city”:“city”}

The result here is obvious!!!

Hope to help you!!!

The above article uses redis to get data and transfer it to JSON to solve the problem of dynamic generic parameter transfer, which is all the content shared by the editor. I hope it can give you a reference, and I hope you can support developeppaer more.

Recommended Today

Rust programming video tutorial (Advanced) – 024_ 3 syntax of all modes 3

Video address Headline address:https://www.ixigua.com/i677586170644791348…Station B address:https://www.bilibili.com/video/av81202308/ Source address GitHub address:https://github.com/anonymousGiga/learn_rus… Explanation content 1. Ignore values in mode(1) Use_ Ignore entire valueexample: fn foo(_: i32, y: i32) { println!(“This code only uses the y parameter: {}”, y); } fn main() { foo(3, 4); } Note: placeholders are used for parameters in the function, mainly when implementing […]