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

相关文章

Java中线程池遇到父子任务示例及避坑

在Java中使用线程池可以有效地管理和调度线程,提高系统的并发处理能力。然而,当涉及到父子任务时,可能会遇到一些常见的Bug,特别是在子线程中查询数据并行处理时。本文将通过示例代码展示这些常见问...

java中异步任务的实现详解

在Java中实现异步任务是一种提高应用程序性能和响应性的常用技术。异步编程允许某些任务在等待其他任务完成时继续执行,从而避免了阻塞。本文将介绍几种在Java中实现异步任务的方法,并讨论它们的解决...

解密 ClassFinal 加密的 Java Jar包

ClassFinal 是一款java class文件安全加密工具,支持直接加密jar包或war包,无需修改任何项目代码,兼容spring-framework;可避免源码泄漏或字节码被反编译。要点...

图片Base64编码

CSR生成

图片无损放大

图片占位符

Excel拆分文件