In the previous article, we explored Spring Boot integration with Redis. This article focuses on implementing Redis caching in MyBatis operations. We'll examine four key annotations: @CachePut, @Cacheable, @CacheEvict, and @CacheConfig.
Fundamentals
@Cacheable
The @Cacheable annotation configures method-level caching, storing results based on method parameters.
| Parameter | Description | Example |
|---|---|---|
| value | Cache name defined in configuration, at least one required | @Cacheable(value="mycache") @Cacheable(value={"cache1","cache2"}) |
| key | Cache key, uses SpEL expressions. If omitted, defaults to all method parameters | @Cacheable(value="testcache",key="#username") |
| condition | SpEL expression returning true/false. Only caches when true | @Cacheable(value="testcache",condition="#username.langth()>2") |
@CachePut
@CachePut also provides method-level caching, but unlike @Cacheable, it always executes the method and caches the return value. A common pitfall occurs when applying this annotation at the Mapper layer with void return types. Since @CachePut stores the method's return value, not null, attempting to deserialize stored values as domain objects will fail.
@CachePut(cacheNames="account",key = "#p0.accountId")
@Update("UPDATE account SET name=#{name},balance=#{balance} WHERE id=#{id}")
Account updateAccount(Account account);
</div>| Parameter | Description | Example |
|---|---|---|
| value | Cache name defined in configuration, at least one required | @CachePut(value="mycache") |
| key | Cache key using SpEL. Defaults to all method parameters if unspecified | @CachePut(value="testcache",key="#username") |
| condition | SpEL expression for conditional caching | @CachePut(value="testcache",condition="#username.length()>2") |
### @CacheEvict
@CacheEvict clears cached entries based on specified conditions.
| Parameter | Description | Example |
|---|---|---|
| value | Cache name, at least one required | @CacheEvict(value="mycache") |
| key | Cache key to evict | @CacheEvict(value="testcache",key="#username") |
| condition | SpEL expression for conditional eviction | @CacheEvict(value="testcache",condition="#username.length()>2") |
| allEntries | If true, clears entire cache after method execution. Default: false | @CacheEvict(value="testcache",allEntries=trrue) |
| beforeInvocation | If true, evicts before method execution. Default: false (exception prevents eviction) | @CacheEvict(value="testcache",beforeInvocation=true) |
### @CacheConfig
When multiple methods share the same cache name with @Cacheable annotations, @CacheConfig provides class-level cache configuration to avoid repetition. Method-level cache names override class-level settings.
Implemantation Example
----------------------
The following example builds upon a previous Spring Boot demo project. Since MyBatis Mapper methods typically return void for updates, a service layer is introduced to handle caching properly.
### Service Layer
<div>```
package com.example.services;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import com.example.model.User;
import com.example.write.mapper.WriteUserMapper;
@Service
@CacheConfig(cacheNames="user")
public class UserServices {
@Autowired
private WriteUserMapper writeUserMapper;
public List<User> getAll() {
return writeUserMapper.getAll();
}
@Cacheable(key = "#p0")
public User getOne(String id) {
return writeUserMapper.getOne(id);
}
public void insert(User user) {
writeUserMapper.insert(user);
}
@CachePut(value="user", key = "#p0.id")
public User update(User user) {
writeUserMapper.update(user);
return user;
}
@CacheEvict(value="user", key ="#p0", allEntries=true)
public void delete(String id) {
writeUserMapper.delete(id);
}
}
import java.io.Serializable; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.ModelAndView; import com.example.model.User; import com.example.model.UserSexEnum; import com.example.read.mapper.ReadUserMapper; import com.example.services.UserServices; import com.example.write.mapper.WriteUserMapper;
@Controller @RequestMapping("/user") public class UserController {
@Autowired
private WriteUserMapper userMapperWrite;
@Autowired
private ReadUserMapper userMapperRead;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedisTemplate<String, Serializable> redisCacheTemplate;
@Autowired
private UserServices userServices;
@RequestMapping(value = "/alluser.do", method = RequestMethod.GET)
public String getAllUsers(Model model) {
List<User> users = userServices.getAll();
model.addAttribute("users", users);
return "userlist";
}
@RequestMapping(value = "/insert.do", method = RequestMethod.GET)
public String addUser(Model model) {
User user = new User();
user.setName("john");
user.setAge(30);
userServices.insert(user);
return "forward:/user/alluser.do";
}
@RequestMapping(value = "/getuserbyid.do/{id}", method = RequestMethod.GET)
public ModelAndView getUserById(@PathVariable("id") String id) {
System.out.println(id);
User user = userServices.getOne(id);
System.out.println(user.toString());
ModelAndView modelAndView = new ModelAndView("userlist");
modelAndView.addObject("user", user);
return modelAndView;
}
@RequestMapping(value = "/deleteuserbyid.do/{id}", method = RequestMethod.GET)
public String deleteUserById(@PathVariable("id") String id) {
userServices.delete(id);
return "forward:/user/alluser.do";
}
@RequestMapping(value = "/updateuserbyid.do/{id}", method = RequestMethod.GET)
public String updateUserById(@PathVariable("id") String id) {
User user = userServices.getOne(id);
System.out.println(user.toString());
user.setAge(31);
System.out.println(user.toString());
userServices.update(user);
System.out.println(user.toString());
return "forward:/user/alluser.do";
}
}
</div>### Testing the Implementation
First, access http://localhost:8080/user/getuserbyid.do/17 to cache a user via the getOne() method. Use redis-cli to verify that user::17 exists in Redis.
Next, update the user through http://localhost:8080/user/updateuserbyid.do/17. The age changes to 31 and the database updates accordingly. Multiple refreshes of this URL will execute successfully because @CachePut stores the returned User object. This differs from applying @CachePut directly on a Mapper with void return, which would store null and cause deserialization errors.
Finally, delete the user via http://localhost:8080/user/deleteuserbyid.do/17, which removes the entry from both Redis and the database.
### Component Scanning Configuration
Ensure the service package is included in component scanning by adding @ComponentScan with the appropriate base packages in your main application class:
<div>```
@ComponentScan(basePackages={"com.example.config","com.example.demo","com.example.services"})