|
|
|
@ -20,6 +20,7 @@ import com.google.common.util.concurrent.ListenableFuture; |
|
|
|
import com.google.common.util.concurrent.ListeningExecutorService; |
|
|
|
import com.google.common.util.concurrent.MoreExecutors; |
|
|
|
import lombok.extern.slf4j.Slf4j; |
|
|
|
import org.apache.commons.lang3.tuple.Pair; |
|
|
|
import org.springframework.beans.factory.annotation.Value; |
|
|
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
|
|
|
import org.springframework.context.annotation.Primary; |
|
|
|
@ -147,47 +148,47 @@ public class CachedAttributesService implements AttributesService { |
|
|
|
return Futures.transformAsync(cacheExecutor.submit(() -> findCachedAttributes(entityId, scope, attributeKeys)), |
|
|
|
wrappedCachedAttributes -> { |
|
|
|
|
|
|
|
List<AttributeKvEntry> cachedAttributes = wrappedCachedAttributes.values().stream() |
|
|
|
.map(TbCacheValueWrapper::get) |
|
|
|
.filter(Objects::nonNull) |
|
|
|
.collect(Collectors.toList()); |
|
|
|
if (wrappedCachedAttributes.size() == attributeKeys.size()) { |
|
|
|
log.trace("[{}][{}] Found all attributes from cache: {}", entityId, scope, attributeKeys); |
|
|
|
return Futures.immediateFuture(cachedAttributes); |
|
|
|
} |
|
|
|
|
|
|
|
Set<String> notFoundAttributeKeys = new HashSet<>(attributeKeys); |
|
|
|
notFoundAttributeKeys.removeAll(wrappedCachedAttributes.keySet()); |
|
|
|
|
|
|
|
List<AttributeCacheKey> notFoundKeys = notFoundAttributeKeys.stream().map(k -> new AttributeCacheKey(scope, entityId, k)).collect(Collectors.toList()); |
|
|
|
|
|
|
|
// DB call should run in DB executor, not in cache-related executor
|
|
|
|
return jpaExecutorService.submit(() -> { |
|
|
|
var cacheTransaction = cache.newTransactionForKeys(notFoundKeys); |
|
|
|
try { |
|
|
|
log.trace("[{}][{}] Lookup attributes from db: {}", entityId, scope, notFoundAttributeKeys); |
|
|
|
List<AttributeKvEntry> result = attributesDao.find(tenantId, entityId, scope, notFoundAttributeKeys); |
|
|
|
for (AttributeKvEntry foundInDbAttribute : result) { |
|
|
|
AttributeCacheKey attributeCacheKey = new AttributeCacheKey(scope, entityId, foundInDbAttribute.getKey()); |
|
|
|
cacheTransaction.putIfAbsent(attributeCacheKey, foundInDbAttribute); |
|
|
|
notFoundAttributeKeys.remove(foundInDbAttribute.getKey()); |
|
|
|
} |
|
|
|
for (String key : notFoundAttributeKeys) { |
|
|
|
cacheTransaction.putIfAbsent(new AttributeCacheKey(scope, entityId, key), null); |
|
|
|
} |
|
|
|
List<AttributeKvEntry> mergedAttributes = new ArrayList<>(cachedAttributes); |
|
|
|
mergedAttributes.addAll(result); |
|
|
|
cacheTransaction.commit(); |
|
|
|
log.trace("[{}][{}] Commit cache transaction: {}", entityId, scope, notFoundAttributeKeys); |
|
|
|
return mergedAttributes; |
|
|
|
} catch (Throwable e) { |
|
|
|
cacheTransaction.rollback(); |
|
|
|
log.debug("Could not find attributes from cache: [{}] [{}] [{}]", entityId, scope, notFoundAttributeKeys, e); |
|
|
|
throw e; |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
}, MoreExecutors.directExecutor()); // cacheExecutor analyse and returns results or submit to DB executor
|
|
|
|
List<AttributeKvEntry> cachedAttributes = wrappedCachedAttributes.values().stream() |
|
|
|
.map(TbCacheValueWrapper::get) |
|
|
|
.filter(Objects::nonNull) |
|
|
|
.collect(Collectors.toList()); |
|
|
|
if (wrappedCachedAttributes.size() == attributeKeys.size()) { |
|
|
|
log.trace("[{}][{}] Found all attributes from cache: {}", entityId, scope, attributeKeys); |
|
|
|
return Futures.immediateFuture(cachedAttributes); |
|
|
|
} |
|
|
|
|
|
|
|
Set<String> notFoundAttributeKeys = new HashSet<>(attributeKeys); |
|
|
|
notFoundAttributeKeys.removeAll(wrappedCachedAttributes.keySet()); |
|
|
|
|
|
|
|
List<AttributeCacheKey> notFoundKeys = notFoundAttributeKeys.stream().map(k -> new AttributeCacheKey(scope, entityId, k)).collect(Collectors.toList()); |
|
|
|
|
|
|
|
// DB call should run in DB executor, not in cache-related executor
|
|
|
|
return jpaExecutorService.submit(() -> { |
|
|
|
var cacheTransaction = cache.newTransactionForKeys(notFoundKeys); |
|
|
|
try { |
|
|
|
log.trace("[{}][{}] Lookup attributes from db: {}", entityId, scope, notFoundAttributeKeys); |
|
|
|
List<AttributeKvEntry> result = attributesDao.find(tenantId, entityId, scope, notFoundAttributeKeys); |
|
|
|
for (AttributeKvEntry foundInDbAttribute : result) { |
|
|
|
AttributeCacheKey attributeCacheKey = new AttributeCacheKey(scope, entityId, foundInDbAttribute.getKey()); |
|
|
|
cacheTransaction.putIfAbsent(attributeCacheKey, foundInDbAttribute); |
|
|
|
notFoundAttributeKeys.remove(foundInDbAttribute.getKey()); |
|
|
|
} |
|
|
|
for (String key : notFoundAttributeKeys) { |
|
|
|
cacheTransaction.putIfAbsent(new AttributeCacheKey(scope, entityId, key), null); |
|
|
|
} |
|
|
|
List<AttributeKvEntry> mergedAttributes = new ArrayList<>(cachedAttributes); |
|
|
|
mergedAttributes.addAll(result); |
|
|
|
cacheTransaction.commit(); |
|
|
|
log.trace("[{}][{}] Commit cache transaction: {}", entityId, scope, notFoundAttributeKeys); |
|
|
|
return mergedAttributes; |
|
|
|
} catch (Throwable e) { |
|
|
|
cacheTransaction.rollback(); |
|
|
|
log.debug("Could not find attributes from cache: [{}] [{}] [{}]", entityId, scope, notFoundAttributeKeys, e); |
|
|
|
throw e; |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
}, MoreExecutors.directExecutor()); // cacheExecutor analyse and returns results or submit to DB executor
|
|
|
|
} |
|
|
|
|
|
|
|
private Map<String, TbCacheValueWrapper<AttributeKvEntry>> findCachedAttributes(EntityId entityId, String scope, Collection<String> attributeKeys) { |
|
|
|
@ -268,4 +269,15 @@ public class CachedAttributesService implements AttributesService { |
|
|
|
}, cacheExecutor)).collect(Collectors.toList())); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public int removeAllByEntityId(TenantId tenantId, EntityId entityId) { |
|
|
|
List<Pair<String, String>> result = attributesDao.removeAllByEntityId(tenantId, entityId); |
|
|
|
result.forEach(deleted -> { |
|
|
|
String scope = deleted.getKey(); |
|
|
|
String key = deleted.getValue(); |
|
|
|
cache.evict(new AttributeCacheKey(scope, entityId, key)); |
|
|
|
}); |
|
|
|
return result.size(); |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|