Spring Boot中使用SQLite替代Redis实现轻量级缓存:支持任意对象存取与自动初始化
不是所有的缓存都要用Redis来实现,今天介绍在 SpringBoot 中使用 SQLite 替代 Redis 做数据缓存的方案,尤其是为了在SQLite文件不存在时自动创建数据库文件并初始化表结构,可以通过JDBC或者JPA的方式来处理
SQLite是一个嵌入式数据库,不需要单独的服务器进程,非常适合用于本地缓存。本文将介绍如何在Spring Boot中使用SQLite作为缓存,并封装类似于Redis的set
和get
方法,支持任意对象的存取。
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.properties
或application.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的set
和get
功能,支持任意对象的存取。
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
来使用封装好的set
和get
功能。
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();
}
}
版权声明:本文为原创文章,版权归 全栈开发技术博客 所有。
本文链接:https://www.lvtao.net/dev/springboot-sqlite-replaces-redis.html
转载时须注明出处及本声明