MyBatis一二級(jí)緩存機(jī)制全解析
引言
在現(xiàn)代Web應(yīng)用中,數(shù)據(jù)庫(kù)訪問(wèn)往往是性能瓶頸之一。MyBatis作為流行的持久層框架,其緩存機(jī)制是提升應(yīng)用性能的關(guān)鍵特性。理解MyBatis的一二級(jí)緩存不僅有助于優(yōu)化應(yīng)用性能,還能避免因緩存不當(dāng)導(dǎo)致的數(shù)據(jù)一致性問(wèn)題。本文將從基礎(chǔ)概念到高級(jí)原理,全方位解析MyBatis緩存機(jī)制。
一、緩存的基本概念:為什么需要緩存?
1.1 緩存的價(jià)值
想象一下,如果你每次需要知道時(shí)間都去天文臺(tái)查詢,效率會(huì)很低。相反,看一眼手表(緩存)就能立即獲取時(shí)間。MyBatis緩存扮演的就是這個(gè)“手表”的角色,它避免了頻繁訪問(wèn)數(shù)據(jù)庫(kù)(天文臺(tái)),極大提升了查詢效率。
1.2 緩存的經(jīng)濟(jì)學(xué)原理
- 時(shí)間局部性:剛被訪問(wèn)的數(shù)據(jù)很可能再次被訪問(wèn)
- 空間局部性:相鄰的數(shù)據(jù)很可能被一起訪問(wèn)
- 訪問(wèn)成本:內(nèi)存訪問(wèn)(納秒級(jí))vs 磁盤(pán)/網(wǎng)絡(luò)訪問(wèn)(毫秒級(jí))
二、一級(jí)緩存:SqlSession級(jí)別的緩存
2.1 什么是SqlSession?
在深入一級(jí)緩存前,需要先理解SqlSession。SqlSession不是數(shù)據(jù)庫(kù)連接(Connection),而是一次數(shù)據(jù)庫(kù)對(duì)話的抽象:
// SqlSession相當(dāng)于一次完整對(duì)話,不是一通電話
SqlSession session = sqlSessionFactory.openSession();
try {
// 對(duì)話中的多次查詢
userMapper.getUser(1); // 第一次查詢
orderMapper.getOrders(1); // 第二次查詢
accountMapper.getBalance(1); // 第三次查詢
session.commit(); // 確認(rèn)對(duì)話內(nèi)容
} finally {
session.close(); // 結(jié)束對(duì)話
}2.2 一級(jí)緩存的核心特性
作用范圍:SqlSession內(nèi)部(一次對(duì)話)
默認(rèn)狀態(tài):自動(dòng)開(kāi)啟,無(wú)法關(guān)閉
生命周期:隨SqlSession創(chuàng)建而創(chuàng)建,隨其關(guān)閉而銷(xiāo)毀
2.3 一級(jí)緩存的工作原理
// 示例代碼展示一級(jí)緩存行為
public void demonstrateLevel1Cache() {
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
System.out.println("第一次查詢用戶1:");
User user1 = mapper.selectById(1); // 發(fā)SQL:SELECT * FROM user WHERE id=1
System.out.println("第二次查詢用戶1:");
User user2 = mapper.selectById(1); // 不發(fā)SQL!從一級(jí)緩存讀取
System.out.println("查詢用戶2:");
User user3 = mapper.selectById(2); // 發(fā)SQL:參數(shù)不同,緩存未命中
System.out.println("修改用戶1:");
mapper.updateUser(user1); // 清空一級(jí)緩存
System.out.println("再次查詢用戶1:");
User user4 = mapper.selectById(1); // 發(fā)SQL:緩存被清空
session.close();
}2.4 一級(jí)緩存的數(shù)據(jù)結(jié)構(gòu)
一級(jí)緩存的實(shí)現(xiàn)非常簡(jiǎn)單直接:
// 一級(jí)緩存的核心實(shí)現(xiàn)類
public class PerpetualCache implements Cache {
// 核心:就是一個(gè)ConcurrentHashMap!
private final Map<Object, Object> cache = new ConcurrentHashMap<>();
@Override
public void putObject(Object key, Object value) {
cache.put(key, value); // 簡(jiǎn)單的Map.put()
}
@Override
public Object getObject(Object key) {
return cache.get(key); // 簡(jiǎn)單的Map.get()
}
}緩存Key的生成規(guī)則:
// CacheKey包含以下要素,決定兩個(gè)查詢是否"相同" // 1. Mapper Id(namespace + method) // 2. 分頁(yè)參數(shù)(offset, limit) // 3. SQL語(yǔ)句 // 4. 參數(shù)值 // 5. 環(huán)境Id // 這意味著:即使SQL相同,參數(shù)不同,也會(huì)生成不同的CacheKey
2.5 一級(jí)緩存的失效場(chǎng)景
- 執(zhí)行任何UPDATE/INSERT/DELETE操作
- 手動(dòng)調(diào)用clearCache()
- 設(shè)置flushCache="true"
- SqlSession關(guān)閉
- 查詢參數(shù)變化(因?yàn)镃acheKey不同)
三、二級(jí)緩存:Mapper級(jí)別的全局緩存
3.1 二級(jí)緩存的核心特性
作用范圍:Mapper級(jí)別(跨SqlSession共享)
默認(rèn)狀態(tài):默認(rèn)關(guān)閉,需要手動(dòng)開(kāi)啟
生命周期:隨應(yīng)用運(yùn)行而存在
3.2 二級(jí)緩存的配置
<!-- 1. 全局配置開(kāi)啟二級(jí)緩存 -->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<!-- 2. Mapper XML中配置 -->
<mapper namespace="com.example.UserMapper">
<!-- 基本配置 -->
<cache/>
<!-- 詳細(xì)配置 -->
<cache
eviction="LRU" <!-- 淘汰策略 -->
flushInterval="60000" <!-- 刷新間隔(毫秒) -->
size="1024" <!-- 緩存對(duì)象數(shù) -->
readOnly="true" <!-- 是否只讀 -->
blocking="false"/> <!-- 是否阻塞 -->
</mapper>
<!-- 3. 在具體查詢上使用緩存 -->
<select id="selectById" resultType="User" useCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
<!-- 4. 增刪改操作刷新緩存 -->
<update id="updateUser" flushCache="true">
UPDATE user SET name = #{name} WHERE id = #{id}
</update>3.3 二級(jí)緩存的數(shù)據(jù)結(jié)構(gòu)
二級(jí)緩存不像一級(jí)緩存那么簡(jiǎn)單,它采用了裝飾器模式:
二級(jí)緩存裝飾器鏈(層層包裝): ┌─────────────────────────┐ │ SerializedCache │ ← 序列化存儲(chǔ) │ LoggingCache │ ← 日志統(tǒng)計(jì) │ SynchronizedCache │ ← 線程安全 │ LruCache │ ← LRU淘汰 │ PerpetualCache │ ← 基礎(chǔ)HashMap └─────────────────────────┘
每個(gè)裝飾器都有特定功能:
- PerpetualCache:基礎(chǔ)存儲(chǔ),使用HashMap
- LruCache:最近最少使用淘汰
- SynchronizedCache:保證線程安全
- LoggingCache:記錄命中率
- SerializedCache:序列化對(duì)象,防止修改
3.4 二級(jí)緩存的工作流程
public void demonstrateLevel2Cache() {
// 用戶A查詢(第一個(gè)訪問(wèn)者)
SqlSession sessionA = sqlSessionFactory.openSession();
UserMapper mapperA = sessionA.getMapper(UserMapper.class);
User user1 = mapperA.selectById(1); // 查詢數(shù)據(jù)庫(kù)
sessionA.close(); // 關(guān)鍵:關(guān)閉時(shí)才會(huì)寫(xiě)入二級(jí)緩存
// 用戶B查詢(不同SqlSession)
SqlSession sessionB = sqlSessionFactory.openSession();
UserMapper mapperB = sessionB.getMapper(UserMapper.class);
User user2 = mapperB.selectById(1); // 從二級(jí)緩存讀取,不發(fā)SQL
// 管理員更新數(shù)據(jù)
SqlSession sessionC = sqlSessionFactory.openSession();
UserMapper mapperC = sessionC.getMapper(UserMapper.class);
mapperC.updateUser(user1); // 清空相關(guān)二級(jí)緩存
sessionC.commit();
sessionC.close();
// 用戶D再次查詢
SqlSession sessionD = sqlSessionFactory.openSession();
UserMapper mapperD = sessionD.getMapper(UserMapper.class);
User user3 = mapperD.selectById(1); // 緩存被清,重新查詢數(shù)據(jù)庫(kù)
sessionD.close();
}3.5 二級(jí)緩存的同步機(jī)制
二級(jí)緩存有一個(gè)重要特性:事務(wù)提交后才更新。這意味著:
// 場(chǎng)景:事務(wù)內(nèi)查詢,事務(wù)提交前其他會(huì)話看不到更新 SqlSession session1 = sqlSessionFactory.openSession(); UserMapper mapper1 = session1.getMapper(UserMapper.class); // 修改數(shù)據(jù),但未提交 mapper1.updateUser(user); // 此時(shí)二級(jí)緩存還未更新 // 另一個(gè)會(huì)話查詢 SqlSession session2 = sqlSessionFactory.openSession(); UserMapper mapper2 = session2.getMapper(UserMapper.class); User user2 = mapper2.selectById(1); // 可能讀到舊數(shù)據(jù)! session1.commit(); // 提交后,二級(jí)緩存才會(huì)更新 // 之后的新查詢才會(huì)看到新數(shù)據(jù)
四、一二級(jí)緩存的對(duì)比與選擇
4.1 核心差異對(duì)比
| 特性 | 一級(jí)緩存 | 二級(jí)緩存 |
|---|---|---|
| 作用范圍 | SqlSession內(nèi)部 | Mapper級(jí)別,跨SqlSession |
| 默認(rèn)狀態(tài) | 開(kāi)啟 | 關(guān)閉 |
| 數(shù)據(jù)結(jié)構(gòu) | 簡(jiǎn)單HashMap | 裝飾器鏈 |
| 共享性 | 私有,不共享 | 公共,所有會(huì)話共享 |
| 生命周期 | 隨SqlSession創(chuàng)建銷(xiāo)毀 | 隨應(yīng)用運(yùn)行持久存在 |
| 性能影響 | 極小(內(nèi)存訪問(wèn)) | 中等(可能有序列化開(kāi)銷(xiāo)) |
| 適用場(chǎng)景 | 會(huì)話內(nèi)重復(fù)查詢 | 跨會(huì)話共享查詢 |
4.2 生活化比喻
一級(jí)緩存 = 私人對(duì)話記憶
- 你和朋友的聊天內(nèi)容,只有你們兩人知道
- 聊天結(jié)束(SqlSession關(guān)閉),記憶逐漸模糊
二級(jí)緩存 = 公司公告欄
- 重要通知寫(xiě)在公告欄,所有員工都能看到
- 通知更新時(shí),需要擦掉舊的,寫(xiě)上新的
- 公告欄內(nèi)容持久存在,直到被更新
4.3 使用場(chǎng)景建議
適合一級(jí)緩存的場(chǎng)景:
// 場(chǎng)景1:方法內(nèi)多次查詢相同數(shù)據(jù)
public void processOrder(Long orderId) {
Order order1 = validateOrder(orderId); // 第一次查數(shù)據(jù)庫(kù)
Order order2 = calculateDiscount(orderId); // 走一級(jí)緩存
Order order3 = generateInvoice(orderId); // 走一級(jí)緩存
}
// 場(chǎng)景2:循環(huán)內(nèi)查詢
for (int i = 0; i < 100; i++) {
Config config = configMapper.getConfig("system_timeout");
// 只有第一次查數(shù)據(jù)庫(kù),后續(xù)99次走緩存
}適合二級(jí)緩存的場(chǎng)景:
// 場(chǎng)景1:讀多寫(xiě)少的配置數(shù)據(jù)
SystemConfig config = configMapper.getConfig("app_settings");
// 多個(gè)用戶頻繁讀取,很少修改
// 場(chǎng)景2:熱門(mén)商品信息
Product product = productMapper.getHotProduct(666);
// 商品詳情頁(yè),大量用戶訪問(wèn)同一商品
// 場(chǎng)景3:靜態(tài)字典數(shù)據(jù)
List<City> cities = addressMapper.getAllCities();
// 城市列表,很少變化不適合緩存的場(chǎng)景:
// 場(chǎng)景1:實(shí)時(shí)性要求高的數(shù)據(jù) Stock stock = stockMapper.getRealTimeStock(productId); // 庫(kù)存信息,需要實(shí)時(shí)準(zhǔn)確 // 場(chǎng)景2:頻繁更新的數(shù)據(jù) UserBalance balance = accountMapper.getBalance(userId); // 用戶余額,每次交易都變化 // 場(chǎng)景3:大數(shù)據(jù)量查詢 List<Log> logs = logMapper.getTodayLogs(); // 數(shù)據(jù)量大,緩存占用內(nèi)存過(guò)多
五、緩存的高級(jí)特性與原理
5.1 緩存淘汰策略
MyBatis提供了多種淘汰策略:
<cache eviction="策略類型" size="緩存大小">
可用策略:
- LRU(Least Recently Used):最近最少使用(默認(rèn))
- FIFO(First In First Out):先進(jìn)先出
- SOFT:軟引用,內(nèi)存不足時(shí)被GC回收
- WEAK:弱引用,GC時(shí)立即回收
5.2 LRU緩存的實(shí)現(xiàn)原理
public class LruCache implements Cache {
private final Cache delegate;
// 使用LinkedHashMap實(shí)現(xiàn)LRU
private Map<Object, Object> keyMap;
private Object eldestKey;
public void setSize(final int size) {
keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
boolean tooBig = size() > size;
if (tooBig) {
eldestKey = eldest.getKey();
}
return tooBig;
}
};
}
@Override
public Object getObject(Object key) {
// 訪問(wèn)時(shí)更新順序
keyMap.get(key);
return delegate.getObject(key);
}
}5.3 緩存查詢的完整流程
查詢執(zhí)行流程: 1. 請(qǐng)求到達(dá)CachingExecutor(二級(jí)緩存入口) 2. 生成CacheKey(包含SQL、參數(shù)等信息) 3. 查詢二級(jí)緩存 └─ 命中 → 返回結(jié)果 └─ 未命中 → 繼續(xù) 4. 查詢一級(jí)緩存 └─ 命中 → 返回結(jié)果,并放入二級(jí)緩存(事務(wù)提交時(shí)) └─ 未命中 → 繼續(xù) 5. 查詢數(shù)據(jù)庫(kù) 6. 結(jié)果存入一級(jí)緩存 7. 事務(wù)提交時(shí),一級(jí)緩存刷入二級(jí)緩存 8. 返回結(jié)果
六、緩存的最佳實(shí)踐與避坑指南
6.1 最佳實(shí)踐
1. 合理配置緩存大小
<!-- 根據(jù)數(shù)據(jù)特點(diǎn)設(shè)置合適的大小 --> <cache size="1024"/> <!-- 緩存1024個(gè)對(duì)象 -->
2. 設(shè)置合理的刷新間隔
<!-- 對(duì)于變化不頻繁但需要定期更新的數(shù)據(jù) --> <cache flushInterval="1800000"/> <!-- 30分鐘自動(dòng)刷新 -->
3. 選擇性使用緩存
<!-- 某些查詢跳過(guò)緩存 -->
<select id="getRealTimeData" useCache="false">
SELECT * FROM realtime_table
</select>
<!-- 某些查詢強(qiáng)制刷新緩存 -->
<select id="getImportantData" flushCache="true">
SELECT * FROM important_table
</select>4. 關(guān)聯(lián)查詢的緩存策略
<!-- 關(guān)聯(lián)查詢時(shí),使用cache-ref同步緩存 -->
<mapper namespace="com.example.UserMapper">
<cache/>
<!-- 其他配置 -->
</mapper>
<mapper namespace="com.example.OrderMapper">
<!-- 引用UserMapper的緩存 -->
<cache-ref namespace="com.example.UserMapper"/>
</mapper>6.2 常見(jiàn)問(wèn)題與解決方案
問(wèn)題1:臟讀問(wèn)題
場(chǎng)景:一個(gè)會(huì)話修改數(shù)據(jù)但未提交,另一個(gè)會(huì)話從二級(jí)緩存讀取到舊數(shù)據(jù)。
解決方案:
// 設(shè)置事務(wù)隔離級(jí)別
@Transactional(isolation = Isolation.READ_COMMITTED)
public void updateUser(User user) {
userMapper.updateUser(user);
}
// 或者在Mapper中設(shè)置flushCache
@Update("UPDATE user SET name=#{name} WHERE id=#{id}")
@Options(flushCache = Options.FlushCachePolicy.TRUE)
int updateUser(User user);問(wèn)題2:內(nèi)存溢出
場(chǎng)景:緩存大量數(shù)據(jù)導(dǎo)致JVM內(nèi)存不足。
解決方案:
- 設(shè)置合理的緩存大小和淘汰策略
- 使用軟引用/弱引用緩存
- 定期清理不活躍的緩存
問(wèn)題3:分布式環(huán)境緩存不一致
場(chǎng)景:多臺(tái)服務(wù)器,每臺(tái)有自己的緩存,數(shù)據(jù)不一致。
解決方案:
- 使用集中式緩存(Redis、Memcached)替代默認(rèn)二級(jí)緩存
- 實(shí)現(xiàn)自定義Cache接口:
public class RedisCache implements Cache {
private JedisPool jedisPool;
@Override
public void putObject(Object key, Object value) {
try (Jedis jedis = jedisPool.getResource()) {
jedis.set(serialize(key), serialize(value));
}
}
@Override
public Object getObject(Object key) {
try (Jedis jedis = jedisPool.getResource()) {
byte[] value = jedis.get(serialize(key));
return deserialize(value);
}
}
}問(wèn)題4:緩存穿透
場(chǎng)景:查詢不存在的數(shù)據(jù),每次都查數(shù)據(jù)庫(kù)。
解決方案:
// 緩存空對(duì)象
public User getUser(Long id) {
User user = userMapper.selectById(id);
if (user == null) {
// 緩存空值,設(shè)置短過(guò)期時(shí)間
cacheNullValue(id);
return null;
}
return user;
}6.3 監(jiān)控與調(diào)試
開(kāi)啟緩存日志
# 查看緩存命中情況 logging.level.org.mybatis=DEBUG logging.level.com.example.mapper=TRACE
監(jiān)控緩存命中率
// 獲取緩存統(tǒng)計(jì)信息
Cache cache = sqlSession.getConfiguration()
.getCache("com.example.UserMapper");
if (cache instanceof LoggingCache) {
LoggingCache loggingCache = (LoggingCache) cache;
System.out.println("命中次數(shù): " + loggingCache.getHitCount());
System.out.println("未命中次數(shù): " + loggingCache.getMissCount());
System.out.println("命中率: " +
(loggingCache.getHitCount() * 100.0 /
(loggingCache.getHitCount() + loggingCache.getMissCount())) + "%");
}
七、總結(jié)與思考
7.1 核心要點(diǎn)回顧
- 一級(jí)緩存:SqlSession級(jí)別,自動(dòng)開(kāi)啟,基于HashMap,簡(jiǎn)單高效
- 二級(jí)緩存:Mapper級(jí)別,需手動(dòng)開(kāi)啟,基于裝飾器模式,功能豐富
- 緩存Key:由SQL、參數(shù)等要素生成,決定查詢是否"相同"
- 事務(wù)同步:二級(jí)緩存在事務(wù)提交后才更新,避免臟讀
- 適用場(chǎng)景:根據(jù)數(shù)據(jù)特點(diǎn)選擇合適的緩存策略
7.2 設(shè)計(jì)思想啟示
MyBatis緩存設(shè)計(jì)體現(xiàn)了幾個(gè)重要軟件設(shè)計(jì)原則:
- 單一職責(zé)原則:每個(gè)緩存裝飾器只負(fù)責(zé)一個(gè)功能
- 開(kāi)閉原則:通過(guò)裝飾器模式,無(wú)需修改原有代碼即可擴(kuò)展功能
- 接口隔離:Cache接口定義清晰,便于自定義實(shí)現(xiàn)
7.3 實(shí)際應(yīng)用建議
在實(shí)際項(xiàng)目中:
- 從小開(kāi)始:先使用一級(jí)緩存,確有需要再開(kāi)啟二級(jí)緩存
- 測(cè)試驗(yàn)證:上線前充分測(cè)試緩存效果和內(nèi)存占用
- 監(jiān)控調(diào)整:生產(chǎn)環(huán)境監(jiān)控緩存命中率,根據(jù)實(shí)際情況調(diào)整配置
- 文檔記錄:記錄緩存配置和策略,便于團(tuán)隊(duì)協(xié)作和維護(hù)
7.4 未來(lái)展望
隨著微服務(wù)和云原生架構(gòu)的普及,MyBatis緩存也在演進(jìn):
- 分布式緩存集成:更好支持Redis等分布式緩存
- 多級(jí)緩存策略:本地緩存+分布式緩存的組合使用
- 智能緩存管理:基于訪問(wèn)模式的自動(dòng)緩存優(yōu)化
結(jié)語(yǔ)
MyBatis緩存機(jī)制是一個(gè)看似簡(jiǎn)單實(shí)則精妙的設(shè)計(jì)。理解它不僅能幫助我們優(yōu)化應(yīng)用性能,還能加深對(duì)緩存設(shè)計(jì)模式的理解。記住,緩存是提升性能的利器,但也可能成為數(shù)據(jù)一致的陷阱。合理使用、謹(jǐn)慎配置、持續(xù)監(jiān)控,才能讓緩存真正為應(yīng)用賦能。
緩存不是銀彈,而是需要精心調(diào)校的利器。 在實(shí)際開(kāi)發(fā)中,應(yīng)根據(jù)業(yè)務(wù)特點(diǎn)、數(shù)據(jù)特性和訪問(wèn)模式,選擇最合適的緩存策略,在性能與一致性之間找到最佳平衡點(diǎn)。
到此這篇關(guān)于MyBatis一二級(jí)緩存機(jī)制全解析的文章就介紹到這了,更多相關(guān)MyBatis一二級(jí)緩存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中MyBatis Plus知識(shí)點(diǎn)總結(jié)
在本篇文章里小編給大家整理一篇關(guān)于Java中MyBatis Plus知識(shí)點(diǎn)總結(jié),需要的朋友們參考下。2019-10-10
Mybatis-Plus多種批量插入方案對(duì)比小結(jié)
在項(xiàng)目中優(yōu)化Mybatis-Plus批量插入性能是關(guān)鍵,通過(guò)比較不同方案,本文就來(lái)介紹一下Mybatis-Plus多種批量插入方案對(duì)比小結(jié),感興趣都的可以了解一下2024-10-10
SpringBoot實(shí)現(xiàn)配置文件的替換
這篇文章主要介紹了SpringBoot實(shí)現(xiàn)配置文件的替換,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
java實(shí)現(xiàn)鮮花銷(xiāo)售系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)鮮花銷(xiāo)售系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06
Mybatis詳解動(dòng)態(tài)SQL以及單表多表查詢的應(yīng)用
MyBatis的動(dòng)態(tài)SQL是基于OGNL表達(dá)式的,它可以幫助我們方便的在SQL語(yǔ)句中實(shí)現(xiàn)某些邏輯,下面這篇文章主要給大家介紹了關(guān)于Mybatis超級(jí)強(qiáng)大的動(dòng)態(tài)SQL語(yǔ)句的相關(guān)資料,需要的朋友可以參考下2022-06-06
Java ForkJoin 框架全面解析:分而治之的并行編程藝術(shù)
本文介紹了Java Fork/JoinJoin框架用于并行執(zhí)行任務(wù)的思想、核心組件和使用方法,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2026-05-05

