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

相关推荐
Hello.Reader4 小时前
Redis 延迟排查与优化全攻略
数据库·redis·缓存
东窗西篱梦11 小时前
Redis集群部署指南:高可用与分布式实践
数据库·redis·分布式
半新半旧12 小时前
Redis集群和 zookeeper 实现分布式锁的优势和劣势
redis·分布式·zookeeper
@ chen14 小时前
Redis事务机制
数据库·redis
静若繁花_jingjing16 小时前
Redis线程模型
java·数据库·redis
在肯德基吃麻辣烫16 小时前
《Redis》缓存与分布式锁
redis·分布式·缓存
先睡1 天前
Redis的缓存击穿和缓存雪崩
redis·spring·缓存
weixin_446122461 天前
JAVA内存区域划分
java·开发语言·redis
TT哇1 天前
JavaEE==网站开发
java·redis·java-ee
qq_392397121 天前
Redis常用操作
数据库·redis·wpf