Preface
Spring encapsulates a relatively powerful Template for easy use in the use of Redis; I used redis in the Spring ecosystem before, but directly used Jedis for corresponding interactions. Now let's take a look at how RedisTemplate is implemented and whether it is more convenient to use.
Jedis is still used for connection pool management, so in addition to introducing spring-data-redis, plus jedis dependencies, add the pom file
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.8.4.RELEASE</version></dependency><dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version></dependency>
If you need to specify serialization-related parameters, you can also introduce jackson. This article is simple entry-level, so you won't add this
Prepare redis-related configuration parameters, common ones include host, port, password, timeout.... The following is a simple configuration and gives the corresponding meanings.
redis.hostName=127.0.0.1redis.port=6379redis.password=https://blog.hhui.top# Connection timeout redis.timeout=10000#maximum idle number redis.maxIdle=300#controls how many jedis instances can be allocated for a pool to replace the above redis.maxActive. If it is jedis 2.4, use this property to redis.maxTotal=1000#maximum connection establishment waiting time. If this time exceeds this time, an exception will be received. Set to -1 means no limit. redis.maxWaitMillis=1000#The minimum idle time of connection is default 18000000ms (30 minutes) redis.minEvictableIdleTimeMillis=300000#The maximum number of connections is released each time, default 3redis.numTestsPerEvictionRun=1024#The time interval for evicting scan (ms) If it is negative, the evicting thread is not run. Default -1redis.timeBetweenEvictionRunsMillis=30000# Whether to check before removing the connection from the pool, if the verification fails, remove the connection from the pool and try to remove another redis.testOnBorrow=true#Check validity when idle, default falsedis.testWhileIdle=true
illustrate
Please remember to set the redis password, especially when allowing remote access. If you do not have a password, the default port number will be easily scanned and injected into the script, and then start mining for people (personal experience...)
According to the general idea, first, you have to load the above configuration, create a redis connection pool, then instantiate the RedisTemplate object, and finally hold this strength to start various read and write operations
Use JavaConfig to configure, mainly two beans, read the configuration file to set various parameters, and the expected RedisTemplate
@Configuration@PropertySource("classpath:redis.properties")public class RedisConfig extends JCacheConfigurerSupport { @Autowired private Environment environment; @Bean public RedisConnectionFactory redisConnectionFactory() { JedisConnectionFactory fac = new JedisConnectionFactory(); fac.setHostName(environment.getProperty("redis.hostName")); fac.setPort(Integer.parseInt(environment.getProperty("redis.port"))); fac.setPassword(environment.getProperty("redis.password")); fac.setTimeout(Integer.parseInt(environment.getProperty("redis.timeout"))); fac.getPoolConfig().setMaxIdle(Integer.parseInt(environment.getProperty("redis.maxIdle"))); fac.getPoolConfig().setMaxTotal(Integer.parseInt(environment.getProperty("redis.maxTotal"))); fac.getPoolConfig().setMaxWaitMillis(Integer.parseInt(environment.getProperty("redis.maxWaitMillis"))); fac.getPoolConfig().setMinEvictableIdleTimeMillis( Integer.parseInt(environment.getProperty("redis.minEvictableIdleTimeMillis"))); fac.getPoolConfig().setMinEvictableIdleTimeMillis"))); fac.getPoolConfig().setMinEvictableIdleTimeMillis( Integer.parseInt(environment.getProperty("redis.minEvictableIdleTimeMillis"))); fac.getPoolConfig() .setNumTestsPerEvictionRun(Integer.parseInt(environment.getProperty("redis.numTestsPerEvictionRun"))); fac.getPoolConfig().setTimeBetweenEvictionRunsMillis( Integer.parseInt(environment.getProperty("redis.timeBetweenEvictionRunsMillis"))); fac.getPoolConfig().setTestOnBorrow(Boolean.parseBoolean(environment.getProperty("redis.testOnBorrow"))); fac.getPoolConfig().setTestWhileIdle(Boolean.parseBoolean(environment.getProperty("redis.testWhileIdle"))); return fac; } @Bean public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, String> redis = new RedisTemplate<>(); redis.setConnectionFactory(redisConnectionFactory); redis.afterPropertiesSet(); return redis; }} @RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = {RedisConfig.class})public class RedisTest { @Autowired private RedisTemplate<String, String> redisTemplate; @Test public void testRedisObj() { Map<String, Object> properties = new HashMap<>(); properties.put("123", "hello"); properties.put("abc", 456); redisTemplate.opsForHash().putAll("hash", properties); Map<Object, Object> ans = redisTemplate.opsForHash().entries("hash"); System.out.println("ans: " + ans); }}After execution, the output is as follows
ans: {123=hello, abc=456}Judging from the above configuration and implementation, it is very simple. There is basically no circle around, but when you connect it with redis-cli, you cannot query the content of the hash key
127.0.0.1:6379> get hash(nil)127.0.0.1:6379> keys *1) "/xac/xed/x00/x05t/x00/x04hash"
There is no problem using the code to check it out. I directly connected the console and found that this key is different from what we expected, with some prefixes, why?
In order to solve the above problem, I can only debug it and see what caused it.
Corresponding source code location:
// org.springframework.data.redis.core.AbstractOperations#rawKeybyte[] rawKey(Object key) { Assert.notNull(key, "non null key required"); return this.keySerializer() == null && key instanceof byte[] ? (byte[])((byte[])key) : this.keySerializer().serialize(key);}You can see that this key is not the key.getBytes() we expected, but this.keySerializer().serialize(key) is called. The result of debugging is that the default Serializer is JdkSerializationRedisSerializer.
Then follow the clues and go deeper step by step, the link is as follows
// org.springframework.core.serializer.serializingConverter#convert// org.springframework.core.serializer.DefaultSerializer#serializepublic class DefaultSerializer implements Serializer<Object> { public DefaultSerializer() { } public void serialize(Object object, OutputStream outputStream) throws IOException { if (!(object instanceof Serializable)) { throw new IllegalArgumentException(this.getClass().getSimpleName() + " requires a Serializable payload but received an object of type [" + object.getClass().getName() + "]"); } else { ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); objectOutputStream.writeObject(object); objectOutputStream.flush(); } }}So the specific implementation is very clear, which is ObjectOutputStream. This thing is the most primitive serialized deserial streaming tool in Java. It will contain type information, so it will be equipped with the prefix.
So to solve this problem, it is clearer. Replace the native JdkSerializationRedisSerializer and change it to String, which just provides a StringRedisSerializer, so at the configuration of RedisTemplate, slightly modify it
@Beanpublic RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, String> redis = new RedisTemplate<>(); redis.setConnectionFactory(redisConnectionFactory); // Set the default serialization method of redis String/Value StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); redis.setKeySerializer(stringRedisSerializer); redis.setValueSerializer(stringRedisSerializer); redis.setHashKeySerializer(stringRedisSerializer); redis.setHashValueSerializer(stringRedisSerializer); redis.afterPropertiesSet(); return redis;}Execute again, and the embarrassing thing happened, the exception was thrown, and the type conversion failed
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at org.springframework.data.redis.serializer.StringRedisSerializer.serialize(StringRedisSerializer.java:33) at org.springframework.data.redis.core.AbstractOperations.rawHashValue(AbstractOperations.java:171) at org.springframework.data.redis.core.DefaultHashOperations.putAll(DefaultHashOperations.java:129) ...
Looking at the previous test case, the value in the map has integer, and the parameter received by StringRedisSerializer must be String, so don't use this, and you will still write a compatible one.
public class DefaultStrSerializer implements RedisSerializer<Object> { private final Charset charset; public DefaultStrSerializer() { this(Charset.forName("UTF8")); } public DefaultStrSerializer(Charset charset) { Assert.notNull(charset, "Charset must not be null!"); this.charset = charset; } @Override public byte[] serialize(Object o) throws SerializationException { return o == null ? null : String.valueOf(o).getBytes(charset); } @Override public Object deserialize(byte[] bytes) throws SerializationException { return bytes == null ? null : new String(bytes, charset); }}Then you can start having fun and test after execution
keys *1) "/xac/xed/x00/x05t/x00/x04hash"2) "hash"127.0.0.1:6379> hgetAll hash1) "123"2) "hello"3) "abc"4) "456"
Let's briefly see the usage posture of RedisTemplate, and read and encapsulate the calling method used by opsForXXX for different data structures (String, List, ZSet, Hash).
// hash data structure operation org.springframework.data.redis.core.RedisTemplate#opsForHash// listorg.springframework.data.redis.core.RedisTemplate#opsForList// stringorg.springframework.data.redis.core.RedisTemplate#opsForValue// setorg.springframework.data.redis.core.RedisTemplate#opsForZSet
In addition to the above usage method, another common one is to use execute directly. A simple case is as follows
@Testpublic void testRedis() { String key = "hello"; String value = "world"; redisTemplate.execute((RedisCallback<Void>) con -> { con.set(key.getBytes(), value.getBytes()); return null; }); String asn = redisTemplate.execute((RedisCallback<String>) con -> new String(con.get(key.getBytes()))); System.out.println(asn); String hkey = "hKey"; redisTemplate.execute((RedisCallback<Void>) con -> { con.hSet(hkey.getBytes(), "23".getBytes(), "what".getBytes()); return null; }); Map<byte[], byte[]> map = redisTemplate.execute((RedisCallback<Map<byte[], byte[]>) con -> con.hGetAll(hkey.getBytes())); for (Map.Entry<byte[], byte[]>> entry : map.entrySet()) { System.out.println("key: " + new String(entry.getKey()) + " | value: " + new String(entry.getValue())); }}The output result is as follows
world
key: 23 | value: what
A question that can be thought of naturally is what is the difference between the above two methods?
The underlying layer of opsForXXX is done by calling execute. It mainly encapsulates some usage postures and defines serialization, which makes it easier and more convenient to use. In this way, the small trumpet is that it needs to create a new DefaultXXXOperations object every time, and it takes one more step. Based on this, will it bring additional performance and memory overhead? I haven't tested it, but I personally feel that the amount is small, there should be no obvious impact; and when the qps is very high, the help this convenient optimization can bring is probably not much.
study-demo/spring-redis
The above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support to Wulin.com.