In a recent project to integrate Redis with SpringBoot, a hole was stepped in: null data was retrieved from Redis, but the corresponding data actually exists in Redis. What causes this pit?
This article will take you from SpringBoot integration Redis, stepped on the pit and automatic configuration source analysis to learn how to correctly use Redis in SpringBoot.
SpringBoot integrate Redis
In the SpringBoot project, you only need to introduce the Redis corresponding starter into the POM file and configure Redis connection information to use it. Pom dependency introduction:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Copy the code
Corresponding application profile configuration:
Spring: redis: host: 127.0.0.1 Port: 6379 database: 1 password: 123456 timeout: 5000Copy the code
The integration of Redis is completed through the above two configurations. Here is the detailed usage, which is presented in the form of unit tests.
@SpringBootTest @RunWith(SpringRunner.class) public class TokenTest { @Autowired private RedisTemplate redisTemplate; @Test public void getValue() { Object value = redisTemplate.opsForValue().get("1"); System.out.println("value:" + value); }}Copy the code
As you can see, after injecting RedisTemplate directly via @AutoWired, you can call the method operations provided by RedisTemplate. The RedisTemplate provides a variety of Redis operation methods. For details, please refer to the corresponding API, which will not be extended here.
Potholes encountered in the project
Going back to the original problem: fetching data from Redis is null, but there is actually data in Redis.
In fact, the appearance of the problem is very strange, but the reason of the problem is very simple, which is caused by the different RedisTemplate used for data storage and data retrieval in Redis.
In SpringBoot, the auto-configuration class for Redis initializes two redistemplates by default.
@Configuration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
Copy the code
You can see that the two beans of the RedisTemplate are initialized in the RedisAutoConfiguration. The first Bean is of type RedisTemplate<Object, Object>, the name of the Bean is RedisTemplate, and it is initialized when the corresponding Bean name does not exist in the container. The second Bean type is StringRedisTemplate, and the Bean name is StringRedisTemplate, which inherits from RedisTemplate<String, String>.
So one Bean is for objects and one Bean is for strings.
The cause of the pit is that RedisTemplate<Object, Object> is injected when set and StringRedisTemplate is injected when fetched. Shouldn’t such an obvious mistake be easy to spot?
If it is directly caused by the two types of inconsistency, it is really easy to check, just look at the injected RedisTemplate.
But the problem is hard to troubleshoot because of another factor: @resource and @AutoWired injection problems.
By default, @Resource is injected by bean name and then type if not found, while @AutoWired is injected by type by default. The project uses @Resource injection to obtain data, as follows:
@Resource
private RedisTemplate<String, String> redisTemplate;
Copy the code
While the store is stored using @autowired injected:
@Autowired
private RedisTemplate<String, String> redisTemplate;
Copy the code
The above two forms of injection do not seem to be a problem when there is only a single instance, either an error is reported directly or the injection succeeds. But when there are two redistemplates like the one above, the problem becomes hidden.
When using @AutoWired, RedisTemplate<String, String> beans are injected directly by type injection, since they are all of type String.
When @resource injection is used, matching by name is used by default. RedisTemplate is of type redisTemplate <Object, Object>. As a result, different redistemplates were injected at both locations, which resulted in the problem of not getting a value when fetching.
Once you get to the root of the problem, it’s much easier to fix it: Option one is to change the injection of @Resource to @AutoWired. Option 2: Change the name of the bean injected with @Resource from redisTemplate to stringRedisTemplate. Of course, there are other solutions based on specific business scenarios.
summary
SpringBoot integration with Redis is actually quite simple. SpringBoot already does most of the work for us, but the default initialization of two Redistemplates, plus the difference between the @AutoWired and @Resource annotations, adds complexity. Therefore, try to keep the specification consistent throughout the use process, and the Ali Java Development Manual recommends using the @Resource annotation. At the same time, of course, the use of source code, annotations and other in-depth study and understanding.
Personal public account [Program New Vision], a platform for simultaneous promotion of hard technology and soft power. Author of “Inside Spring Boo Technology: Principles of Architectural Design and Implementation” and co-author of “Inside Ethereum Smart Contract Development”. Mainly engaged in the tripartite payment industry.