最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

在分布式環(huán)境下正確使用MyBatis二級緩存的最佳實(shí)踐

 更新時間:2025年08月27日 09:37:59   作者:一葉飄零_sweeeet  
緩存就是內(nèi)存中的數(shù)據(jù),常常來自對數(shù)據(jù)庫查詢結(jié)果的保存,使用緩存,我們可以避免頻繁與數(shù)據(jù)庫進(jìn)行交互,從而提高響應(yīng)速度,這篇文章主要介紹了在分布式環(huán)境下正確使用MyBatis二級緩存的最佳實(shí)踐,需要的朋友可以參考下

前言

在分布式環(huán)境下使用 MyBatis 二級緩存,核心挑戰(zhàn)是解決多節(jié)點(diǎn)緩存一致性問題。單機(jī)環(huán)境中,二級緩存是內(nèi)存級別的本地緩存,而分布式環(huán)境下多節(jié)點(diǎn)獨(dú)立部署,本地緩存無法跨節(jié)點(diǎn)共享,易導(dǎo)致 “緩存孤島” 和數(shù)據(jù)不一致。本文從底層原理出發(fā),提供一套完整的分布式二級緩存解決方案,包含實(shí)戰(zhàn)配置與最佳實(shí)踐。

一、分布式環(huán)境下二級緩存的核心問題

在分布式架構(gòu)(如微服務(wù)集群)中,默認(rèn)的 MyBatis 二級緩存(本地內(nèi)存緩存)會暴露三個致命問題:

  • 緩存孤島:每個節(jié)點(diǎn)維護(hù)獨(dú)立緩存,同一查詢在不同節(jié)點(diǎn)可能命中不同緩存數(shù)據(jù)(如節(jié)點(diǎn) A 更新數(shù)據(jù)后,節(jié)點(diǎn) B 的緩存仍是舊值)。
  • 數(shù)據(jù)不一致:跨節(jié)點(diǎn)更新數(shù)據(jù)時,無法通知其他節(jié)點(diǎn)同步清空緩存,導(dǎo)致部分節(jié)點(diǎn)返回臟數(shù)據(jù)。
  • 序列化風(fēng)險:本地緩存可直接存儲 Java 對象引用,而分布式緩存需網(wǎng)絡(luò)傳輸,若對象未序列化會導(dǎo)致緩存失敗。

二、解決方案:基于集中式緩存的二級緩存改造

分布式環(huán)境下的核心解決方案是:用集中式緩存(如 Redis、Memcached)替代本地內(nèi)存緩存,讓所有節(jié)點(diǎn)共享同一緩存源,實(shí)現(xiàn)緩存數(shù)據(jù)全局一致。

2.1 技術(shù)選型:MyBatis + Redis(最常用組合)

Redis 作為高性能的分布式緩存中間件,支持?jǐn)?shù)據(jù)持久化、過期策略和集群模式,是 MyBatis 二級緩存的理想選擇。實(shí)現(xiàn)思路是:

  • 讓 MyBatis 的二級緩存數(shù)據(jù)存儲到 Redis,而非本地內(nèi)存。
  • 所有節(jié)點(diǎn)通過 Redis 訪問緩存,確保緩存數(shù)據(jù)全局唯一。

三、實(shí)戰(zhàn):MyBatis 集成 Redis 實(shí)現(xiàn)分布式二級緩存

3.1 環(huán)境準(zhǔn)備

  • JDK 17
  • MyBatis 3.5.10+
  • Redis 6.2+
  • Spring Boot 2.7.x(簡化配置)

3.2 依賴配置(Maven)

<!-- MyBatis核心依賴 -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.3.0</version>
</dependency>

<!-- Redis緩存依賴(MyBatis官方適配) -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-redis</artifactId>
    <version>1.0.0-beta2</version>
</dependency>

<!-- Redis客戶端 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

3.3 配置 Redis 連接

src/main/resources下創(chuàng)建redis.properties,配置 Redis 連接信息:

# Redis服務(wù)器地址
redis.host=192.168.1.100
# Redis端口
redis.port=6379
# 連接超時時間(毫秒)
redis.timeout=2000
# Redis密碼(無密碼則留空)
redis.password=your_redis_password
# 數(shù)據(jù)庫索引(默認(rèn)0)
redis.database=1
# 緩存默認(rèn)過期時間(毫秒,30分鐘)
redis.default.expiration=1800000

3.4 改造實(shí)體類:實(shí)現(xiàn)序列化

分布式緩存中,對象需在網(wǎng)絡(luò)中傳輸,必須實(shí)現(xiàn)Serializable接口,否則會導(dǎo)致緩存失敗。

User.java

package com.example.entity;

import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 用戶實(shí)體類(必須實(shí)現(xiàn)Serializable)
 */
@Data
public class User implements Serializable {
    // 序列化版本號(避免反序列化沖突)
    private static final long serialVersionUID = 1L;
    
    private Long id;
    private String username;
    private String email;
    private LocalDateTime createTime;
}

3.5 配置 Mapper 使用 Redis 緩存

在 Mapper.xml 中指定緩存類型為 Redis,替代默認(rèn)的本地緩存。

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.mapper.UserMapper">
    <!-- 配置Redis作為二級緩存 -->
    <cache 
        type="org.mybatis.caches.redis.RedisCache"  <!-- 指定Redis緩存實(shí)現(xiàn)類 -->
        eviction="LRU"  <!-- 緩存淘汰策略:最近最少使用 -->
        flushInterval="300000"  <!-- 自動刷新間隔(5分鐘) -->
        size="1000"  <!-- 最大緩存對象數(shù)量 -->
        readOnly="false"/>  <!-- 非只讀(需序列化) -->

    <!-- 查詢語句:默認(rèn)使用二級緩存 -->
    <select id="selectById" resultType="com.example.entity.User">
        SELECT id, username, email, create_time AS createTime
        FROM t_user
        WHERE id = #{id}
    </select>

    <!-- 更新語句:默認(rèn)觸發(fā)緩存清空(flushCache=true) -->
    <update id="update">
        UPDATE t_user
        SET username = #{username}, email = #{email}
        WHERE id = #{id}
    </update>
</mapper>

關(guān)鍵配置說明

  • type="org.mybatis.caches.redis.RedisCache":指定 MyBatis 使用 Redis 存儲緩存數(shù)據(jù)。
  • eviction="LRU":當(dāng)緩存滿時,移除最久未使用的對象,避免內(nèi)存溢出。
  • flushInterval="300000":5 分鐘自動刷新一次緩存,作為數(shù)據(jù)一致性的兜底策略。

3.6 全局啟用二級緩存

在 MyBatis 配置文件(或 Spring Boot 配置)中確保二級緩存全局開啟(默認(rèn)開啟,建議顯式配置)。

application.yml

mybatis:
  configuration:
    cache-enabled: true  # 全局啟用二級緩存(默認(rèn)true)
  mapper-locations: classpath:mapper/*.xml  # 指定Mapper.xml路徑

3.7 驗(yàn)證分布式緩存效果

部署兩個服務(wù)節(jié)點(diǎn)(Node1 和 Node2),通過測試驗(yàn)證緩存一致性:

測試代碼(Service 層)

@Slf4j
@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    /**
     * 查詢用戶:優(yōu)先從Redis緩存獲取
     */
    public User getUserById(Long id) {
        if (Objects.isNull(id)) {
            log.warn("用戶ID為空");
            return null;
        }
        User user = userMapper.selectById(id);
        log.info("查詢用戶結(jié)果:{}", user);
        return user;
    }

    /**
     * 更新用戶:觸發(fā)Redis緩存清空
     */
    @Transactional
    public void updateUser(User user) {
        if (Objects.isNull(user) || Objects.isNull(user.getId())) {
            log.warn("用戶信息不完整");
            return;
        }
        int rows = userMapper.update(user);
        log.info("更新用戶影響行數(shù):{}", rows);
        // 事務(wù)提交后,MyBatis會自動清空Redis中該Mapper的緩存
    }
}

測試步驟與預(yù)期結(jié)果

  • Node1 首次查詢用戶 ID=1:未命中緩存,查詢數(shù)據(jù)庫,結(jié)果存入 Redis。
  • Node2 查詢用戶 ID=1:命中 Redis 緩存,直接返回結(jié)果(無需查庫)。
  • Node1 更新用戶 ID=1:事務(wù)提交后,Redis 中該用戶的緩存被清空。
  • Node2 再次查詢用戶 ID=1:未命中緩存,查詢數(shù)據(jù)庫獲取最新數(shù)據(jù),并存入 Redis。

通過 Redis 客戶端(如redis-cli)可觀察到緩存鍵值的創(chuàng)建與刪除,證明所有節(jié)點(diǎn)共享同一緩存。

四、分布式緩存的高級優(yōu)化策略

4.1 緩存鍵設(shè)計(jì):避免命名沖突

MyBatis 默認(rèn)的緩存鍵由namespace + SQL語句 + 參數(shù)組成,在分布式環(huán)境下需確保唯一性??赏ㄟ^自定義RedisCache實(shí)現(xiàn)類優(yōu)化鍵名:

CustomRedisCache.java

package com.example.cache;

import org.mybatis.caches.redis.RedisCache;
import java.util.UUID;

/**
 * 自定義Redis緩存,添加應(yīng)用前綴避免鍵沖突
 */
public class CustomRedisCache extends RedisCache {
    // 應(yīng)用唯一標(biāo)識(避免多應(yīng)用共用Redis時鍵沖突)
    private static final String APP_PREFIX = "myapp:";

    public CustomRedisCache(String id) {
        super(id);
    }

    /**
     * 重寫緩存鍵,添加應(yīng)用前綴
     */
    @Override
    public Object getObject(Object key) {
        String cacheKey = APP_PREFIX + key.toString();
        return super.getObject(cacheKey);
    }

    @Override
    public void putObject(Object key, Object value) {
        String cacheKey = APP_PREFIX + key.toString();
        super.putObject(cacheKey, value);
    }

    @Override
    public Object removeObject(Object key) {
        String cacheKey = APP_PREFIX + key.toString();
        return super.removeObject(cacheKey);
    }
}

在 Mapper.xml 中使用自定義緩存:

<cache type="com.example.cache.CustomRedisCache"/>

4.2 緩存失效策略:主動 + 被動結(jié)合

分布式環(huán)境下,單一的自動失效可能存在延遲,需結(jié)合主動失效策略:

  • 被動失效:依賴flushInterval自動刷新(如 30 分鐘),適合非核心數(shù)據(jù)。
  • 主動失效:更新數(shù)據(jù)后,通過代碼手動刪除緩存(極端場景):
/**
 * 手動刪除指定用戶的緩存
 */
public void deleteUserCache(Long userId) {
    // 獲取UserMapper的緩存對象
    Cache cache = sqlSessionFactory.getConfiguration().getCache("com.example.mapper.UserMapper");
    if (Objects.nonNull(cache)) {
        // 構(gòu)造緩存鍵(需與MyBatis生成規(guī)則一致)
        // 鍵格式:namespace + "::" + SQLID + "::" + 參數(shù)
        String cacheKey = "com.example.mapper.UserMapper::selectById::" + userId;
        cache.removeObject(cacheKey);
        log.info("手動刪除用戶緩存,key: {}", cacheKey);
    }
}

4.3 處理緩存與數(shù)據(jù)庫一致性:延遲雙刪

在高并發(fā)場景,更新數(shù)據(jù)庫后立即刪除緩存可能仍有風(fēng)險(刪除緩存前已有請求讀取舊緩存)??刹捎?“延遲雙刪” 策略:

@Transactional
public void updateUserWithDelayDelete(User user) {
    // 1. 更新數(shù)據(jù)庫
    userMapper.update(user);
    // 2. 第一次刪除緩存(事務(wù)提交后執(zhí)行)
    transactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
        @Override
        public void afterCommit() {
            // 事務(wù)提交后刪除緩存
            deleteUserCache(user.getId());
            
            // 3. 延遲1秒后第二次刪除(避免更新前的請求仍讀取舊緩存)
            CompletableFuture.runAsync(() -> {
                try {
                    Thread.sleep(1000);
                    deleteUserCache(user.getId());
                } catch (InterruptedException e) {
                    log.error("延遲刪除緩存失敗", e);
                }
            });
        }
    });
}

4.4 緩存序列化優(yōu)化:使用 JSON 替代 Java 序列化

默認(rèn)情況下,MyBatis-Redis 使用 Java 序列化存儲對象,存在性能差、可讀性低的問題??勺远x序列化方式(如 JSON):

JsonRedisCache.java(簡化版)

package com.example.cache;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class JsonRedisCache implements Cache {
    private final String id;
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private final RedisTemplate<String, Object> redisTemplate;
    private final ObjectMapper objectMapper = new ObjectMapper();

    public JsonRedisCache(String id) {
        this.id = id;
        // 注入RedisTemplate(實(shí)際需通過Spring上下文獲?。?
        this.redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
    }

    @Override
    public String getId() { return id; }

    @Override
    public void putObject(Object key, Object value) {
        try {
            String jsonValue = objectMapper.writeValueAsString(value);
            redisTemplate.opsForValue().set(key.toString(), jsonValue, 30, TimeUnit.MINUTES);
        } catch (Exception e) {
            log.error("緩存序列化失敗", e);
        }
    }

    @Override
    public Object getObject(Object key) {
        try {
            String jsonValue = (String) redisTemplate.opsForValue().get(key.toString());
            if (StringUtils.hasText(jsonValue)) {
                // 根據(jù)實(shí)際類型反序列化(簡化示例)
                return objectMapper.readValue(jsonValue, User.class);
            }
        } catch (Exception e) {
            log.error("緩存反序列化失敗", e);
        }
        return null;
    }

    @Override
    public Object removeObject(Object key) {
        redisTemplate.delete(key.toString());
        return null;
    }

    @Override
    public void clear() {
        // 清空當(dāng)前namespace的所有緩存(需批量刪除匹配鍵)
    }

    @Override
    public int getSize() { return 0; }

    @Override
    public ReadWriteLock getReadWriteLock() { return readWriteLock; }
}

使用 JSON 序列化后,Redis 中緩存的數(shù)據(jù)可讀性更強(qiáng),且序列化效率更高。

五、分布式二級緩存的適用場景與禁忌

5.1 適用場景

  • 查詢頻繁、更新極少的數(shù)據(jù):如字典表、地區(qū)表、系統(tǒng)配置表(更新頻率低,緩存命中率高)。
  • 非核心業(yè)務(wù)數(shù)據(jù):如商品詳情、歷史訂單(允許短時間不一致,優(yōu)先保證性能)。
  • 數(shù)據(jù)一致性要求不高的場景:如用戶瀏覽記錄、熱門商品排行(可接受分鐘級延遲)。

5.2 禁忌場景

  • 實(shí)時性要求極高的數(shù)據(jù):如庫存數(shù)量、賬戶余額(緩存延遲可能導(dǎo)致超賣、余額顯示錯誤)。
  • 高頻更新數(shù)據(jù):如秒殺商品狀態(tài)、實(shí)時在線人數(shù)(緩存命中率低,反而增加 Redis 負(fù)擔(dān))。
  • 超大對象:如包含大量字段的報表數(shù)據(jù)(序列化 / 傳輸成本高,不如直接查庫)。

六、監(jiān)控與調(diào)優(yōu)

  • 緩存命中率監(jiān)控:通過 Redis 的INFO stats命令查看keyspace_hits(命中數(shù))和keyspace_misses(未命中數(shù)),命中率低于 70% 需優(yōu)化緩存策略。
  • 過期鍵清理:避免緩存鍵永久有效,結(jié)合業(yè)務(wù)設(shè)置合理過期時間(如 30 分鐘~24 小時)。
  • Redis 集群:高并發(fā)場景下,使用 Redis Cluster 保證緩存服務(wù)的高可用。

七、總結(jié)

分布式環(huán)境下正確使用 MyBatis 二級緩存的核心是用集中式緩存(如 Redis)替代本地緩存,關(guān)鍵步驟包括:

  • 集成 MyBatis-Redis 適配器,讓緩存數(shù)據(jù)存儲到 Redis。
  • 實(shí)體類實(shí)現(xiàn)序列化,確??绻?jié)點(diǎn)傳輸正常。
  • 配置合理的緩存淘汰策略與過期時間,平衡性能與一致性。
  • 結(jié)合業(yè)務(wù)場景選擇緩存對象,實(shí)時性數(shù)據(jù)禁用緩存。

通過這套方案,既能保留二級緩存的性能優(yōu)勢,又能解決分布式環(huán)境的數(shù)據(jù)一致性問題,實(shí)現(xiàn) “高性能 + 高可靠” 的平衡。

到此這篇關(guān)于在分布式環(huán)境下正確使用MyBatis二級緩存的文章就介紹到這了,更多相關(guān)分布式環(huán)境使用MyBatis二級緩存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 關(guān)于SpringBoot的熱部署方案

    關(guān)于SpringBoot的熱部署方案

    這篇文章主要介紹了關(guān)于SpringBoot的熱部署方案,每次修改代碼就得將項(xiàng)目重啟,重新部署,對于一些大型應(yīng)用來說,重啟時間需要花費(fèi)大量的時間成本,本文就來詳解熱部署方案,需要的朋友可以參考下
    2023-05-05
  • Jackson優(yōu)雅序列化Java枚舉類過程解析

    Jackson優(yōu)雅序列化Java枚舉類過程解析

    這篇文章主要介紹了Jackson優(yōu)雅序列化Java枚舉類過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-09-09
  • SpringBoot實(shí)現(xiàn)單點(diǎn)登錄的實(shí)現(xiàn)詳解

    SpringBoot實(shí)現(xiàn)單點(diǎn)登錄的實(shí)現(xiàn)詳解

    在現(xiàn)代的Web應(yīng)用程序中,單點(diǎn)登錄(Single?Sign-On)已經(jīng)變得越來越流行,在本文中,我們將使用Spring?Boot構(gòu)建一個基本的單點(diǎn)登錄系統(tǒng),需要的可以參考一下
    2023-05-05
  • Java Web項(xiàng)目中編寫定時任務(wù)的實(shí)現(xiàn)

    Java Web項(xiàng)目中編寫定時任務(wù)的實(shí)現(xiàn)

    本篇文章主要介紹了Java Web項(xiàng)目中編寫定時任務(wù)的實(shí)現(xiàn),具有一定的參考價值,有興趣的可以了解一下。
    2017-01-01
  • Java調(diào)用DeepSeek?API的8個高頻坑與解決方法

    Java調(diào)用DeepSeek?API的8個高頻坑與解決方法

    現(xiàn)在大模型開發(fā)特別火,DeepSeek?因?yàn)橹形睦斫夂?、反?yīng)快、還便宜,不少?Java?開發(fā)者都用它,本文整理了最常踩的?8?個坑,希望對大家有所幫助
    2025-12-12
  • JavaSE的類和對象你真的了解嗎

    JavaSE的類和對象你真的了解嗎

    這篇文章主要為大家詳細(xì)介紹了JavaSE的類和對象,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03
  • 詳解如何判斷Java線程池任務(wù)已執(zhí)行完

    詳解如何判斷Java線程池任務(wù)已執(zhí)行完

    線程池的使用并不復(fù)雜,麻煩的是如何判斷線程池中的任務(wù)已經(jīng)全部執(zhí)行完了,所以接下來,我們就來看看如何判斷線程中的任務(wù)是否已經(jīng)全部執(zhí)行完吧
    2023-08-08
  • 解決@Validated對List對象驗(yàn)證失效問題

    解決@Validated對List對象驗(yàn)證失效問題

    文章指出@Validated注解無法驗(yàn)證List數(shù)組參數(shù),建議手動逐項(xiàng)驗(yàn)證或封裝List為對象進(jìn)行驗(yàn)證,前者代碼冗余但直接,后者需調(diào)整JSON結(jié)構(gòu)但更簡潔,根據(jù)需求選擇方案,權(quán)衡靈活性與參數(shù)改造成本
    2025-09-09
  • SpringBoot使用@Scheduled定時器的示例詳解

    SpringBoot使用@Scheduled定時器的示例詳解

    SpringBoot的@Scheduled注解用于配置定時任務(wù),需啟用@EnableScheduling,支持fixedRate、fixedDelay、cron等參數(shù),控制執(zhí)行頻率、延遲及周期,支持線程池和異常處理配置,默認(rèn)單線程串行執(zhí)行,本文介紹SpringBoot使用@Scheduled定時器的操作示例,感興趣的朋友一起看看吧
    2025-07-07
  • Java快速排序的實(shí)現(xiàn)詳細(xì)代碼及通俗解釋

    Java快速排序的實(shí)現(xiàn)詳細(xì)代碼及通俗解釋

    這篇文章主要介紹了Java快速排序?qū)崿F(xiàn)的相關(guān)資料,快速排序是一種高效的排序算法,通過選擇一個基準(zhǔn)值將數(shù)組分成兩部分,左邊的元素比基準(zhǔn)值小,右邊的元素比基準(zhǔn)值大,然后遞歸地對這兩部分進(jìn)行排序,需要的朋友可以參考下
    2025-02-02

最新評論

喀喇| 塔城市| 明溪县| 武川县| 浙江省| 拉萨市| 象山县| 武隆县| 内乡县| 黎平县| 阳城县| 公安县| 富顺县| 宁强县| 通榆县| 松潘县| 宁远县| 清新县| 万州区| 临武县| 凤阳县| 成都市| 高青县| 莱阳市| 平邑县| 大宁县| 芜湖市| 睢宁县| 抚远县| 抚州市| 天全县| 那曲县| 无锡市| 西贡区| 资阳市| 阜南县| 广宗县| 金阳县| 桐庐县| 三台县| 固原市|