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

相关推荐
Armyyyyy丶1 天前
Redis底层实现原理之五大基础结构
数据结构·redis·缓存
PXM的算法星球1 天前
ZooKeeper vs Redis:分布式锁的实现与选型指南
redis·分布式·zookeeper
寒士obj1 天前
Redisson分布式锁:看门狗机制与续期原理
redis·分布式
2302_809798321 天前
【Redis】缓存的穿透、击穿和雪崩
数据库·redis·缓存
Badman1 天前
分布式系统下的数据一致性-Redis分布式锁
redis·分布式·后端
努力努力再努力wz1 天前
【c++进阶系列】:万字详解AVL树(附源码实现)
java·运维·开发语言·c++·redis
酷ku的森2 天前
Redis中的hash数据类型
数据库·redis·哈希算法
Arva .2 天前
Redis
数据库·redis·缓存
博一波2 天前
Redis 集群:连锁银行的 “多网点智能协作系统”
数据库·redis·缓存
tuokuac2 天前
Redis 的相关文件作用
数据库·redis·缓存