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

MyBatis之二級(jí)緩存用法及說(shuō)明

 更新時(shí)間:2026年05月11日 09:49:05   作者:Lisonseekpan  
本文介紹了MyBatis二級(jí)緩存的設(shè)計(jì)與實(shí)現(xiàn),涵蓋其在MyBatis中的位置、源碼解析、詳細(xì)使用案例、最佳實(shí)踐總結(jié)及源碼調(diào)試技巧等內(nèi)容,強(qiáng)調(diào)了設(shè)計(jì)模式的應(yīng)用,如裝飾器模式、模板方法模式等,并提供了緩存穿透、擊穿、雪崩等問(wèn)題的解決方案

1. 架構(gòu)總覽

1.1 二級(jí)緩存在MyBatis中的位置

MyBatis Runtime
├── Configuration (全局配置)
│├── MapperRegistry (Mapper注冊(cè)中心)
│└── Cache (緩存管理器)
├── Executor (執(zhí)行器)
│├── BaseExecutor (基礎(chǔ)執(zhí)行器,含一級(jí)緩存)
│└── CachingExecutor (二級(jí)緩存裝飾器) ★
└── SqlSession (會(huì)話)

2. 源碼深度解析

2.1 核心類(lèi)圖解析

// 核心接口和類(lèi)
public interface Cache {
String getId();
void putObject(Object key, Object value);
Object getObject(Object key);
Object removeObject(Object key);
void clear();
int getSize();
ReadWriteLock getReadWriteLock();
}

// 默認(rèn)實(shí)現(xiàn):PerpetualCache
public class PerpetualCache implements Cache {
private final String id;
private final Map<Object, Object> cache = new HashMap<>();
// 基礎(chǔ)的HashMap實(shí)現(xiàn)
}

// 裝飾器模式:各種Cache裝飾器
public class LruCache implements Cache {
private final Cache delegate;
private final Map<Object, Object> keyMap;
private Object eldestKey;
// LRU算法實(shí)現(xiàn)
}

2.2 CachingExecutor:二級(jí)緩存的核心

public class CachingExecutor implements Executor {
private final Executor delegate;// 被裝飾的執(zhí)行器(通常是SimpleExecutor)
private final TransactionalCacheManager tcm = new TransactionalCacheManager();

@Override
public <E> List<E> query(MappedStatement ms,
Object parameter,
RowBounds rowBounds,
ResultHandler resultHandler,
CacheKey key,
BoundSql boundSql) throws SQLException {

// 1. 獲取MappedStatement的緩存
Cache cache = ms.getCache();

if (cache != null) {
// 2. 檢查是否需要刷新緩存
flushCacheIfRequired(ms);

// 3. 如果useCache=true且沒(méi)有設(shè)置resultHandler
if (ms.isUseCache() && resultHandler == null) {
// 4. 確保沒(méi)有輸出參數(shù)
ensureNoOutParams(ms, boundSql);

// 5. 從二級(jí)緩存獲取
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);

if (list == null) {
// 6. 緩存未命中,委托給實(shí)際執(zhí)行器查詢
list = delegate.query(ms, parameter, rowBounds,
resultHandler, key, boundSql);

// 7. 將結(jié)果放入二級(jí)緩存
tcm.putObject(cache, key, list);
}
return list;
}
}
// 8. 沒(méi)有緩存配置,直接查詢
return delegate.query(ms, parameter, rowBounds,
resultHandler, key, boundSql);
}
}

2.3 CacheKey的生成機(jī)制

public class CacheKey implements Cloneable, Serializable {
private static final int DEFAULT_MULTIPLYER = 37;
private static final int DEFAULT_HASHCODE = 17;

private final int multiplier;
private int hashcode;
private long checksum;
private int count;
private List<Object> updateList;// 用于equals比較

public CacheKey(Object[] objects) {
this.multiplier = DEFAULT_MULTIPLYER;
this.hashcode = DEFAULT_HASHCODE;

// 關(guān)鍵:按順序組合所有元素生成hash
updateAll(objects);
}

public void update(Object object) {
int baseHashCode = object == null ? 1 :
ArrayUtil.hashCode(object);

count++;
checksum += baseHashCode;
baseHashCode *= count;

hashcode = multiplier * hashcode + baseHashCode;

updateList.add(object);
}

// CacheKey生成位置:BaseExecutor.createCacheKey()
public CacheKey createCacheKey(MappedStatement ms,
Object parameterObject,
RowBounds rowBounds,
BoundSql boundSql) {
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());// Mapper ID
cacheKey.update(rowBounds.getOffset());// 分頁(yè)offset
cacheKey.update(rowBounds.getLimit());// 分頁(yè)limit
cacheKey.update(boundSql.getSql());// SQL語(yǔ)句

// 參數(shù)
List<ParameterMapping> parameterMappings =
boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry =
ms.getConfiguration().getTypeHandlerRegistry();

for (ParameterMapping parameterMapping : parameterMappings) {
Object value;
String propertyName = parameterMapping.getProperty();
// ... 獲取參數(shù)值邏輯
cacheKey.update(value);
}

// 環(huán)境ID
if (ms.getConfiguration().getEnvironment() != null) {
cacheKey.update(ms.getConfiguration()
.getEnvironment().getId());
}

return cacheKey;
}
}

2.4 緩存裝飾器鏈的實(shí)現(xiàn)

/**
* 裝飾器模式的典型應(yīng)用
* 配置:<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
* 實(shí)際創(chuàng)建的緩存對(duì)象鏈:
* SynchronizedCache
*-> LoggingCache
*-> SerializedCache
*-> LruCache
*-> PerpetualCache
*/
public class CacheBuilder {
private Class<? extends Cache> implementation = PerpetualCache.class;
private final List<Class<? extends Cache>> decorators = new ArrayList<>();
private Class<? extends Cache> evictionClass;
private Long flushInterval;
private Integer size;
private boolean readWrite;
private Properties properties;

public Cache build() {
// 1. 創(chuàng)建基礎(chǔ)緩存
Cache cache = newBaseCacheInstance(implementation, id);
setCacheProperties(cache);

// 2. 根據(jù)配置添加裝飾器
if (PerpetualCache.class.equals(cache.getClass())) {
for (Class<? extends Cache> decorator : decorators) {
cache = newCacheDecoratorInstance(decorator, cache);
setCacheProperties(cache);
}
cache = setStandardDecorators(cache);
}

return cache;
}

private Cache setStandardDecorators(Cache cache) {
try {
MetaObject metaCache = SystemMetaObject.forObject(cache);

// 添加序列化裝飾器(如果readOnly=false)
if (!readWrite) {
cache = new SerializedCache(cache);
}

// 添加日志裝飾器
cache = new LoggingCache(cache);

// 添加同步裝飾器
cache = new SynchronizedCache(cache);

// 添加定時(shí)清理裝飾器
if (flushInterval != null) {
cache = new ScheduledCache(cache);
((ScheduledCache) cache).setClearInterval(flushInterval);
}

// 添加LRU/FIFO等裝飾器
if (size != null && size > 0) {
cache = new LruCache(cache);
((LruCache) cache).setSize(size);
}

return cache;
} catch (Exception e) {
throw new CacheException("Error building standard cache decorators.", e);
}
}
}

2.5 TransactionalCacheManager:事務(wù)性緩存管理

/**
* 管理事務(wù)中的緩存操作
* 關(guān)鍵:只有事務(wù)提交后,緩存才會(huì)真正生效
*/
public class TransactionalCacheManager {
// key: Cache對(duì)象, value: TransactionalCache
private final Map<Cache, TransactionalCache> transactionalCaches =
new HashMap<>();

public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}

public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}

public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}

public void rollback() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.rollback();
}
}

private TransactionalCache getTransactionalCache(Cache cache) {
return transactionalCaches.computeIfAbsent(cache,
key -> new TransactionalCache(cache));
}
}

public class TransactionalCache implements Cache {
private final Cache delegate;
private boolean clearOnCommit;// 提交時(shí)是否清空
private final Map<Object, Object> entriesToAddOnCommit = new HashMap<>();
private final Set<Object> entriesMissedInCache = new HashSet<>();

@Override
public void putObject(Object key, Object value) {
// 不直接放入delegate,先放入臨時(shí)map
entriesToAddOnCommit.put(key, value);
}

public void commit() {
if (clearOnCommit) {
delegate.clear();
}
// 提交時(shí)批量放入緩存
flushPendingEntries();
reset();
}

private void flushPendingEntries() {
for (Map.Entry<Object, Object> entry :
entriesToAddOnCommit.entrySet()) {
delegate.putObject(entry.getKey(), entry.getValue());
}
// 處理未命中的查詢
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null); // 緩存null值
}
}
}
}

3. 詳細(xì)使用案例

3.1 基礎(chǔ)配置與使用

<!-- mybatis-config.xml -->
<configuration>
<settings>
<!-- 開(kāi)啟二級(jí)緩存(默認(rèn)true) -->
<setting name="cacheEnabled" value="true"/>
<!-- 本地緩存作用域 -->
<setting name="localCacheScope" value="SESSION"/>
</settings>

<!-- 使用第三方緩存 -->
<typeAliases>
<typeAlias type="org.mybatis.caches.ehcache.EhcacheCache"
alias="EH_CACHE"/>
</typeAliases>
</configuration>

<!-- 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">

<!-- 開(kāi)啟二級(jí)緩存并詳細(xì)配置 -->
<cache
type="org.mybatis.caches.ehcache.EhcacheCache"
eviction="LRU"
flushInterval="60000"
size="1000"
readOnly="false"
blocking="false"
properties="timeToLiveSeconds=3600,timeToIdleSeconds=1800"/>

<!-- 結(jié)果映射 -->
<resultMap id="userResultMap" type="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="email" column="email"/>
<!-- 注意:關(guān)聯(lián)對(duì)象需要單獨(dú)處理緩存 -->
<association property="department" column="dept_id"
select="com.example.mapper.DeptMapper.selectById"
fetchType="lazy"/>
</resultMap>

<!-- 使用緩存的查詢 -->
<select id="selectById" resultMap="userResultMap" useCache="true">
SELECT * FROM users WHERE id = #{id}
</select>

<!-- 不使用緩存的查詢 -->
<select id="selectForUpdate" resultMap="userResultMap" useCache="false">
SELECT * FROM users WHERE id = #{id} FOR UPDATE
</select>

<!-- 執(zhí)行前刷新緩存的查詢 -->
<select id="selectAfterRefresh" resultMap="userResultMap"
flushCache="true">
SELECT * FROM users WHERE id = #{id}
</select>

<!-- 更新操作,默認(rèn)flushCache="true" -->
<update id="updateUser" parameterType="User">
UPDATE users
SET username=#{username}, email=#{email}
WHERE id=#{id}
</update>
</mapper>

3.2 注解方式配置

package com.example.mapper;

import org.apache.ibatis.annotations.*;
import org.apache.ibatis.cache.decorators.LruCache;
import org.apache.ibatis.cache.impl.PerpetualCache;
import org.apache.ibatis.mapping.StatementType;

// 方式1:直接使用@CacheNamespace
@CacheNamespace(
implementation = PerpetualCache.class,// 實(shí)現(xiàn)類(lèi)
eviction = LruCache.class,// 回收策略
flushInterval = 60000L,// 刷新間隔
size = 512,// 緩存大小
readWrite = true,// 讀寫(xiě)緩存
blocking = false,// 是否阻塞
properties = {
@Property(name = "timeToLiveSeconds", value = "3600"),
@Property(name = "timeToIdleSeconds", value = "1800")
}
)
// 方式2:引用XML配置的緩存
// @CacheNamespaceRef(UserMapper.class)
public interface UserMapper {

// 默認(rèn)使用緩存
@Select("SELECT * FROM users WHERE id = #{id}")
@Results(id = "userResult", value = {
@Result(property = "id", column = "id", id = true),
@Result(property = "username", column = "username"),
@Result(property = "email", column = "email")
})
User selectById(Long id);

// 明確指定使用緩存
@Select("SELECT * FROM users WHERE username = #{username}")
@Options(useCache = true, flushCache = Options.FlushCachePolicy.FALSE)
User selectByUsername(String username);

// 明確指定不使用緩存
@Select("SELECT * FROM users WHERE id = #{id}")
@Options(useCache = false)
User selectForUpdate(Long id);

// 更新操作,刷新緩存
@Update("UPDATE users SET username = #{username} WHERE id = #{id}")
@Options(flushCache = Options.FlushCachePolicy.TRUE)
int updateUsername(@Param("id") Long id, @Param("username") String username);

// 存儲(chǔ)過(guò)程調(diào)用,緩存處理
@Select(value = "{CALL get_user_by_id(#{id, mode=IN})}",
statementType = StatementType.CALLABLE)
@Options(useCache = true)
User selectByProcedure(Long id);
}

3.3 復(fù)雜場(chǎng)景:關(guān)聯(lián)查詢緩存

/**
* 關(guān)聯(lián)查詢的緩存處理方案
*/
public class AssociationCacheExample {

// DeptMapper.java
@CacheNamespace
public interface DeptMapper {
@Select("SELECT * FROM departments WHERE id = #{id}")
Department selectById(Long id);
}

// UserMapper.java - 方案1:嵌套查詢(N+1問(wèn)題)
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
@Results({
@Result(property = "id", column = "id"),
@Result(property = "deptId", column = "dept_id"),
@Result(property = "department", column = "dept_id",
one = @One(select = "com.example.mapper.DeptMapper.selectById",
fetchType = FetchType.LAZY))
})
User selectUserWithDept(Long id);
}

// 方案2:聯(lián)合查詢(緩存處理)
@Select("SELECT u.*, d.name as dept_name, d.code as dept_code " +
"FROM users u LEFT JOIN departments d ON u.dept_id = d.id " +
"WHERE u.id = #{id}")
@Results(id = "userWithDept", value = {
@Result(property = "id", column = "id", id = true),
@Result(property = "username", column = "username"),
@Result(property = "department", javaType = Department.class,
resultMap = "com.example.mapper.DeptMapper.deptResult")
})
@Options(useCache = true)
User selectUserWithDeptJoin(Long id);

/**
* 測(cè)試關(guān)聯(lián)查詢緩存
*/
@Test
public void testAssociationCache() {
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);

// 第一次查詢:會(huì)查詢user和dept
System.out.println("第一次查詢:");
User user1 = mapper.selectUserWithDept(1L);
session.commit(); // 必須提交

// 第二次查詢:user從緩存獲取,dept可能從緩存獲取
System.out.println("\n第二次查詢:");
User user2 = mapper.selectUserWithDept(1L);

// 驗(yàn)證緩存
System.out.println("User對(duì)象相同:" + (user1 == user2));
System.out.println("Dept對(duì)象相同:" +
(user1.getDepartment() == user2.getDepartment()));
}
}
}

3.4 自定義緩存實(shí)現(xiàn)

/**
* 自定義Redis緩存實(shí)現(xiàn)
*/
package com.example.cache;

import org.apache.ibatis.cache.Cache;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class CustomRedisCache implements Cache {

private final String id;
private final JedisPool jedisPool;
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

// 序列化器
private final Serializer serializer = new JdkSerializer();

public CustomRedisCache(String id) {
this.id = id;
this.jedisPool = new JedisPool("localhost", 6379);
}

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

@Override
public void putObject(Object key, Object value) {
try (Jedis jedis = jedisPool.getResource()) {
byte[] keyBytes = serializer.serialize(key);
byte[] valueBytes = serializer.serialize(value);

jedis.setex(keyBytes, 3600, valueBytes); // 1小時(shí)過(guò)期

// 維護(hù)key集合,用于clear操作
jedis.sadd(getKeysSetName(), keyBytes);
}
}

@Override
public Object getObject(Object key) {
try (Jedis jedis = jedisPool.getResource()) {
byte[] keyBytes = serializer.serialize(key);
byte[] valueBytes = jedis.get(keyBytes);

return valueBytes == null ? null : serializer.deserialize(valueBytes);
}
}

@Override
public Object removeObject(Object key) {
try (Jedis jedis = jedisPool.getResource()) {
byte[] keyBytes = serializer.serialize(key);
byte[] valueBytes = jedis.get(keyBytes);

jedis.del(keyBytes);
jedis.srem(getKeysSetName(), keyBytes);

return valueBytes == null ? null : serializer.deserialize(valueBytes);
}
}

@Override
public void clear() {
try (Jedis jedis = jedisPool.getResource()) {
String keysSetName = getKeysSetName();
Set<byte[]> keys = jedis.smembers(keysSetName.getBytes());

if (!keys.isEmpty()) {
jedis.del(keys.toArray(new byte[0][]));
}
jedis.del(keysSetName);
}
}

@Override
public int getSize() {
try (Jedis jedis = jedisPool.getResource()) {
Long size = jedis.scard(getKeysSetName());
return size != null ? size.intValue() : 0;
}
}

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

private String getKeysSetName() {
return id + ":keys";
}

// 序列化接口
interface Serializer {
byte[] serialize(Object obj);
Object deserialize(byte[] bytes);
}

// JDK序列化實(shí)現(xiàn)
static class JdkSerializer implements Serializer {
@Override
public byte[] serialize(Object obj) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(obj);
return baos.toByteArray();
} catch (IOException e) {
throw new CacheException("Serialization failed", e);
}
}

@Override
public Object deserialize(byte[] bytes) {
if (bytes == null) return null;
try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais)) {
return ois.readObject();
} catch (IOException | ClassNotFoundException e) {
throw new CacheException("Deserialization failed", e);
}
}
}
}

3.5 分布式環(huán)境下的緩存同步

/**
* 分布式緩存同步解決方案
*/
public class DistributedCacheSync {

/**
* 方案1:使用Redis Pub/Sub同步緩存失效
*/
@Component
public class CacheInvalidationListener {

private final JedisPool jedisPool;
private final SqlSessionFactory sqlSessionFactory;

@PostConstruct
public void init() {
new Thread(() -> {
try (Jedis jedis = jedisPool.getResource()) {
jedis.subscribe(new JedisPubSub() {
@Override
public void onMessage(String channel, String message) {
// 收到緩存失效消息
handleCacheInvalidation(message);
}
}, "cache:invalidation");
}
}).start();
}

private void handleCacheInvalidation(String message) {
// 消息格式:mapperName:cacheKey
String[] parts = message.split(":");
if (parts.length == 2) {
String mapperName = parts[0];
String cacheKey = parts[1];

try (SqlSession session = sqlSessionFactory.openSession()) {
Configuration config = session.getConfiguration();
Cache cache = config.getCache(mapperName);

if (cache != null) {
// 清除指定緩存
cache.removeObject(cacheKey);
System.out.println("清除緩存:" + mapperName + " - " + cacheKey);
}
}
}
}

/**
* 發(fā)送緩存失效通知
*/
public void notifyCacheInvalidation(String mapperName, String cacheKey) {
try (Jedis jedis = jedisPool.getResource()) {
String message = mapperName + ":" + cacheKey;
jedis.publish("cache:invalidation", message);
}
}
}

/**
* 方案2:自定義Cache實(shí)現(xiàn),集成分布式鎖
*/
public class DistributedLockCache implements Cache {

private final Cache delegate;
private final RedissonClient redisson;

@Override
public void putObject(Object key, Object value) {
String lockKey = "lock:cache:" + getId() + ":" + key.hashCode();
RLock lock = redisson.getLock(lockKey);

try {
lock.lock(5, TimeUnit.SECONDS); // 獲取分布式鎖
delegate.putObject(key, value);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
}

3.6 性能監(jiān)控與調(diào)優(yōu)

/**
* 二級(jí)緩存性能監(jiān)控工具
*/
public class CachePerformanceMonitor {

private static final Map<String, CacheMetrics> metricsMap =
new ConcurrentHashMap<>();

@Data
public static class CacheMetrics {
private String cacheId;
private AtomicLong hitCount = new AtomicLong();
private AtomicLong missCount = new AtomicLong();
private AtomicLong putCount = new AtomicLong();
private AtomicLong evictionCount = new AtomicLong();
private AtomicLong totalTime = new AtomicLong();

public double getHitRate() {
long total = hitCount.get() + missCount.get();
return total == 0 ? 0 : (double) hitCount.get() / total;
}

public double getAverageAccessTime() {
long totalAccess = hitCount.get() + missCount.get();
return totalAccess == 0 ? 0 : (double) totalTime.get() / totalAccess;
}
}

/**
* 監(jiān)控裝飾器
*/
public static class MonitoredCache implements Cache {
private final Cache delegate;
private final CacheMetrics metrics;

public MonitoredCache(Cache delegate) {
this.delegate = delegate;
this.metrics = metricsMap.computeIfAbsent(delegate.getId(),
id -> new CacheMetrics());
this.metrics.setCacheId(delegate.getId());
}

@Override
public Object getObject(Object key) {
long start = System.nanoTime();
try {
Object value = delegate.getObject(key);
if (value != null) {
metrics.getHitCount().incrementAndGet();
} else {
metrics.getMissCount().incrementAndGet();
}
return value;
} finally {
long time = System.nanoTime() - start;
metrics.getTotalTime().addAndGet(time);
}
}

@Override
public void putObject(Object key, Object value) {
long start = System.nanoTime();
try {
delegate.putObject(key, value);
metrics.getPutCount().incrementAndGet();
} finally {
long time = System.nanoTime() - start;
metrics.getTotalTime().addAndGet(time);
}
}

// 其他方法...
}

/**
* 生成監(jiān)控報(bào)告
*/
public static void generateReport() {
System.out.println("=== 二級(jí)緩存性能報(bào)告 ===");
System.out.printf("%-30s %-10s %-10s %-10s %-10s%n",
"Cache ID", "Hit Rate", "Hit", "Miss", "Avg Time(ns)");
System.out.println("-".repeat(80));

for (CacheMetrics metrics : metricsMap.values()) {
System.out.printf("%-30s %-10.2f %-10d %-10d %-10.2f%n",
metrics.getCacheId(),
metrics.getHitRate() * 100,
metrics.getHitCount().get(),
metrics.getMissCount().get(),
metrics.getAverageAccessTime());
}
}
}

/**
* 使用AOP進(jìn)行緩存監(jiān)控
*/
@Aspect
@Component
public class CacheMonitorAspect {

@Pointcut("execution(* org.apache.ibatis.cache.Cache.getObject(..))")
public void cacheGetPointcut() {}

@Around("cacheGetPointcut()")
public Object monitorCacheAccess(ProceedingJoinPoint pjp) throws Throwable {
Cache cache = (Cache) pjp.getTarget();
Object key = pjp.getArgs()[0];

String cacheId = cache.getId();
String keyStr = key.toString();

long start = System.currentTimeMillis();
Object result = pjp.proceed();
long time = System.currentTimeMillis() - start;

// 記錄到日志或監(jiān)控系統(tǒng)
LogRecord record = new LogRecord(cacheId, keyStr,
result != null ? "HIT" : "MISS", time);

// 發(fā)送到監(jiān)控系統(tǒng)
sendToMonitor(record);

return result;
}
}

4. 最佳實(shí)踐總結(jié)

4.1 配置建議

<!-- 生產(chǎn)環(huán)境推薦配置 -->
<cache
type="org.mybatis.caches.redis.RedisCache"
eviction="LRU"
flushInterval="0"<!-- 分布式環(huán)境下不自動(dòng)刷新 -->
size="0"<!-- Redis管理大小 -->
readOnly="false"
blocking="true"<!-- 防止緩存擊穿 -->
properties="
maxTotal=100,
maxIdle=10,
minIdle=5,
testOnBorrow=true,
timeBetweenEvictionRunsMillis=30000
"
/>

4.2 代碼規(guī)范

// 1. 明確的事務(wù)邊界
@Transactional
public User getUserWithCache(Long id) {
// 必須在一個(gè)事務(wù)內(nèi)
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectById(id);
session.commit();// 關(guān)鍵:必須提交!
return user;
}
}

// 2. 批量操作處理
@Transactional
public void batchUpdateUsers(List<User> users) {
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);

for (User user : users) {
mapper.updateUser(user);
// 每100條提交一次,避免緩存過(guò)大
if (i % 100 == 0) {
session.commit();
session.clearCache();// 可選:清空緩存
}
}
session.commit();
}
}

4.3 常見(jiàn)問(wèn)題解決方案

問(wèn)題現(xiàn)象解決方案
緩存穿透查詢不存在的數(shù)據(jù)1. 緩存空對(duì)象
2. 布隆過(guò)濾器
緩存擊穿熱點(diǎn)key失效1. 永不過(guò)期
2. 互斥鎖
緩存雪崩大量key同時(shí)失效1. 隨機(jī)過(guò)期時(shí)間
2. 二級(jí)緩存
數(shù)據(jù)不一致緩存與DB不一致1. 合理設(shè)置過(guò)期時(shí)間
2. 更新時(shí)清除緩存

5. 源碼調(diào)試技巧

5.1 調(diào)試緩存流程

// 在IDE中設(shè)置斷點(diǎn):
// 1. CachingExecutor.query() - 入口
// 2. TransactionalCacheManager.getObject() - 獲取緩存
// 3. CacheKey.update() - 查看緩存鍵生成
// 4. BaseExecutor.createCacheKey() - 創(chuàng)建緩存鍵

// 啟用MyBatis日志
org.apache.ibatis.cache.decorators.LoggingCache.level = DEBUG
org.apache.ibatis.executor.CachingExecutor.level = TRACE

5.2 查看緩存狀態(tài)

public class CacheInspector {

public static void inspectCache(SqlSessionFactory factory) {
Configuration config = factory.getConfiguration();

// 獲取所有緩存
Map<String, Cache> caches = config.getCaches();

for (Map.Entry<String, Cache> entry : caches.entrySet()) {
System.out.println("Cache: " + entry.getKey());
Cache cache = entry.getValue();

// 獲取緩存裝飾器鏈
while (cache != null) {
System.out.println("|- " + cache.getClass().getSimpleName());

// 通過(guò)反射獲取delegate
try {
Field delegateField = cache.getClass()
.getDeclaredField("delegate");
delegateField.setAccessible(true);
cache = (Cache) delegateField.get(cache);
} catch (Exception e) {
break;
}
}
}
}
}

總結(jié)

MyBatis二級(jí)緩存的源碼設(shè)計(jì)體現(xiàn)了多個(gè)設(shè)計(jì)模式的精妙應(yīng)用:

  1. 裝飾器模式:靈活組合緩存功能
  2. 模板方法模式:CachingExecutor的查詢流程
  3. 策略模式:不同的緩存淘汰策略
  4. 代理模式:TransactionalCache的事務(wù)管理

在實(shí)際使用中,需要根據(jù)業(yè)務(wù)場(chǎng)景選擇合適的緩存策略,并注意:

  • 事務(wù)提交后緩存才生效
  • 關(guān)聯(lián)查詢的緩存處理
  • 分布式環(huán)境的一致性保證
  • 性能監(jiān)控和調(diào)優(yōu)

通過(guò)深入理解源碼,可以更好地利用二級(jí)緩存提升系統(tǒng)性能,同時(shí)避免常見(jiàn)的數(shù)據(jù)一致性問(wèn)題。

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • spring后置通知@AfterReturning的使用

    spring后置通知@AfterReturning的使用

    這篇文章主要介紹了spring后置通知@AfterReturning的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • Java Semaphore實(shí)現(xiàn)高并發(fā)場(chǎng)景下的流量控制

    Java Semaphore實(shí)現(xiàn)高并發(fā)場(chǎng)景下的流量控制

    在java開(kāi)發(fā)的工作中是否會(huì)出現(xiàn)這樣的場(chǎng)景,你需要實(shí)現(xiàn)一些異步運(yùn)行的任務(wù),該任務(wù)可能存在消耗大量?jī)?nèi)存的情況,所以需要對(duì)任務(wù)進(jìn)行并發(fā)控制。本文將介紹通過(guò)Semaphore類(lèi)優(yōu)雅的實(shí)現(xiàn)并發(fā)控制,感興趣的可以了解一下
    2021-12-12
  • Java使用Armitage進(jìn)行滲透測(cè)試的方法

    Java使用Armitage進(jìn)行滲透測(cè)試的方法

    在網(wǎng)絡(luò)安全領(lǐng)域,滲透測(cè)試是一種重要的安全評(píng)估手段,它通過(guò)模擬惡意黑客的行為來(lái)檢測(cè)目標(biāo)系統(tǒng)是否存在安全漏洞,Armitage是一個(gè)基于Java的圖形化滲透測(cè)試工具,本文將介紹如何使用Armitage進(jìn)行滲透測(cè)試,需要的朋友可以參考下
    2024-12-12
  • java實(shí)現(xiàn)學(xué)生選課系統(tǒng)

    java實(shí)現(xiàn)學(xué)生選課系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)學(xué)生選課系統(tǒng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-02-02
  • 使用SpringEvent解決WebUploader大文件上傳解耦問(wèn)題

    使用SpringEvent解決WebUploader大文件上傳解耦問(wèn)題

    Spring Event是Spring框架內(nèi)建的一種發(fā)布/訂閱模式的實(shí)現(xiàn),它允許應(yīng)用內(nèi)部不同組件之間通過(guò)事件進(jìn)行通信,本文以WebUploader大文件上傳組件為例,在大文件處理的場(chǎng)景中使用SpringEvent的事件發(fā)布機(jī)制,靈活的擴(kuò)展對(duì)文件的處理需求,需要的朋友可以參考下
    2024-07-07
  • SpringBoot集成Curator實(shí)現(xiàn)Zookeeper基本操作的代碼示例

    SpringBoot集成Curator實(shí)現(xiàn)Zookeeper基本操作的代碼示例

    Zookeeper是一個(gè)Apache開(kāi)源的分布式的應(yīng)用,為系統(tǒng)架構(gòu)提供協(xié)調(diào)服務(wù),ZooKeeper的目標(biāo)就是封裝好復(fù)雜易出錯(cuò)的關(guān)鍵服務(wù),將簡(jiǎn)單易用的接口和性能高效、功能穩(wěn)定的系統(tǒng)提供給用戶,本文給大家介紹了SpringBoot集成Curator實(shí)現(xiàn)Zookeeper基本操作,需要的朋友可以參考下
    2024-05-05
  • java文件讀寫(xiě)操作實(shí)例詳解

    java文件讀寫(xiě)操作實(shí)例詳解

    java的io流讀取數(shù)據(jù)使用io流讀取文件和向文件中寫(xiě)數(shù)據(jù),這篇文章主要給大家介紹了關(guān)于java文件讀寫(xiě)操作的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-02-02
  • Java文件基本操作總結(jié)

    Java文件基本操作總結(jié)

    今天給大家?guī)?lái)的是關(guān)于Java基礎(chǔ)的相關(guān)知識(shí),文章圍繞著Java文件操作展開(kāi),文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下
    2021-06-06
  • 詳解JAVA后端實(shí)現(xiàn)統(tǒng)一掃碼支付:微信篇

    詳解JAVA后端實(shí)現(xiàn)統(tǒng)一掃碼支付:微信篇

    本篇文章主要介紹了詳解JAVA后端實(shí)現(xiàn)統(tǒng)一掃碼支付:微信篇,這里整理了詳細(xì)的代碼,有需要的小伙伴可以參考下。
    2017-01-01
  • Java中的private、protected、public和default的區(qū)別(詳解)

    Java中的private、protected、public和default的區(qū)別(詳解)

    下面小編就為大家?guī)?lái)一篇Java中的private、protected、public和default的區(qū)別(詳解)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2016-11-11

最新評(píng)論

禄劝| 海南省| 环江| 墨竹工卡县| 马公市| 南城县| 宜春市| 松滋市| 曲靖市| 宁武县| 沈阳市| 福州市| 团风县| 衡东县| 开平市| 泸定县| 宣城市| 大厂| 马尔康县| 贺兰县| 沙田区| 泾源县| 常德市| 汕尾市| 舟山市| 乾安县| 云阳县| 灵寿县| 宜兰市| 留坝县| 灯塔市| 香河县| 宜君县| 武川县| 黄平县| 万安县| 绥宁县| 喀喇| 长顺县| 阳谷县| 藁城市|