为什么要写RedisUtil这个类

引言:

不知道大家有没有这个疑惑,反正我是有,我学习就有一股打破砂锅问到底的劲儿,明明调用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 没有直接提供的实用功能:

  • 分布式锁 :如你示例中的 tryLockreleaseLock 方法

  • 缓存区域管理 :如 getRegionKeysdeleteRegion 方法

  • 业务特定操作:根据项目需求添加特定操作

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 这样的工具类能带来更好的可维护性、一致性和开发效率。

相关推荐
熊文豪8 小时前
openEuler 云原生实战:部署高性能 Redis 集群与压测分析
数据库·redis·云原生·openeuler
xrkhy12 小时前
canal1.1.8+mysql8.0+jdk17+redis的使用
android·redis·adb
MuYiLuck18 小时前
redis持久化与集群
java·数据库·redis
埃泽漫笔18 小时前
Redis性能优化避坑指南
redis
升鲜宝供应链及收银系统源代码服务19 小时前
升鲜宝生鲜配送供应链管理系统--- 《多语言商品查询优化方案(Redis + 翻译表 + 模糊匹配)》
java·数据库·redis·bootstrap·供应链系统·生鲜配送·生鲜配送源代码
JH307319 小时前
Redis 中被忽视的“键过期策略”与内存回收机制
数据库·redis·缓存
Microsoft Word19 小时前
Redis常见面试题
数据库·redis·缓存
bing.shao19 小时前
mongodb与redis在聊天场景中的选择
数据库·redis·mongodb
dudke19 小时前
c#实现redis的调用与基础类
数据库·redis·缓存
苦学编程的谢19 小时前
Redis_7_hash
数据库·redis·哈希算法