Introduce evictIfPresent/invalidate operations on Cache abstraction
@CacheEvict.beforeInvocation suggests immediate execution even in case of transactional caches. The cache interceptor delegates to the new evictIfPresent/invalidate operations now which imply immediate execution semantics (and also provide an indication for whether any corresponding entries where present when programmatically called). Closes gh-23192
This commit is contained in:
parent
2c33c11d4c
commit
ffc1f242ca
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -123,11 +123,23 @@ public class CaffeineCache extends AbstractValueAdaptingCache {
|
||||||
this.cache.invalidate(key);
|
this.cache.invalidate(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean evictIfPresent(Object key) {
|
||||||
|
return (this.cache.asMap().remove(key) != null);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
this.cache.invalidateAll();
|
this.cache.invalidateAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean invalidate() {
|
||||||
|
boolean notEmpty = !this.cache.asMap().isEmpty();
|
||||||
|
this.cache.invalidateAll();
|
||||||
|
return notEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private class PutIfAbsentFunction implements Function<Object, Object> {
|
private class PutIfAbsentFunction implements Function<Object, Object> {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -72,6 +72,19 @@ public class EhCacheCache implements Cache {
|
||||||
return toValueWrapper(element);
|
return toValueWrapper(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public <T> T get(Object key, @Nullable Class<T> type) {
|
||||||
|
Element element = this.cache.get(key);
|
||||||
|
Object value = (element != null ? element.getObjectValue() : null);
|
||||||
|
if (value != null && type != null && !type.isInstance(value)) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Cached value is not of required type [" + type.getName() + "]: " + value);
|
||||||
|
}
|
||||||
|
return (T) value;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|
@ -95,7 +108,6 @@ public class EhCacheCache implements Cache {
|
||||||
this.cache.releaseWriteLockOnKey(key);
|
this.cache.releaseWriteLockOnKey(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> T loadValue(Object key, Callable<T> valueLoader) {
|
private <T> T loadValue(Object key, Callable<T> valueLoader) {
|
||||||
|
|
@ -110,19 +122,6 @@ public class EhCacheCache implements Cache {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Nullable
|
|
||||||
public <T> T get(Object key, @Nullable Class<T> type) {
|
|
||||||
Element element = this.cache.get(key);
|
|
||||||
Object value = (element != null ? element.getObjectValue() : null);
|
|
||||||
if (value != null && type != null && !type.isInstance(value)) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Cached value is not of required type [" + type.getName() + "]: " + value);
|
|
||||||
}
|
|
||||||
return (T) value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void put(Object key, @Nullable Object value) {
|
public void put(Object key, @Nullable Object value) {
|
||||||
this.cache.put(new Element(key, value));
|
this.cache.put(new Element(key, value));
|
||||||
|
|
@ -140,11 +139,23 @@ public class EhCacheCache implements Cache {
|
||||||
this.cache.remove(key);
|
this.cache.remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean evictIfPresent(Object key) {
|
||||||
|
return this.cache.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
this.cache.removeAll();
|
this.cache.removeAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean invalidate() {
|
||||||
|
boolean notEmpty = (this.cache.getSize() > 0);
|
||||||
|
this.cache.removeAll();
|
||||||
|
return notEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Element lookup(Object key) {
|
private Element lookup(Object key) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -105,11 +105,23 @@ public class JCacheCache extends AbstractValueAdaptingCache {
|
||||||
this.cache.remove(key);
|
this.cache.remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean evictIfPresent(Object key) {
|
||||||
|
return this.cache.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
this.cache.removeAll();
|
this.cache.removeAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean invalidate() {
|
||||||
|
boolean notEmpty = this.cache.iterator().hasNext();
|
||||||
|
this.cache.removeAll();
|
||||||
|
return notEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private class ValueLoaderEntryProcessor<T> implements EntryProcessor<Object, Object, T> {
|
private class ValueLoaderEntryProcessor<T> implements EntryProcessor<Object, Object, T> {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -59,7 +59,7 @@ abstract class AbstractCacheInterceptor<O extends AbstractJCacheOperation<A>, A
|
||||||
/**
|
/**
|
||||||
* Resolve the cache to use.
|
* Resolve the cache to use.
|
||||||
* @param context the invocation context
|
* @param context the invocation context
|
||||||
* @return the cache to use (never null)
|
* @return the cache to use (never {@code null})
|
||||||
*/
|
*/
|
||||||
protected Cache resolveCache(CacheOperationInvocationContext<O> context) {
|
protected Cache resolveCache(CacheOperationInvocationContext<O> context) {
|
||||||
Collection<? extends Cache> caches = context.getOperation().getCacheResolver().resolveCaches(context);
|
Collection<? extends Cache> caches = context.getOperation().getCacheResolver().resolveCaches(context);
|
||||||
|
|
@ -73,7 +73,7 @@ abstract class AbstractCacheInterceptor<O extends AbstractJCacheOperation<A>, A
|
||||||
/**
|
/**
|
||||||
* Convert the collection of caches in a single expected element.
|
* Convert the collection of caches in a single expected element.
|
||||||
* <p>Throw an {@link IllegalStateException} if the collection holds more than one element
|
* <p>Throw an {@link IllegalStateException} if the collection holds more than one element
|
||||||
* @return the single element or {@code null} if the collection is empty
|
* @return the single element, or {@code null} if the collection is empty
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
static Cache extractFrom(Collection<? extends Cache> caches) {
|
static Cache extractFrom(Collection<? extends Cache> caches) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -42,7 +42,6 @@ class CacheRemoveAllInterceptor extends AbstractCacheInterceptor<CacheRemoveAllO
|
||||||
CacheOperationInvocationContext<CacheRemoveAllOperation> context, CacheOperationInvoker invoker) {
|
CacheOperationInvocationContext<CacheRemoveAllOperation> context, CacheOperationInvoker invoker) {
|
||||||
|
|
||||||
CacheRemoveAllOperation operation = context.getOperation();
|
CacheRemoveAllOperation operation = context.getOperation();
|
||||||
|
|
||||||
boolean earlyRemove = operation.isEarlyRemove();
|
boolean earlyRemove = operation.isEarlyRemove();
|
||||||
if (earlyRemove) {
|
if (earlyRemove) {
|
||||||
removeAll(context);
|
removeAll(context);
|
||||||
|
|
@ -67,10 +66,10 @@ class CacheRemoveAllInterceptor extends AbstractCacheInterceptor<CacheRemoveAllO
|
||||||
protected void removeAll(CacheOperationInvocationContext<CacheRemoveAllOperation> context) {
|
protected void removeAll(CacheOperationInvocationContext<CacheRemoveAllOperation> context) {
|
||||||
Cache cache = resolveCache(context);
|
Cache cache = resolveCache(context);
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace("Invalidating entire cache '" + cache.getName() + "' for operation "
|
logger.trace("Invalidating entire cache '" + cache.getName() + "' for operation " +
|
||||||
+ context.getOperation());
|
context.getOperation());
|
||||||
}
|
}
|
||||||
doClear(cache);
|
doClear(cache, context.getOperation().isEarlyRemove());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -42,7 +42,6 @@ class CacheRemoveEntryInterceptor extends AbstractKeyCacheInterceptor<CacheRemov
|
||||||
CacheOperationInvocationContext<CacheRemoveOperation> context, CacheOperationInvoker invoker) {
|
CacheOperationInvocationContext<CacheRemoveOperation> context, CacheOperationInvoker invoker) {
|
||||||
|
|
||||||
CacheRemoveOperation operation = context.getOperation();
|
CacheRemoveOperation operation = context.getOperation();
|
||||||
|
|
||||||
boolean earlyRemove = operation.isEarlyRemove();
|
boolean earlyRemove = operation.isEarlyRemove();
|
||||||
if (earlyRemove) {
|
if (earlyRemove) {
|
||||||
removeValue(context);
|
removeValue(context);
|
||||||
|
|
@ -68,10 +67,10 @@ class CacheRemoveEntryInterceptor extends AbstractKeyCacheInterceptor<CacheRemov
|
||||||
Object key = generateKey(context);
|
Object key = generateKey(context);
|
||||||
Cache cache = resolveCache(context);
|
Cache cache = resolveCache(context);
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace("Invalidating key [" + key + "] on cache '" + cache.getName()
|
logger.trace("Invalidating key [" + key + "] on cache '" + cache.getName() +
|
||||||
+ "' for operation " + context.getOperation());
|
"' for operation " + context.getOperation());
|
||||||
}
|
}
|
||||||
doEvict(cache, key);
|
doEvict(cache, key, context.getOperation().isEarlyRemove());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2014 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -50,8 +50,8 @@ class CacheRemoveOperation extends AbstractJCacheKeyOperation<CacheRemove> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specify if the cache entry should be remove before invoking the method. By default, the
|
* Specify if the cache entry should be removed before invoking the method.
|
||||||
* cache entry is removed after the method invocation.
|
* <p>By default, the cache entry is removed after the method invocation.
|
||||||
* @see javax.cache.annotation.CacheRemove#afterInvocation()
|
* @see javax.cache.annotation.CacheRemove#afterInvocation()
|
||||||
*/
|
*/
|
||||||
public boolean isEarlyRemove() {
|
public boolean isEarlyRemove() {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -25,14 +25,16 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache decorator which synchronizes its {@link #put}, {@link #evict} and {@link #clear}
|
* Cache decorator which synchronizes its {@link #put}, {@link #evict} and
|
||||||
* operations with Spring-managed transactions (through Spring's {@link TransactionSynchronizationManager},
|
* {@link #clear} operations with Spring-managed transactions (through Spring's
|
||||||
* performing the actual cache put/evict/clear operation only in the after-commit phase of a
|
* {@link TransactionSynchronizationManager}, performing the actual cache
|
||||||
* successful transaction. If no transaction is active, {@link #put}, {@link #evict} and
|
* put/evict/clear operation only in the after-commit phase of a successful
|
||||||
|
* transaction. If no transaction is active, {@link #put}, {@link #evict} and
|
||||||
* {@link #clear} operations will be performed immediately, as usual.
|
* {@link #clear} operations will be performed immediately, as usual.
|
||||||
*
|
*
|
||||||
* <p>Use of more aggressive operations such as {@link #putIfAbsent} cannot be deferred
|
* <p><b>Note:</b> Use of immediate operations such as {@link #putIfAbsent} and
|
||||||
* to the after-commit phase of a running transaction. Use these with care.
|
* {@link #evictIfPresent} cannot be deferred to the after-commit phase of a
|
||||||
|
* running transaction. Use these with care in a transactional environment.
|
||||||
*
|
*
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
|
|
@ -54,6 +56,7 @@ public class TransactionAwareCacheDecorator implements Cache {
|
||||||
this.targetCache = targetCache;
|
this.targetCache = targetCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the target Cache that this Cache should delegate to.
|
* Return the target Cache that this Cache should delegate to.
|
||||||
*/
|
*/
|
||||||
|
|
@ -124,6 +127,11 @@ public class TransactionAwareCacheDecorator implements Cache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean evictIfPresent(Object key) {
|
||||||
|
return this.targetCache.evictIfPresent(key);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
if (TransactionSynchronizationManager.isSynchronizationActive()) {
|
if (TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||||
|
|
@ -139,4 +147,9 @@ public class TransactionAwareCacheDecorator implements Cache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean invalidate() {
|
||||||
|
return this.targetCache.invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,25 +21,23 @@ import org.junit.Test;
|
||||||
import org.springframework.cache.Cache;
|
import org.springframework.cache.Cache;
|
||||||
import org.springframework.cache.concurrent.ConcurrentMapCache;
|
import org.springframework.cache.concurrent.ConcurrentMapCache;
|
||||||
import org.springframework.tests.transaction.CallCountingTransactionManager;
|
import org.springframework.tests.transaction.CallCountingTransactionManager;
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
import org.springframework.transaction.support.TransactionTemplate;
|
||||||
import org.springframework.transaction.TransactionDefinition;
|
|
||||||
import org.springframework.transaction.TransactionStatus;
|
|
||||||
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
|
* @author Juergen Hoeller
|
||||||
*/
|
*/
|
||||||
public class TransactionAwareCacheDecoratorTests {
|
public class TransactionAwareCacheDecoratorTests {
|
||||||
|
|
||||||
private final PlatformTransactionManager txManager = new CallCountingTransactionManager();
|
private final TransactionTemplate txTemplate = new TransactionTemplate(new CallCountingTransactionManager());
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createWithNullTarget() {
|
public void createWithNullTarget() {
|
||||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
assertThatIllegalArgumentException().isThrownBy(() -> new TransactionAwareCacheDecorator(null));
|
||||||
new TransactionAwareCacheDecorator(null));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -79,20 +77,18 @@ public class TransactionAwareCacheDecoratorTests {
|
||||||
public void putTransactional() {
|
public void putTransactional() {
|
||||||
Cache target = new ConcurrentMapCache("testCache");
|
Cache target = new ConcurrentMapCache("testCache");
|
||||||
Cache cache = new TransactionAwareCacheDecorator(target);
|
Cache cache = new TransactionAwareCacheDecorator(target);
|
||||||
|
|
||||||
TransactionStatus status = this.txManager.getTransaction(
|
|
||||||
new DefaultTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED));
|
|
||||||
|
|
||||||
Object key = new Object();
|
Object key = new Object();
|
||||||
cache.put(key, "123");
|
|
||||||
assertThat(target.get(key)).isNull();
|
txTemplate.execute(() -> {
|
||||||
this.txManager.commit(status);
|
cache.put(key, "123");
|
||||||
|
assertThat(target.get(key)).isNull();
|
||||||
|
});
|
||||||
|
|
||||||
assertThat(target.get(key, String.class)).isEqualTo("123");
|
assertThat(target.get(key, String.class)).isEqualTo("123");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void putIfAbsent() { // no transactional support for putIfAbsent
|
public void putIfAbsentNonTransactional() {
|
||||||
Cache target = new ConcurrentMapCache("testCache");
|
Cache target = new ConcurrentMapCache("testCache");
|
||||||
Cache cache = new TransactionAwareCacheDecorator(target);
|
Cache cache = new TransactionAwareCacheDecorator(target);
|
||||||
|
|
||||||
|
|
@ -104,6 +100,23 @@ public class TransactionAwareCacheDecoratorTests {
|
||||||
assertThat(target.get(key, String.class)).isEqualTo("123");
|
assertThat(target.get(key, String.class)).isEqualTo("123");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void putIfAbsentTransactional() { // no transactional support for putIfAbsent
|
||||||
|
Cache target = new ConcurrentMapCache("testCache");
|
||||||
|
Cache cache = new TransactionAwareCacheDecorator(target);
|
||||||
|
Object key = new Object();
|
||||||
|
|
||||||
|
txTemplate.execute(() -> {
|
||||||
|
assertThat(cache.putIfAbsent(key, "123")).isNull();
|
||||||
|
assertThat(target.get(key, String.class)).isEqualTo("123");
|
||||||
|
assertThat(cache.putIfAbsent(key, "456").get()).isEqualTo("123");
|
||||||
|
// unchanged
|
||||||
|
assertThat(target.get(key, String.class)).isEqualTo("123");
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThat(target.get(key, String.class)).isEqualTo("123");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void evictNonTransactional() {
|
public void evictNonTransactional() {
|
||||||
Cache target = new ConcurrentMapCache("testCache");
|
Cache target = new ConcurrentMapCache("testCache");
|
||||||
|
|
@ -122,12 +135,36 @@ public class TransactionAwareCacheDecoratorTests {
|
||||||
Object key = new Object();
|
Object key = new Object();
|
||||||
cache.put(key, "123");
|
cache.put(key, "123");
|
||||||
|
|
||||||
|
txTemplate.execute(() -> {
|
||||||
|
cache.evict(key);
|
||||||
|
assertThat(target.get(key, String.class)).isEqualTo("123");
|
||||||
|
});
|
||||||
|
|
||||||
TransactionStatus status = this.txManager.getTransaction(
|
assertThat(target.get(key)).isNull();
|
||||||
new DefaultTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED));
|
}
|
||||||
cache.evict(key);
|
|
||||||
assertThat(target.get(key, String.class)).isEqualTo("123");
|
@Test
|
||||||
this.txManager.commit(status);
|
public void evictIfPresentNonTransactional() {
|
||||||
|
Cache target = new ConcurrentMapCache("testCache");
|
||||||
|
Cache cache = new TransactionAwareCacheDecorator(target);
|
||||||
|
Object key = new Object();
|
||||||
|
cache.put(key, "123");
|
||||||
|
|
||||||
|
cache.evictIfPresent(key);
|
||||||
|
assertThat(target.get(key)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void evictIfPresentTransactional() { // no transactional support for evictIfPresent
|
||||||
|
Cache target = new ConcurrentMapCache("testCache");
|
||||||
|
Cache cache = new TransactionAwareCacheDecorator(target);
|
||||||
|
Object key = new Object();
|
||||||
|
cache.put(key, "123");
|
||||||
|
|
||||||
|
txTemplate.execute(() -> {
|
||||||
|
cache.evictIfPresent(key);
|
||||||
|
assertThat(target.get(key)).isNull();
|
||||||
|
});
|
||||||
|
|
||||||
assertThat(target.get(key)).isNull();
|
assertThat(target.get(key)).isNull();
|
||||||
}
|
}
|
||||||
|
|
@ -150,13 +187,38 @@ public class TransactionAwareCacheDecoratorTests {
|
||||||
Object key = new Object();
|
Object key = new Object();
|
||||||
cache.put(key, "123");
|
cache.put(key, "123");
|
||||||
|
|
||||||
|
txTemplate.execute(() -> {
|
||||||
TransactionStatus status = this.txManager.getTransaction(
|
cache.clear();
|
||||||
new DefaultTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED));
|
assertThat(target.get(key, String.class)).isEqualTo("123");
|
||||||
cache.clear();
|
});
|
||||||
assertThat(target.get(key, String.class)).isEqualTo("123");
|
|
||||||
this.txManager.commit(status);
|
|
||||||
|
|
||||||
assertThat(target.get(key)).isNull();
|
assertThat(target.get(key)).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void invalidateNonTransactional() {
|
||||||
|
Cache target = new ConcurrentMapCache("testCache");
|
||||||
|
Cache cache = new TransactionAwareCacheDecorator(target);
|
||||||
|
Object key = new Object();
|
||||||
|
cache.put(key, "123");
|
||||||
|
|
||||||
|
cache.invalidate();
|
||||||
|
assertThat(target.get(key)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void invalidateTransactional() { // no transactional support for invalidate
|
||||||
|
Cache target = new ConcurrentMapCache("testCache");
|
||||||
|
Cache cache = new TransactionAwareCacheDecorator(target);
|
||||||
|
Object key = new Object();
|
||||||
|
cache.put(key, "123");
|
||||||
|
|
||||||
|
txTemplate.execute(() -> {
|
||||||
|
cache.invalidate();
|
||||||
|
assertThat(target.get(key)).isNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThat(target.get(key)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -55,6 +55,7 @@ public interface Cache {
|
||||||
* a cached {@code null} value. A straight {@code null} being
|
* a cached {@code null} value. A straight {@code null} being
|
||||||
* returned means that the cache contains no mapping for this key.
|
* returned means that the cache contains no mapping for this key.
|
||||||
* @see #get(Object, Class)
|
* @see #get(Object, Class)
|
||||||
|
* @see #get(Object, Callable)
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
ValueWrapper get(Object key);
|
ValueWrapper get(Object key);
|
||||||
|
|
@ -94,6 +95,7 @@ public interface Cache {
|
||||||
* @return the value to which this cache maps the specified key
|
* @return the value to which this cache maps the specified key
|
||||||
* @throws ValueRetrievalException if the {@code valueLoader} throws an exception
|
* @throws ValueRetrievalException if the {@code valueLoader} throws an exception
|
||||||
* @since 4.3
|
* @since 4.3
|
||||||
|
* @see #get(Object)
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
<T> T get(Object key, Callable<T> valueLoader);
|
<T> T get(Object key, Callable<T> valueLoader);
|
||||||
|
|
@ -102,8 +104,13 @@ public interface Cache {
|
||||||
* Associate the specified value with the specified key in this cache.
|
* Associate the specified value with the specified key in this cache.
|
||||||
* <p>If the cache previously contained a mapping for this key, the old
|
* <p>If the cache previously contained a mapping for this key, the old
|
||||||
* value is replaced by the specified value.
|
* value is replaced by the specified value.
|
||||||
|
* <p>Actual registration may be performed in an asynchronous or deferred
|
||||||
|
* fashion, with subsequent lookups possibly not seeing the entry yet.
|
||||||
|
* This may for example be the case with transactional cache decorators.
|
||||||
|
* Use {@link #putIfAbsent} for guaranteed immediate registration.
|
||||||
* @param key the key with which the specified value is to be associated
|
* @param key the key with which the specified value is to be associated
|
||||||
* @param value the value to be associated with the specified key
|
* @param value the value to be associated with the specified key
|
||||||
|
* @see #putIfAbsent(Object, Object)
|
||||||
*/
|
*/
|
||||||
void put(Object key, @Nullable Object value);
|
void put(Object key, @Nullable Object value);
|
||||||
|
|
||||||
|
|
@ -112,19 +119,19 @@ public interface Cache {
|
||||||
* if it is not set already.
|
* if it is not set already.
|
||||||
* <p>This is equivalent to:
|
* <p>This is equivalent to:
|
||||||
* <pre><code>
|
* <pre><code>
|
||||||
* Object existingValue = cache.get(key);
|
* ValueWrapper existingValue = cache.get(key);
|
||||||
* if (existingValue == null) {
|
* if (existingValue == null) {
|
||||||
* cache.put(key, value);
|
* cache.put(key, value);
|
||||||
* return null;
|
|
||||||
* } else {
|
|
||||||
* return existingValue;
|
|
||||||
* }
|
* }
|
||||||
|
* return existingValue;
|
||||||
* </code></pre>
|
* </code></pre>
|
||||||
* except that the action is performed atomically. While all out-of-the-box
|
* except that the action is performed atomically. While all out-of-the-box
|
||||||
* {@link CacheManager} implementations are able to perform the put atomically,
|
* {@link CacheManager} implementations are able to perform the put atomically,
|
||||||
* the operation may also be implemented in two steps, e.g. with a check for
|
* the operation may also be implemented in two steps, e.g. with a check for
|
||||||
* presence and a subsequent put, in a non-atomic way. Check the documentation
|
* presence and a subsequent put, in a non-atomic way. Check the documentation
|
||||||
* of the native cache implementation that you are using for more details.
|
* of the native cache implementation that you are using for more details.
|
||||||
|
* <p>The default implementation delegates to {@link #get(Object)} and
|
||||||
|
* {@link #put(Object, Object)} along the lines of the code snippet above.
|
||||||
* @param key the key with which the specified value is to be associated
|
* @param key the key with which the specified value is to be associated
|
||||||
* @param value the value to be associated with the specified key
|
* @param value the value to be associated with the specified key
|
||||||
* @return the value to which this cache maps the specified key (which may be
|
* @return the value to which this cache maps the specified key (which may be
|
||||||
|
|
@ -132,21 +139,73 @@ public interface Cache {
|
||||||
* mapping for that key prior to this call. Returning {@code null} is therefore
|
* mapping for that key prior to this call. Returning {@code null} is therefore
|
||||||
* an indicator that the given {@code value} has been associated with the key.
|
* an indicator that the given {@code value} has been associated with the key.
|
||||||
* @since 4.1
|
* @since 4.1
|
||||||
|
* @see #put(Object, Object)
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
ValueWrapper putIfAbsent(Object key, @Nullable Object value);
|
default ValueWrapper putIfAbsent(Object key, @Nullable Object value) {
|
||||||
|
ValueWrapper existingValue = get(key);
|
||||||
|
if (existingValue == null) {
|
||||||
|
put(key, value);
|
||||||
|
}
|
||||||
|
return existingValue;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evict the mapping for this key from this cache if it is present.
|
* Evict the mapping for this key from this cache if it is present.
|
||||||
|
* <p>Actual eviction may be performed in an asynchronous or deferred
|
||||||
|
* fashion, with subsequent lookups possibly still seeing the entry.
|
||||||
|
* This may for example be the case with transactional cache decorators.
|
||||||
|
* Use {@link #evictIfPresent} for guaranteed immediate removal.
|
||||||
* @param key the key whose mapping is to be removed from the cache
|
* @param key the key whose mapping is to be removed from the cache
|
||||||
|
* @see #evictIfPresent(Object)
|
||||||
*/
|
*/
|
||||||
void evict(Object key);
|
void evict(Object key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all mappings from the cache.
|
* Evict the mapping for this key from this cache if it is present,
|
||||||
|
* expecting the key to be immediately invisible for subsequent lookups.
|
||||||
|
* <p>The default implementation delegates to {@link #evict(Object)},
|
||||||
|
* returning {@code false} for not-determined prior presence of the key.
|
||||||
|
* Cache providers and in particular cache decorators are encouraged
|
||||||
|
* to perform immediate eviction if possible (e.g. in case of generally
|
||||||
|
* deferred cache operations within a transaction) and to reliably
|
||||||
|
* determine prior presence of the given key.
|
||||||
|
* @param key the key whose mapping is to be removed from the cache
|
||||||
|
* @return {@code true} if the cache was known to have a mapping for
|
||||||
|
* this key before, {@code false} if it did not (or if prior presence
|
||||||
|
* could not be determined)
|
||||||
|
* @since 5.2
|
||||||
|
* @see #evict(Object)
|
||||||
|
*/
|
||||||
|
default boolean evictIfPresent(Object key) {
|
||||||
|
evict(key);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the cache through removing all mappings.
|
||||||
|
* <p>Actual clearing may be performed in an asynchronous or deferred
|
||||||
|
* fashion, with subsequent lookups possibly still seeing the entries.
|
||||||
|
* This may for example be the case with transactional cache decorators.
|
||||||
|
* Use {@link #invalidate()} for guaranteed immediate removal of entries.
|
||||||
|
* @see #invalidate()
|
||||||
*/
|
*/
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate the cache through removing all mappings, expecting all
|
||||||
|
* entries to be immediately invisible for subsequent lookups.
|
||||||
|
* @return {@code true} if the cache was known to have mappings before,
|
||||||
|
* {@code false} if it did not (or if prior presence of entries could
|
||||||
|
* not be determined)
|
||||||
|
* @since 5.2
|
||||||
|
* @see #clear()
|
||||||
|
*/
|
||||||
|
default boolean invalidate() {
|
||||||
|
clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A (wrapper) object representing a cache value.
|
* A (wrapper) object representing a cache value.
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ public @interface CacheEvict {
|
||||||
* occur irrespective of the method outcome (i.e., whether it threw an
|
* occur irrespective of the method outcome (i.e., whether it threw an
|
||||||
* exception or not).
|
* exception or not).
|
||||||
* <p>Defaults to {@code false}, meaning that the cache eviction operation
|
* <p>Defaults to {@code false}, meaning that the cache eviction operation
|
||||||
* will occur <em>after</em> the advised method is invoked successfully (i.e.,
|
* will occur <em>after</em> the advised method is invoked successfully (i.e.
|
||||||
* only if the invocation did not throw an exception).
|
* only if the invocation did not throw an exception).
|
||||||
*/
|
*/
|
||||||
boolean beforeInvocation() default false;
|
boolean beforeInvocation() default false;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -141,7 +141,7 @@ public class ConcurrentMapCache extends AbstractValueAdaptingCache {
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public <T> T get(Object key, Callable<T> valueLoader) {
|
public <T> T get(Object key, Callable<T> valueLoader) {
|
||||||
return (T) fromStoreValue(this.store.computeIfAbsent(key, r -> {
|
return (T) fromStoreValue(this.store.computeIfAbsent(key, k -> {
|
||||||
try {
|
try {
|
||||||
return toStoreValue(valueLoader.call());
|
return toStoreValue(valueLoader.call());
|
||||||
}
|
}
|
||||||
|
|
@ -168,11 +168,23 @@ public class ConcurrentMapCache extends AbstractValueAdaptingCache {
|
||||||
this.store.remove(key);
|
this.store.remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean evictIfPresent(Object key) {
|
||||||
|
return (this.store.remove(key) != null);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
this.store.clear();
|
this.store.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean invalidate() {
|
||||||
|
boolean notEmpty = !this.store.isEmpty();
|
||||||
|
this.store.clear();
|
||||||
|
return notEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Object toStoreValue(@Nullable Object userValue) {
|
protected Object toStoreValue(@Nullable Object userValue) {
|
||||||
Object storeValue = super.toStoreValue(userValue);
|
Object storeValue = super.toStoreValue(userValue);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -92,12 +92,17 @@ public abstract class AbstractCacheInvoker {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute {@link Cache#evict(Object)} on the specified {@link Cache} and
|
* Execute {@link Cache#evict(Object)}/{@link Cache#evictIfPresent(Object)} on the
|
||||||
* invoke the error handler if an exception occurs.
|
* specified {@link Cache} and invoke the error handler if an exception occurs.
|
||||||
*/
|
*/
|
||||||
protected void doEvict(Cache cache, Object key) {
|
protected void doEvict(Cache cache, Object key, boolean immediate) {
|
||||||
try {
|
try {
|
||||||
cache.evict(key);
|
if (immediate) {
|
||||||
|
cache.evictIfPresent(key);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cache.evict(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (RuntimeException ex) {
|
catch (RuntimeException ex) {
|
||||||
getErrorHandler().handleCacheEvictError(ex, cache, key);
|
getErrorHandler().handleCacheEvictError(ex, cache, key);
|
||||||
|
|
@ -108,9 +113,14 @@ public abstract class AbstractCacheInvoker {
|
||||||
* Execute {@link Cache#clear()} on the specified {@link Cache} and
|
* Execute {@link Cache#clear()} on the specified {@link Cache} and
|
||||||
* invoke the error handler if an exception occurs.
|
* invoke the error handler if an exception occurs.
|
||||||
*/
|
*/
|
||||||
protected void doClear(Cache cache) {
|
protected void doClear(Cache cache, boolean immediate) {
|
||||||
try {
|
try {
|
||||||
cache.clear();
|
if (immediate) {
|
||||||
|
cache.invalidate();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (RuntimeException ex) {
|
catch (RuntimeException ex) {
|
||||||
getErrorHandler().handleCacheClearError(ex, cache);
|
getErrorHandler().handleCacheClearError(ex, cache);
|
||||||
|
|
|
||||||
|
|
@ -485,14 +485,14 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
|
||||||
for (Cache cache : context.getCaches()) {
|
for (Cache cache : context.getCaches()) {
|
||||||
if (operation.isCacheWide()) {
|
if (operation.isCacheWide()) {
|
||||||
logInvalidating(context, operation, null);
|
logInvalidating(context, operation, null);
|
||||||
doClear(cache);
|
doClear(cache, operation.isBeforeInvocation());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
key = generateKey(context, result);
|
key = generateKey(context, result);
|
||||||
}
|
}
|
||||||
logInvalidating(context, operation, key);
|
logInvalidating(context, operation, key);
|
||||||
doEvict(cache, key);
|
doEvict(cache, key, operation.isBeforeInvocation());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -93,8 +93,18 @@ public class NoOpCache implements Cache {
|
||||||
public void evict(Object key) {
|
public void evict(Object key) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean evictIfPresent(Object key) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean invalidate() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue