引言:
不知道大家有没有这个疑惑,反正我是有,我学习就有一股打破砂锅问到底的劲儿,明明调用RedisTemplate不就可以和redis交互了吗,为什么还要写RedisUtil这个工具类,是我理解错了吗?
先写个工具类:
@Component
public class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================
/**
* 指定缓存失效时间
*/
public boolean expire(String key, long time, TimeUnit unit) {
try {
if (time > 0) {
redisTemplate
.expire(key, time, unit);
}
return true;
} catch (Exception e) {
e
.printStackTrace();
return false;
}
}
/**
* 根据key获取过期时间
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e
.printStackTrace();
return false;
}
}
/**
* 删除缓存
*/
public void delete(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate
.delete(key[0]);
} else {
redisTemplate
.delete(java.util.Arrays.asList(key));
}
}
}
// ============================String=============================
/**
* 普通缓存获取
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
*/
public boolean set(String key, Object value) {
try {
redisTemplate
.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e
.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
*/
public boolean set(String key, Object value, long time, TimeUnit unit) {
try {
if (time > 0) {
redisTemplate
.opsForValue().set(key, value, time, unit);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e
.printStackTrace();
return false;
}
}
/**
* 递增
*/
public long increment(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
// ================================分布式锁================================
/**
* 获取分布式锁
*/
public Boolean tryLock(String key, String value, long timeout, TimeUnit unit) {
String script = "return redis.call('set', KEYS[1], ARGV[1], 'NX', 'EX', ARGV[2])";
Object result = redisTemplate.execute((org.springframework.data.redis.connection.RedisConnection connection) ->
connection
.eval(script.getBytes(),
org.springframework.data.redis.connection.ReturnType.BOOLEAN,
1,
key
.getBytes(),
value
.getBytes(),
String.valueOf(unit.toSeconds(timeout)).getBytes()));
return result != null && (Boolean) result;
}
/**
* 释放分布式锁
*/
public Boolean releaseLock(String key, String value) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = redisTemplate.execute((org.springframework.data.redis.connection.RedisConnection connection) ->
connection
.eval(script.getBytes(),
org.springframework.data.redis.connection.ReturnType.INTEGER,
1,
key
.getBytes(),
value
.getBytes()));
return result != null && (Long) result > 0;
}
// ================================缓存区域================================
/**
* 获取缓存区域下的所有key
*/
public java.util.Set<String> getRegionKeys(String region) {
return redisTemplate.keys(region + ":*");
}
/**
* 删除缓存区域
*/
public void deleteRegion(String region) {
java.util.Set<String> keys = getRegionKeys(region);
if (keys != null && !keys.isEmpty()) {
redisTemplate
.delete(keys);
}
}
}
确实直接使用 RedisTemplate
就可以完成所有 Redis 操作,但在实际项目中创建 RedisUtil
这样的工具类有几个重要的原因:
1. 封装常用操作
RedisUtil
的主要目的是对 RedisTemplate
进行二次封装,提供更简洁、更符合业务需求的 API:
-
简化调用:将常用的多步操作封装成单方法调用
-
统一处理:集中处理异常、日志、默认值等
-
减少重复代码:避免在各个业务类中重复编写相同的 Redis 操作代码
2. 异常统一处理
在你的 RedisUtil
示例中可以看到,它对异常进行了统一处理:
try {
// Redis操作
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
这种处理方式:
-
避免了业务代码中到处写 try-catch
-
统一了异常处理逻辑
-
提供了更友好的返回值(如返回 boolean 而不是抛出异常)
3. 扩展功能
RedisUtil
可以添加 RedisTemplate
没有直接提供的实用功能:
-
分布式锁 :如你示例中的
tryLock
和releaseLock
方法 -
缓存区域管理 :如
getRegionKeys
和deleteRegion
方法 -
业务特定操作:根据项目需求添加特定操作
4. 统一业务逻辑
通过工具类可以:
-
统一键的命名规则(如自动添加前缀)
-
统一序列化方式
-
统一过期时间设置策略
-
统一缓存空值处理逻辑
5. 代码可维护性
使用工具类的好处:
-
减少耦合 :业务代码不直接依赖
RedisTemplate
-
易于修改:Redis 操作逻辑变更只需修改工具类
-
便于测试:可以针对工具类单独测试 Redis 操作
对代码的改进
提供的 RedisUtil
是一个很好的起点,但在生产环境中还可以进一步优化:
1. 使用泛型改进类型安全
public <T> T get(String key, Class<T> clazz) {
Object value = redisTemplate.opsForValue().get(key);
return clazz.cast(value);
}
2. 添加更丰富的操作方法
// 批量操作
public <T> void multiSet(Map<String, T> map) {
redisTemplate.opsForValue().multiSet(map);
}
// 原子操作
public <T> T getAndSet(String key, T value) {
return (T) redisTemplate.opsForValue().getAndSet(key, value);
}
3. 使用 Spring 的缓存抽象
@Cacheable(value = "users", key = "#userId")
public User getUser(String userId) {
// 数据库查询
}
@CacheEvict(value = "users", key = "#user.id")
public void updateUser(User user) {
// 更新逻辑
}
4. 分布式锁改进
public boolean tryLock(String lockKey, String requestId, long expireTime) {
return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
byte[] key = serializer.serialize(lockKey);
byte[] value = serializer.serialize(requestId);
Boolean result = connection.set(key, value,
Expiration.from(expireTime, TimeUnit.SECONDS),
RedisStringCommands.SetOption.SET_IF_ABSENT);
return result != null && result;
});
}
总结
直接使用 RedisTemplate | 使用 RedisUtil |
---|---|
需要记住各种 opsForXxx() 方法 | 提供业务语义明确的方法 |
需要手动处理异常 | 异常已统一处理 |
散落在各业务类中 | 集中管理 Redis 操作 |
灵活性高 | 一致性高 |
适合简单场景 | 适合中大型项目 |
所以我们的理解没有错,直接使用 RedisTemplate
确实可以完成所有操作,但在实际项目中,使用 RedisUtil
这样的工具类能带来更好的可维护性、一致性和开发效率。