Files
JChargePointProtocol/jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/CaffeineTransactionalCache.java
2025-03-04 10:42:17 +08:00

183 lines
5.2 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
package sanbing.jcpp.infrastructure.cache;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@RequiredArgsConstructor
public abstract class CaffeineTransactionalCache<K extends Serializable, V extends Serializable> implements TransactionalCache<K, V> {
@Getter
protected final String cacheName;
protected final Cache cache;
protected final Lock lock = new ReentrantLock();
private final Map<K, Set<UUID>> objectTransactions = new HashMap<>();
private final Map<UUID, CaffeineCacheTransaction<K, V>> transactions = new HashMap<>();
public CaffeineTransactionalCache(CacheManager cacheManager, String cacheName) {
this.cacheName = cacheName;
this.cache = Optional.ofNullable(cacheManager.getCache(cacheName))
.orElseThrow(() -> new IllegalArgumentException("Cache '" + cacheName + "' is not configured"));
}
@Override
public CacheValueWrapper<V> get(K key) {
return SimpleCacheValueWrapper.wrap(cache.get(key));
}
@Override
public void put(K key, V value) {
lock.lock();
try {
failAllTransactionsByKey(key);
cache.put(key, value);
} finally {
lock.unlock();
}
}
@Override
public void putIfAbsent(K key, V value) {
lock.lock();
try {
failAllTransactionsByKey(key);
doPutIfAbsent(key, value);
} finally {
lock.unlock();
}
}
@Override
public void evict(K key) {
lock.lock();
try {
failAllTransactionsByKey(key);
doEvict(key);
} finally {
lock.unlock();
}
}
@Override
public void evict(Collection<K> keys) {
lock.lock();
try {
keys.forEach(key -> {
failAllTransactionsByKey(key);
doEvict(key);
});
} finally {
lock.unlock();
}
}
@Override
public void evictOrPut(K key, V value) {
evict(key);
}
@Override
public CacheTransaction<K, V> newTransactionForKey(K key) {
return newTransaction(Collections.singletonList(key));
}
@Override
public CacheTransaction<K, V> newTransactionForKeys(List<K> keys) {
return newTransaction(keys);
}
void doPutIfAbsent(K key, V value) {
cache.putIfAbsent(key, value);
}
void doEvict(K key) {
cache.evict(key);
}
CacheTransaction<K, V> newTransaction(List<K> keys) {
lock.lock();
try {
var transaction = new CaffeineCacheTransaction<>(this, keys);
var transactionId = transaction.getId();
for (K key : keys) {
objectTransactions.computeIfAbsent(key, k -> new HashSet<>()).add(transactionId);
}
transactions.put(transactionId, transaction);
return transaction;
} finally {
lock.unlock();
}
}
public boolean commit(UUID trId, Map<K, V> pendingPuts) {
lock.lock();
try {
var tr = transactions.get(trId);
var success = !tr.isFailed();
if (success) {
for (K key : tr.getKeys()) {
Set<UUID> otherTransactions = objectTransactions.get(key);
if (otherTransactions != null) {
for (UUID otherTrId : otherTransactions) {
if (trId == null || !trId.equals(otherTrId)) {
transactions.get(otherTrId).setFailed(true);
}
}
}
}
pendingPuts.forEach(this::doPutIfAbsent);
}
removeTransaction(trId);
return success;
} finally {
lock.unlock();
}
}
void rollback(UUID id) {
lock.lock();
try {
removeTransaction(id);
} finally {
lock.unlock();
}
}
private void removeTransaction(UUID id) {
CaffeineCacheTransaction<K, V> transaction = transactions.remove(id);
if (transaction != null) {
for (var key : transaction.getKeys()) {
Set<UUID> transactions = objectTransactions.get(key);
if (transactions != null) {
transactions.remove(id);
if (transactions.isEmpty()) {
objectTransactions.remove(key);
}
}
}
}
}
protected void failAllTransactionsByKey(K key) {
Set<UUID> transactionsIds = objectTransactions.get(key);
if (transactionsIds != null) {
for (UUID otherTrId : transactionsIds) {
transactions.get(otherTrId).setFailed(true);
}
}
}
}