Spring Boot中使用SQLite替代Redis实现轻量级缓存:支持任意对象存取与自动初始化

不是所有的缓存都要用Redis来实现,今天介绍在 SpringBoot 中使用 SQLite 替代 Redis 做数据缓存的方案,尤其是为了在SQLite文件不存在时自动创建数据库文件并初始化表结构,可以通过JDBC或者JPA的方式来处理

SQLite是一个嵌入式数据库,不需要单独的服务器进程,非常适合用于本地缓存。本文将介绍如何在Spring Boot中使用SQLite作为缓存,并封装类似于Redis的setget方法,支持任意对象的存取。

1. 引入依赖

首先,在pom.xml中引入SQLite和Jackson库的依赖:

<dependency>
    <groupId>org.xerial</groupId>
    <artifactId>sqlite-jdbc</artifactId>
    <version>3.46.1.3</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.18.0</version>
</dependency>

2. 配置数据库连接

application.propertiesapplication.yml中配置SQLite数据库文件路径。

spring.datasource.url=jdbc:sqlite:./cache.db
spring.datasource.driver-class-name=org.sqlite.JDBC
spring.datasource.username=
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.SQLiteDialect

3. 数据库初始化

我们可以通过Spring的CommandLineRunner来检测SQLite数据库文件是否存在,如果不存在,则创建并初始化表结构。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

@Component
public class SQLiteInitializer implements CommandLineRunner {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void run(String... args) throws Exception {
        // 创建缓存表,如果表不存在则创建
        String createTableSql = "CREATE TABLE IF NOT EXISTS cache (key TEXT PRIMARY KEY, value TEXT, expiry_time INTEGER)";
        jdbcTemplate.execute(createTableSql);
    }
}

4. 封装缓存操作类

为了方便Redis的平滑迁移,我们可以创建一个封装类SQLiteCacheService,实现类似于Redis的setget功能,支持任意对象的存取。

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

import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.Instant;
import java.util.Optional;

@Service
public class SQLiteCacheService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    private final ObjectMapper objectMapper = new ObjectMapper();

    // 插入或更新缓存
    public <T> void set(String key, T value, long ttlSeconds) {
        try {
            String jsonValue = objectMapper.writeValueAsString(value);
            long expiryTime = Instant.now().getEpochSecond() + ttlSeconds;
            String sql = "INSERT INTO cache (key, value, expiry_time) VALUES (?, ?, ?) "
                       + "ON CONFLICT(key) DO UPDATE SET value = ?, expiry_time = ?";
            jdbcTemplate.update(sql, key, jsonValue, expiryTime, jsonValue, expiryTime);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("Failed to serialize object to JSON", e);
        }
    }

    // 获取缓存,如果不存在或者已过期,返回Optional.empty()
    public <T> Optional<T> get(String key, Class<T> clazz) {
        String sql = "SELECT value, expiry_time FROM cache WHERE key = ?";
        return jdbcTemplate.query(sql, new Object[]{key}, new CacheRowMapper(objectMapper, clazz)).stream().findFirst();
    }

    // 删除缓存
    public void del(String key) {
        String sql = "DELETE FROM cache WHERE key = ?";
        jdbcTemplate.update(sql, key);
    }

    // 清理过期缓存
    public void cleanExpired() {
        String sql = "DELETE FROM cache WHERE expiry_time < ?";
        long currentTime = Instant.now().getEpochSecond();
        jdbcTemplate.update(sql, currentTime);
    }

    // RowMapper 实现,用于将查询结果转换为缓存数据
    private static class CacheRowMapper implements RowMapper<Optional<?>> {
        private final ObjectMapper objectMapper;
        private final Class<?> clazz;

        public CacheRowMapper(ObjectMapper objectMapper, Class<?> clazz) {
            this.objectMapper = objectMapper;
            this.clazz = clazz;
        }

        @Override
        public Optional<?> mapRow(ResultSet rs, int rowNum) throws SQLException {
            long expiryTime = rs.getLong("expiry_time");
            if (Instant.now().getEpochSecond() > expiryTime) {
                // 缓存已过期
                return Optional.empty();
            }
            String jsonValue = rs.getString("value");
            try {
                Object value = objectMapper.readValue(jsonValue, clazz);
                return Optional.of(value);
            } catch (JsonProcessingException e) {
                throw new RuntimeException("Failed to deserialize JSON to object", e);
            }
        }
    }
}

5. 示例用法

我们可以通过注入SQLiteCacheService来使用封装好的setget功能。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CacheController {

    @Autowired
    private SQLiteCacheService cacheService;

    @GetMapping("/cache/set")
    public String setCache(@RequestParam String key, @RequestParam String value, @RequestParam long ttl) {
        cacheService.set(key, value, ttl);
        return "Cache set successfully";
    }

    @GetMapping("/cache/get")
    public String getCache(@RequestParam String key) {
        return cacheService.get(key, String.class).orElse("Cache miss");
    }
}

6. 定期清理过期缓存

可以使用@Scheduled注解定期清理过期缓存。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class CacheCleaner {

    @Autowired
    private SQLiteCacheService cacheService;

    // 每小时清理一次过期缓存
    @Scheduled(cron = "0 0 * * * ?")
    public void cleanCache() {
        cacheService.cleanExpired();
    }
}

标签: Java, Redis

相关文章

如何使用Go编写跨平台组件并让Java或PHP调用

在现代软件开发中,跨语言调用是一个常见的需求。假设我们有一个用Go语言编写的组件,我们希望Java或PHP能够直接调用这个组件中对外提供的方法。为了实现这一目标,我们可以使用以下几种方法:1. ...

linux下或macOS中配置maven及加速

下载 Maven 压缩包:访问 Maven 官方下载页面,下载你需要的版本。解压压缩包:将下载的压缩包解压到你选择的目录,例如 /opt/maven:sudo mkdir -p /opt/mav...

javax.validation中数据验证的注解使用与示例

javax.validation 是 Java 提供的一个用于数据验证的库,其中定义了多种用于约束和验证数据的注解。下面对常用的验证注解类进行详细介绍,并提供对应的使用示例,每个注解都配有中文注...

编译最新的kkFileView并使用docker打包

kkFileView 是一个开源的文件预览服务,支持多种文件格式的在线预览。本文将详细介绍如何编译 kkFileView 的最新版本,并将其打包成 Docker 镜像。1. 环境准备在开始之前,...

Spring Boot 内置的常用工具类整理

Spring Boot 在核心库中提供了一系列常用的工具类,涵盖了断言、对象处理、集合操作、文件与资源处理、IO 流、反射和 AOP。这些工具类帮助开发者提高代码的简洁性和可维护性。本文将对这些...

JAVA+VUE的多国语言跨境电商外贸商城源码

多语言跨境电商外贸商城TikTok内嵌商城,商家入驻、一键铺货、一键提货 全开源完美运营海外版抖音TikTok商城系统源码,TikToK内嵌商城,跨境商城系统源码接在tiktok里面的商城。ti...

图片Base64编码

CSR生成

图片无损放大

图片占位符

Excel拆分文件