基于MySQL实现一个类Redis缓存工具类

大明宫

关注

阅读 1

1天前

我来为你实现一个基于MySQL的Redis缓存工具类。这个工具类将提供完整的缓存功能,包括增删改查和过期时间管理。

1. 首先创建MySQL表结构

CREATE TABLE `cache_data` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `cache_key` varchar(255) NOT NULL COMMENT '缓存键',
  `cache_value` longtext COMMENT '缓存值',
  `expire_time` datetime DEFAULT NULL COMMENT '过期时间',
  `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_cache_key` (`cache_key`),
  KEY `idx_expire_time` (`expire_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='缓存数据表';

2. 实现MysqlCacheService工具类

package com.example.cache;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
public class MysqlCacheService {

    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    private final ObjectMapper objectMapper = new ObjectMapper();
    
    private static final String TABLE_NAME = "cache_data";
    
    /**
     * 设置缓存(永不过期)
     */
    public void set(String key, Object value) {
        set(key, value, null);
    }
    
    /**
     * 设置缓存(带过期时间)
     */
    public void set(String key, Object value, Long expireSeconds) {
        try {
            String jsonValue = objectMapper.writeValueAsString(value);
            LocalDateTime expireTime = null;
            
            if (expireSeconds != null && expireSeconds > 0) {
                expireTime = LocalDateTime.now().plusSeconds(expireSeconds);
            }
            
            String sql = String.format(
                "INSERT INTO %s (cache_key, cache_value, expire_time) VALUES (?, ?, ?) " +
                "ON DUPLICATE KEY UPDATE cache_value = VALUES(cache_value), " +
                "expire_time = VALUES(expire_time), updated_time = NOW()",
                TABLE_NAME
            );
            
            jdbcTemplate.update(sql, key, jsonValue, expireTime);
            
        } catch (Exception e) {
            throw new RuntimeException("设置缓存失败: " + e.getMessage(), e);
        }
    }
    
    /**
     * 设置缓存(带过期时间和时间单位)
     */
    public void set(String key, Object value, Long expire, TimeUnit timeUnit) {
        if (expire != null && timeUnit != null) {
            long expireSeconds = timeUnit.toSeconds(expire);
            set(key, value, expireSeconds);
        } else {
            set(key, value);
        }
    }
    
    /**
     * 获取缓存
     */
    public <T> T get(String key, Class<T> clazz) {
        try {
            // 先清理过期数据
            cleanExpiredCache();
            
            String sql = String.format(
                "SELECT cache_value FROM %s WHERE cache_key = ? " +
                "AND (expire_time IS NULL OR expire_time > NOW())",
                TABLE_NAME
            );
            
            List<String> results = jdbcTemplate.queryForList(sql, String.class, key);
            
            if (results.isEmpty()) {
                return null;
            }
            
            String jsonValue = results.get(0);
            if (!StringUtils.hasText(jsonValue)) {
                return null;
            }
            
            return objectMapper.readValue(jsonValue, clazz);
            
        } catch (Exception e) {
            throw new RuntimeException("获取缓存失败: " + e.getMessage(), e);
        }
    }
    
    /**
     * 获取字符串缓存
     */
    public String get(String key) {
        return get(key, String.class);
    }
    
    /**
     * 判断key是否存在
     */
    public boolean exists(String key) {
        cleanExpiredCache();
        
        String sql = String.format(
            "SELECT COUNT(1) FROM %s WHERE cache_key = ? " +
            "AND (expire_time IS NULL OR expire_time > NOW())",
            TABLE_NAME
        );
        
        Integer count = jdbcTemplate.queryForObject(sql, Integer.class, key);
        return count != null && count > 0;
    }
    
    /**
     * 删除缓存
     */
    public boolean delete(String key) {
        String sql = String.format("DELETE FROM %s WHERE cache_key = ?", TABLE_NAME);
        int rows = jdbcTemplate.update(sql, key);
        return rows > 0;
    }
    
    /**
     * 批量删除缓存
     */
    public int delete(String... keys) {
        if (keys == null || keys.length == 0) {
            return 0;
        }
        
        StringBuilder sql = new StringBuilder(String.format("DELETE FROM %s WHERE cache_key IN (", TABLE_NAME));
        for (int i = 0; i < keys.length; i++) {
            sql.append("?");
            if (i < keys.length - 1) {
                sql.append(",");
            }
        }
        sql.append(")");
        
        return jdbcTemplate.update(sql.toString(), (Object[]) keys);
    }
    
    /**
     * 设置过期时间
     */
    public boolean expire(String key, Long expireSeconds) {
        LocalDateTime expireTime = LocalDateTime.now().plusSeconds(expireSeconds);
        
        String sql = String.format(
            "UPDATE %s SET expire_time = ?, updated_time = NOW() WHERE cache_key = ?",
            TABLE_NAME
        );
        
        int rows = jdbcTemplate.update(sql, expireTime, key);
        return rows > 0;
    }
    
    /**
     * 获取剩余过期时间(秒)
     */
    public Long ttl(String key) {
        String sql = String.format(
            "SELECT expire_time FROM %s WHERE cache_key = ?",
            TABLE_NAME
        );
        
        List<LocalDateTime> results = jdbcTemplate.queryForList(sql, LocalDateTime.class, key);
        
        if (results.isEmpty()) {
            return -2L; // key不存在
        }
        
        LocalDateTime expireTime = results.get(0);
        if (expireTime == null) {
            return -1L; // 永不过期
        }
        
        LocalDateTime now = LocalDateTime.now();
        if (expireTime.isBefore(now)) {
            return 0L; // 已过期
        }
        
        return java.time.Duration.between(now, expireTime).getSeconds();
    }
    
    /**
     * 清理过期缓存
     */
    public int cleanExpiredCache() {
        String sql = String.format(
            "DELETE FROM %s WHERE expire_time IS NOT NULL AND expire_time <= NOW()",
            TABLE_NAME
        );
        
        return jdbcTemplate.update(sql);
    }
    
    /**
     * 获取所有匹配模式的key
     */
    public List<String> keys(String pattern) {
        cleanExpiredCache();
        
        String sql = String.format(
            "SELECT cache_key FROM %s WHERE cache_key LIKE ? " +
            "AND (expire_time IS NULL OR expire_time > NOW())",
            TABLE_NAME
        );
        
        String likePattern = pattern.replace("*", "%");
        return jdbcTemplate.queryForList(sql, String.class, likePattern);
    }
    
    /**
     * 清空所有缓存
     */
    public int flushAll() {
        String sql = String.format("DELETE FROM %s", TABLE_NAME);
        return jdbcTemplate.update(sql);
    }
    
    /**
     * 获取缓存统计信息
     */
    public Map<String, Object> info() {
        cleanExpiredCache();
        
        String totalSql = String.format("SELECT COUNT(1) FROM %s", TABLE_NAME);
        String activeSql = String.format(
            "SELECT COUNT(1) FROM %s WHERE expire_time IS NULL OR expire_time > NOW()",
            TABLE_NAME
        );
        
        Integer total = jdbcTemplate.queryForObject(totalSql, Integer.class);
        Integer active = jdbcTemplate.queryForObject(activeSql, Integer.class);
        
        return Map.of(
            "total_keys", total != null ? total : 0,
            "active_keys", active != null ? active : 0,
            "expired_keys", (total != null ? total : 0) - (active != null ? active : 0)
        );
    }
}

3. 使用示例

@RestController
@RequestMapping("/cache")
public class CacheController {
    
    @Autowired
    private MysqlCacheService cacheService;
    
    @PostMapping("/set")
    public String setCache(@RequestParam String key, 
                          @RequestParam String value,
                          @RequestParam(required = false) Long expireSeconds) {
        cacheService.set(key, value, expireSeconds);
        return "设置成功";
    }
    
    @GetMapping("/get")
    public String getCache(@RequestParam String key) {
        return cacheService.get(key);
    }
    
    @DeleteMapping("/delete")
    public String deleteCache(@RequestParam String key) {
        boolean success = cacheService.delete(key);
        return success ? "删除成功" : "删除失败";
    }
    
    @GetMapping("/ttl")
    public Long getTtl(@RequestParam String key) {
        return cacheService.ttl(key);
    }
    
    @PostMapping("/expire")
    public String setExpire(@RequestParam String key, 
                           @RequestParam Long seconds) {
        boolean success = cacheService.expire(key, seconds);
        return success ? "设置过期时间成功" : "设置失败";
    }
    
    @GetMapping("/info")
    public Map<String, Object> getCacheInfo() {
        return cacheService.info();
    }
}

4. 配置定时清理任务

@Component
public class CacheCleanupTask {
    
    @Autowired
    private MysqlCacheService cacheService;
    
    @Scheduled(fixedRate = 300000) // 每5分钟执行一次
    public void cleanupExpiredCache() {
        int cleaned = cacheService.cleanExpiredCache();
        if (cleaned > 0) {
            System.out.println("清理过期缓存: " + cleaned + " 条");
        }
    }
}

5. 主要特性

基本操作:增删改查
过期时间:支持设置TTL
自动清理:定时清理过期数据
批量操作:支持批量删除
模式匹配:支持通配符查询
统计信息:提供缓存统计
JSON序列化:支持复杂对象存储
线程安全:基于数据库事务保证一致性

这个实现提供了类似Redis的API接口,但数据持久化在MySQL中,适合对数据持久性要求较高的场景。




精彩评论(0)

0 0 举报