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

相关推荐
半新半旧9 小时前
python 整合使用 Redis
redis·python·bootstrap
daixin884811 小时前
什么是缓存雪崩?缓存击穿?缓存穿透?分别如何解决?什么是缓存预热?
java·开发语言·redis·缓存
daixin884814 小时前
Redis过期数据的删除策略是什么?有哪些?
数据库·redis·缓存
幻灭行度17 小时前
通过redis_exporter监控redis cluster
数据库·redis·缓存
冷崖20 小时前
Redis缓存策略以及bigkey的学习(九)
redis·学习·缓存
chen1108____2 天前
用 Docker 一键部署 Flask + Redis 微服务
redis·docker·flask
失散132 天前
大型微服务项目:听书——10 缓存+分布式锁优化根据专辑id查询专辑详情接口
redis·分布式·缓存·微服务
Aeside12 天前
Redis的线程模型
redis
white camel2 天前
分布式方案 一 分布式锁的四大实现方式
redis·分布式·zookeeper·分布式锁
大佐不会说日语~2 天前
Redis高可用架构演进面试笔记
redis·面试·架构