整活 kotlin + springboot3 + sqlite 配置一個 SQLiteCache

要實現一個 SQLiteCache 也是很簡單的只需要創建一個 cacheManager Bean 即可

// 如果配置文件中 spring.cache.sqlite.enable = false 則不啟用
@Bean("cacheManager")
@ConditionalOnProperty(name = ["spring.cache.sqlite.enable"], havingValue = "true", matchIfMissing = false)
fun cacheManager(sqliteMemoryConnection: Connection): CacheManager {// TODO 返回 CacheManager 
}

同樣的還需要 SQLite 這里 SQLite 的 url 設置為 jdbc:sqlite::memory:

@Bean
fun sqliteMemoryConnection(): Connection {val dataSource = SQLiteDataSource()dataSource.url = urllogger.info("SQLite cache 創建連接: $url")return dataSource.connection
}

配置代碼

該代碼僅僅作為整活使用

package io.github.zimoyin.ra3.configimport io.github.zimoyin.ra3.config.SQLiteCacheConfig.EvictionPolicy.*
import kotlinx.coroutines.*
import org.bouncycastle.asn1.x500.style.RFC4519Style.name
import org.slf4j.LoggerFactory
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.cache.Cache
import org.springframework.cache.CacheManager
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.validation.annotation.Validated
import org.sqlite.SQLiteDataSource
import java.sql.Connection
import java.util.*
import java.util.concurrent.*
import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.locks.ReentrantReadWriteLock
import javax.sql.DataSource
import kotlin.math.log
import kotlin.time.Duration.Companion.minutes@Configuration
@Validated
@ConfigurationProperties(prefix = "spring.cache.sqlite")
class SQLiteCacheConfig(var url: String = "jdbc:sqlite::memory:",var enable: Boolean = false,var tableCacheSize: Int = 100,var expirationMilliseconds: Long = 60000L,var evictionPolicy: EvictionPolicy = FIFO,
) {private val logger = LoggerFactory.getLogger(SQLiteCacheConfig::class.java)@Beanfun sqliteDataSource(): DataSource {val dataSource = SQLiteDataSource()dataSource.url = urllogger.info("初始化 SQLiteCache 數據庫地址: $url")return dataSource}@Bean("sqliteCacheManager")@ConditionalOnProperty(name = ["spring.cache.sqlite.enable"], havingValue = "true")fun cacheManager(sqliteDataSource: DataSource): CacheManager = SQLiteCacheManager(dataSource = sqliteDataSource,maxSize = tableCacheSize,expirationMs = expirationMilliseconds,evictionPolicy = evictionPolicy)enum class EvictionPolicy {/*** First In First Out (FIFO)*/FIFO,/*** Least Recently Used (LRU)*/LRU,/*** Least Frequently Used (LFU)*/LFU}class SQLiteCacheManager(private val dataSource: DataSource,private val maxSize: Int,private val expirationMs: Long,private val evictionPolicy: EvictionPolicy,) : CacheManager {private val cacheMap = ConcurrentHashMap<String, SQLiteCache>()private val logger = LoggerFactory.getLogger(SQLiteCacheManager::class.java)private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)init {// 啟動定時清理任務(首次延遲1分鐘,之后每分鐘執行)CoroutineScope(SupervisorJob() + Dispatchers.IO).launch {while (isActive) {delay(1.minutes)cleanupExpiredEntries()}}logger.info("初始化 SQLiteCacheManager: dataSource=$dataSource, maxSize=$maxSize, expirationMs=$expirationMs, evictionPolicy=$evictionPolicy")}override fun getCache(name: String): Cache {return cacheMap.computeIfAbsent(name) {SQLiteCache(name = it,dataSource = dataSource,maxSize = maxSize,expirationMs = expirationMs,evictionPolicy = evictionPolicy).also { cache ->logger.info("創建緩存表 $name")cache.initialize()}}}override fun getCacheNames(): MutableCollection<String> = cacheMap.keysprivate fun cleanupExpiredEntries() {cacheMap.values.forEach { cache ->try {logger.debug("緩存表 ${cache.name} 命中率: ${cache.getHitRate()}")cache.evictExpiredItems()} catch (e: Exception) {logger.error("Error cleaning expired entries in cache ${cache.name}", e)}}}@Synchronizedfun shutdown() {scope.cancel()cacheMap.values.forEach { it.close() }}}class SQLiteCache(private val name: String,val dataSource: DataSource,val maxSize: Int,val expirationMs: Long,val evictionPolicy: EvictionPolicy,) : Cache {private val logger = LoggerFactory.getLogger(SQLiteCache::class.java)private val connection: Connection = dataSource.connection.apply {autoCommit = false}private val lock = ReentrantReadWriteLock()private val hitCount = AtomicLong()private val missCount = AtomicLong()fun initialize() {createTableIfNotExists()createIndexes()}fun close() {connection.close()}override fun getName(): String = nameoverride fun getNativeCache(): Any = connectionoverride fun get(key: Any): Cache.ValueWrapper? {return try {lock.readLock().lock()getInternal(key.toString()).also {if (it != null) hitCount.incrementAndGet() else missCount.incrementAndGet()}} finally {lock.readLock().unlock()}}override fun <T : Any?> get(key: Any, type: Class<T>?): T? {return get(key)?.get() as? T}override fun <T : Any?> get(key: Any, valueLoader: Callable<T>): T {return try {lock.writeLock().lock()get(key)?.get() as? T ?: run {val value = valueLoader.call()put(key, value)value}} finally {lock.writeLock().unlock()}}override fun put(key: Any, value: Any?) {try {lock.writeLock().lock()executeInTransaction {evictIfNecessary()upsertEntry(key.toString(), value)}} finally {lock.writeLock().unlock()}}override fun evict(key: Any) {executeInTransaction {deleteEntry(key.toString())}}override fun clear() {executeInTransaction {connection.createStatement().executeUpdate("DELETE FROM $name")}}fun getHitRate(): Double {val total = hitCount.get() + missCount.get()return if (total == 0L) 0.0 else hitCount.get().toDouble() / total}internal fun evictExpiredItems() {executeInTransaction {val currentTime = System.currentTimeMillis()connection.prepareStatement("DELETE FROM $name WHERE expires_at < ?").use { ps ->ps.setLong(1, currentTime)ps.executeUpdate()}}}private fun getInternal(key: String): Cache.ValueWrapper? {return connection.prepareStatement("SELECT value, expires_at FROM $name WHERE key = ?").use { ps ->ps.setString(1, key)ps.executeQuery().use { rs ->if (rs.next()) {val expiresAt = rs.getLong("expires_at")if (System.currentTimeMillis() > expiresAt) {deleteEntry(key)null} else {updateAccessMetrics(key)Cache.ValueWrapper { rs.getObject("value") }}} else {null}}}}/*** 更新訪問指標*/private fun updateAccessMetrics(key: String) {when (evictionPolicy) {LRU -> updateLastAccessed(key)LFU -> incrementAccessCount(key)FIFO -> run { /*不需要更新*/ }}}private fun upsertEntry(key: String, value: Any?) {val now = System.currentTimeMillis()connection.prepareStatement("""INSERT INTO $name (key, value, expires_at, created_at, last_accessed, access_count)VALUES (?, ?, ?, ?, ?, 1)ON CONFLICT(key) DO UPDATE SETvalue = excluded.value,expires_at = excluded.expires_at,last_accessed = excluded.last_accessed,access_count = access_count + 1""").use { ps ->ps.setString(1, key)ps.setObject(2, value)ps.setLong(3, now + expirationMs)ps.setLong(4, now)ps.setLong(5, now)ps.executeUpdate()}}private fun deleteEntry(key: String) {connection.prepareStatement("DELETE FROM $name WHERE key = ?").use { ps ->ps.setString(1, key)ps.executeUpdate()}}private fun evictIfNecessary() {if (currentSize() >= maxSize) {when (evictionPolicy) {FIFO -> evictByCreatedTime()LRU -> evictByLastAccessed()LFU -> evictByAccessCount()}}}private fun currentSize(): Int {return connection.createStatement().use { stmt ->stmt.executeQuery("SELECT COUNT(*) FROM $name ").use { rs ->if (rs.next()) rs.getInt(1) else 0}}}private fun evictByCreatedTime() {evictOldest("created_at")}private fun evictByLastAccessed() {evictOldest("last_accessed")}private fun evictByAccessCount() {connection.createStatement().use { stmt ->stmt.executeQuery("SELECT key FROM $name ORDER BY access_count ASC, created_at ASC LIMIT 1").use { rs ->if (rs.next()) deleteEntry(rs.getString(1))}}}private fun evictOldest(column: String) {connection.createStatement().use { stmt ->stmt.executeQuery("SELECT key FROM $name ORDER BY $column ASC LIMIT 1").use { rs ->if (rs.next()) deleteEntry(rs.getString(1))}}}private fun updateLastAccessed(key: String) {connection.prepareStatement("UPDATE $name SET last_accessed = ? WHERE key = ?").use { ps ->ps.setLong(1, System.currentTimeMillis())ps.setString(2, key)ps.executeUpdate()}}private fun incrementAccessCount(key: String) {connection.createStatement().executeUpdate("UPDATE $name SET access_count = access_count + 1 WHERE key = '$key'")}private fun createTableIfNotExists() {connection.createStatement().execute("""CREATE TABLE IF NOT EXISTS $name (key TEXT PRIMARY KEY,value BLOB,expires_at INTEGER,created_at INTEGER,last_accessed INTEGER,access_count INTEGER DEFAULT 0)""")}/*** 創建索引*/private fun createIndexes() {arrayOf("CREATE INDEX IF NOT EXISTS idx_${name}_expires ON $name (expires_at)","CREATE INDEX IF NOT EXISTS idx_${name}_created ON $name (created_at)","CREATE INDEX IF NOT EXISTS idx_${name}_last_access ON $name (last_accessed)","CREATE INDEX IF NOT EXISTS idx_${name}_access_count ON $name (access_count)").forEach { indexSql ->connection.createStatement().execute(indexSql)}}/*** 執行在事務中運行的代碼塊,并返回結果。如果代碼塊執行成功,則提交事務;如果代碼塊執行失敗,則回滾事務。*/private inline fun <T> executeInTransaction(block: () -> T): T {return try {val result = block()connection.commit()result} catch (ex: Exception) {connection.rollback()throw ex}}}
}

配置文件

spring:cache:sqlite:enable: falseexpiration-milliseconds: 60000table-cache-size: 10000eviction-policy: lru

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/77221.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/77221.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/77221.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

深入探索如何壓縮 WebAssembly

一、初始體積&#xff1a;默認 Release 構建 我們從最基礎的構建開始&#xff0c;不開啟調試符號&#xff0c;僅使用默認的 release 模式&#xff1a; $ wc -c pkg/wasm_game_of_life_bg.wasm 29410 pkg/wasm_game_of_life_bg.wasm這是我們優化的起點 —— 29,410 字節。 二…

多角度分析Vue3 nextTick() 函數

nextTick() 是 Vue 3 中的一個核心函數&#xff0c;它的作用是延遲執行某些操作&#xff0c;直到下一次 DOM 更新循環結束之后再執行。這個函數常用于在 Vue 更新 DOM 后立即獲取更新后的 DOM 狀態&#xff0c;或者在組件渲染完成后執行某些操作。 官方的解釋是&#xff0c;當…

前端面試-自動化部署

基礎概念 什么是CI/CD&#xff1f;在前端項目中如何應用&#xff1f;自動化部署相比手動部署有哪些優勢&#xff1f;常見的自動化部署工具有哪些&#xff1f;舉例說明它們的區別&#xff08;如Jenkins vs GitHub Actions&#xff09;。如何通過Git Hook實現自動化部署&#xf…

架構生命周期(高軟57)

系列文章目錄 架構生命周期 文章目錄 系列文章目錄前言一、軟件架構是什么&#xff1f;二、軟件架構的內容三、軟件設計階段四、構件總結 前言 本節講明架構設計的架構生命周期概念。 一、軟件架構是什么&#xff1f; 二、軟件架構的內容 三、軟件設計階段 四、構件 總結 就…

GPTNet如何革新創意與效率

引言 人工智能正在以前所未有的速度改變我們的工作與生活方式&#xff0c;從智能寫作到視覺創作&#xff0c;AI工具已成為不可或缺的伙伴。在眾多平臺中&#xff0c;GPTNet以其強大的功能整合和直觀體驗嶄露頭角。它不僅匯集了GPT系列、Claude、Grok、Gemini等頂級對話模型&am…

【計網】SSL/TLS核心原理

序言 在HTTP協議中&#xff0c;信息是明文傳輸的&#xff0c;因此為了通信安全就有了HTTPS(Hyper Text Transfer Protocol over Secure Socket Layer)協議。HTTPS也是一種超文本傳送協議&#xff0c;在HTTP的基礎上加入了SSL/TLS協議&#xff0c;SSL/TLS依靠證書來驗證服務端的…

Web Components 開發與集成

以下是關于 Web Components 開發與集成 的系統知識梳理,涵蓋核心概念、高級特性、集成與優化等內容: 一、Web Components 核心概念 技術作用核心 APICustom Elements定義可復用的自定義 HTML 元素customElements.define()、生命周期鉤子(connectedCallback 等)Shadow DOM封…

day26 學習筆記

文章目錄 前言一、圖像顏色轉換1.HSV顏色空間2.顏色轉換 二、灰度化1.最大值法2.平均值法3.加權均值法 三、二值化1.全局閾值法1.閾值法(THRESH_BINARY)2.反閾值法(THRESH_BINARY_INV)3.截斷閾值法(THRESH_TRUNC)4.低閾值零處理(THRESH_TOZERO)5.超閾值零處理(THRESH_TOZERO_IN…

威鋒VL822-Q7T10GHUB芯片適用于擴展塢顯示器

一、概述 VL822-Q7T是VIA Lab&#xff08;威盛電子旗下專注于USB相關技術研發的子公司&#xff09;精心打造的一款高性能USB 3.1 Gen2集線器控制器芯片。在當今數字化時代&#xff0c;USB接口作為設備連接與數據傳輸的核心通道&#xff0c;其性能與穩定性至關重要。VL822-Q7T憑…

華為OD機試真題——最小的調整次數/特異性雙端隊列(2025A卷:100分)Java/python/JavaScript/C++/C語言/GO六種最佳實現

2025 A卷 100分 題型 本文涵蓋詳細的問題分析、解題思路、代碼實現、代碼詳解、測試用例以及綜合分析&#xff1b; 并提供Java、python、JavaScript、C、C語言、GO六種語言的最佳實現方式&#xff01; 2025華為OD真題目錄全流程解析/備考攻略/經驗分享 華為OD機試真題《最小的調…

關于 Spring Boot 微服務解決方案的對比,并以 Spring Cloud Alibaba 為例,詳細說明其核心組件的使用方式、配置及代碼示例

以下是關于 Spring Boot 微服務解決方案的對比&#xff0c;并以 Spring Cloud Alibaba 為例&#xff0c;詳細說明其核心組件的使用方式、配置及代碼示例&#xff1a; 關于 Spring Cloud Alibaba 致力于提供微服務開發的一站式解決方案! https://sca.aliyun.com/?spm7145af80…

常見的爬蟲算法

1.base64加密 base64是什么 Base64編碼&#xff0c;是由64個字符組成編碼集&#xff1a;26個大寫字母AZ&#xff0c;26個小寫字母az&#xff0c;10個數字0~9&#xff0c;符號“”與符號“/”。Base64編碼的基本思路是將原始數據的三個字節拆分轉化為四個字節&#xff0c;然后…

B樹、紅黑樹、B+樹和平衡二叉樹(如AVL樹)的區別

B樹、紅黑樹、B樹和平衡二叉樹&#xff08;如AVL樹&#xff09;的區別及優缺點的總結&#xff1a; 1. 平衡二叉樹&#xff08;AVL樹&#xff09; 結構&#xff1a;二叉搜索樹&#xff0c;每個節點的左右子樹高度差不超過1。平衡方式&#xff1a;通過旋轉&#xff08;左旋/右旋…

Python Cookbook-6.5 繼承的替代方案——自動托管

任務 你需要從某個類或者類型繼承&#xff0c;但是需要對繼承做一些調整。比如&#xff0c;需要選擇性地隱藏某些基類的方法&#xff0c;而繼承并不能做到這一點。 解決方案 繼承是很方便的&#xff0c;但它并不是萬用良藥。比如&#xff0c;它無法讓你隱藏基類的方法或者屬…

長短期記憶網絡:從理論到創新應用的深度剖析

一、引言 1.1 研究背景 深度學習在人工智能領域的發展可謂突飛猛進&#xff0c;而長短期記憶網絡&#xff08;LSTM&#xff09;在其中占據著至關重要的地位。隨著數據量的不斷增長和對時序數據處理需求的增加&#xff0c;傳統的神經網絡在處理長序列數據時面臨著梯度消失和梯…

vue3.2 + element-plus 實現跟隨input輸入框的彈框,彈框里可以分組或tab形式顯示選項

效果 基礎用法&#xff08;分組選項&#xff09; 高級用法&#xff08;帶Tab欄&#xff09; <!-- 彈窗跟隨通用組件 SmartSelector.vue --> <!-- 彈窗跟隨通用組件 --> <template><div class"smart-selector-container"><el-popove…

C語言中冒泡排序和快速排序的區別

冒泡排序和快速排序都是常見的排序算法&#xff0c;但它們在原理、效率和應用場景等方面存在顯著區別。以下是兩者的詳細對比&#xff1a; 一、算法原理 1. 冒泡排序 原理&#xff1a;通過重復遍歷數組&#xff0c;比較相鄰元素的大小&#xff0c;并在必要時交換它們的位置。…

軟件信息安全性測試如何進行?有哪些注意事項?

隨著信息技術的高速發展&#xff0c;軟件已經成為我們生活和工作中不可或缺的一部分。然而&#xff0c;隨著軟件產品的廣泛普及&#xff0c;軟件信息安全性問題也日益凸顯&#xff0c;因此軟件信息安全性測試必不可少。那么軟件信息安全性測試應如何進行呢?在進行過程中又有哪…

springboot集成mybaits-generator自動生成代碼

文章目錄 概述創建springboot項目pom文件aplication.yml代碼生成類mybatis-plus提供的變量controller模板mapper模板總結 概述 創建springboot項目&#xff0c;在這里使用的是springboot 2.6.13版本&#xff0c;引入的項目依賴包如pom文件所寫&#xff0c;jdk使用1.8&#xff…

數據庫脫褲

假設你已經getshell 找到mysql賬號密碼。 網站要連接mysql&#xff0c;就需要把mysql的賬號密碼保存在一個php文件中&#xff0c;類似config.php、common.inc.php等&#xff0c;在shell中&#xff0c;讀取這些文件&#xff0c;找到其中信息即可 下面是一些常見平臺的配置文…