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:
@@ -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");
|
||||
* 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean evictIfPresent(Object key) {
|
||||
return (this.cache.asMap().remove(key) != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
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> {
|
||||
|
||||
|
||||
@@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -72,6 +72,19 @@ public class EhCacheCache implements Cache {
|
||||
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")
|
||||
@Override
|
||||
@Nullable
|
||||
@@ -95,7 +108,6 @@ public class EhCacheCache implements Cache {
|
||||
this.cache.releaseWriteLockOnKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private <T> T loadValue(Object key, Callable<T> valueLoader) {
|
||||
@@ -110,19 +122,6 @@ public class EhCacheCache implements Cache {
|
||||
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
|
||||
public void put(Object key, @Nullable Object value) {
|
||||
this.cache.put(new Element(key, value));
|
||||
@@ -140,11 +139,23 @@ public class EhCacheCache implements Cache {
|
||||
this.cache.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean evictIfPresent(Object key) {
|
||||
return this.cache.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
this.cache.removeAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean invalidate() {
|
||||
boolean notEmpty = (this.cache.getSize() > 0);
|
||||
this.cache.removeAll();
|
||||
return notEmpty;
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
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");
|
||||
* 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean evictIfPresent(Object key) {
|
||||
return this.cache.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
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> {
|
||||
|
||||
|
||||
@@ -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");
|
||||
* 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.
|
||||
* @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) {
|
||||
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.
|
||||
* <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
|
||||
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");
|
||||
* 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) {
|
||||
|
||||
CacheRemoveAllOperation operation = context.getOperation();
|
||||
|
||||
boolean earlyRemove = operation.isEarlyRemove();
|
||||
if (earlyRemove) {
|
||||
removeAll(context);
|
||||
@@ -67,10 +66,10 @@ class CacheRemoveAllInterceptor extends AbstractCacheInterceptor<CacheRemoveAllO
|
||||
protected void removeAll(CacheOperationInvocationContext<CacheRemoveAllOperation> context) {
|
||||
Cache cache = resolveCache(context);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Invalidating entire cache '" + cache.getName() + "' for operation "
|
||||
+ context.getOperation());
|
||||
logger.trace("Invalidating entire cache '" + cache.getName() + "' for operation " +
|
||||
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");
|
||||
* 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) {
|
||||
|
||||
CacheRemoveOperation operation = context.getOperation();
|
||||
|
||||
boolean earlyRemove = operation.isEarlyRemove();
|
||||
if (earlyRemove) {
|
||||
removeValue(context);
|
||||
@@ -68,10 +67,10 @@ class CacheRemoveEntryInterceptor extends AbstractKeyCacheInterceptor<CacheRemov
|
||||
Object key = generateKey(context);
|
||||
Cache cache = resolveCache(context);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Invalidating key [" + key + "] on cache '" + cache.getName()
|
||||
+ "' for operation " + context.getOperation());
|
||||
logger.trace("Invalidating key [" + key + "] on cache '" + cache.getName() +
|
||||
"' 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");
|
||||
* 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
|
||||
* cache entry is removed after the method invocation.
|
||||
* Specify if the cache entry should be removed before invoking the method.
|
||||
* <p>By default, the cache entry is removed after the method invocation.
|
||||
* @see javax.cache.annotation.CacheRemove#afterInvocation()
|
||||
*/
|
||||
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");
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Cache decorator which synchronizes its {@link #put}, {@link #evict} and {@link #clear}
|
||||
* operations with Spring-managed transactions (through Spring's {@link TransactionSynchronizationManager},
|
||||
* performing the actual cache put/evict/clear operation only in the after-commit phase of a
|
||||
* successful transaction. If no transaction is active, {@link #put}, {@link #evict} and
|
||||
* Cache decorator which synchronizes its {@link #put}, {@link #evict} and
|
||||
* {@link #clear} operations with Spring-managed transactions (through Spring's
|
||||
* {@link TransactionSynchronizationManager}, performing the actual cache
|
||||
* 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.
|
||||
*
|
||||
* <p>Use of more aggressive operations such as {@link #putIfAbsent} cannot be deferred
|
||||
* to the after-commit phase of a running transaction. Use these with care.
|
||||
* <p><b>Note:</b> Use of immediate operations such as {@link #putIfAbsent} and
|
||||
* {@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 Stephane Nicoll
|
||||
@@ -54,6 +56,7 @@ public class TransactionAwareCacheDecorator implements Cache {
|
||||
this.targetCache = targetCache;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
public void clear() {
|
||||
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.concurrent.ConcurrentMapCache;
|
||||
import org.springframework.tests.transaction.CallCountingTransactionManager;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* @author Stephane Nicoll
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
public class TransactionAwareCacheDecoratorTests {
|
||||
|
||||
private final PlatformTransactionManager txManager = new CallCountingTransactionManager();
|
||||
private final TransactionTemplate txTemplate = new TransactionTemplate(new CallCountingTransactionManager());
|
||||
|
||||
|
||||
@Test
|
||||
public void createWithNullTarget() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
new TransactionAwareCacheDecorator(null));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new TransactionAwareCacheDecorator(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -79,20 +77,18 @@ public class TransactionAwareCacheDecoratorTests {
|
||||
public void putTransactional() {
|
||||
Cache target = new ConcurrentMapCache("testCache");
|
||||
Cache cache = new TransactionAwareCacheDecorator(target);
|
||||
|
||||
TransactionStatus status = this.txManager.getTransaction(
|
||||
new DefaultTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED));
|
||||
|
||||
Object key = new Object();
|
||||
cache.put(key, "123");
|
||||
assertThat(target.get(key)).isNull();
|
||||
this.txManager.commit(status);
|
||||
|
||||
txTemplate.execute(() -> {
|
||||
cache.put(key, "123");
|
||||
assertThat(target.get(key)).isNull();
|
||||
});
|
||||
|
||||
assertThat(target.get(key, String.class)).isEqualTo("123");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putIfAbsent() { // no transactional support for putIfAbsent
|
||||
public void putIfAbsentNonTransactional() {
|
||||
Cache target = new ConcurrentMapCache("testCache");
|
||||
Cache cache = new TransactionAwareCacheDecorator(target);
|
||||
|
||||
@@ -104,6 +100,23 @@ public class TransactionAwareCacheDecoratorTests {
|
||||
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
|
||||
public void evictNonTransactional() {
|
||||
Cache target = new ConcurrentMapCache("testCache");
|
||||
@@ -122,12 +135,36 @@ public class TransactionAwareCacheDecoratorTests {
|
||||
Object key = new Object();
|
||||
cache.put(key, "123");
|
||||
|
||||
txTemplate.execute(() -> {
|
||||
cache.evict(key);
|
||||
assertThat(target.get(key, String.class)).isEqualTo("123");
|
||||
});
|
||||
|
||||
TransactionStatus status = this.txManager.getTransaction(
|
||||
new DefaultTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED));
|
||||
cache.evict(key);
|
||||
assertThat(target.get(key, String.class)).isEqualTo("123");
|
||||
this.txManager.commit(status);
|
||||
assertThat(target.get(key)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
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();
|
||||
}
|
||||
@@ -150,13 +187,38 @@ public class TransactionAwareCacheDecoratorTests {
|
||||
Object key = new Object();
|
||||
cache.put(key, "123");
|
||||
|
||||
|
||||
TransactionStatus status = this.txManager.getTransaction(
|
||||
new DefaultTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED));
|
||||
cache.clear();
|
||||
assertThat(target.get(key, String.class)).isEqualTo("123");
|
||||
this.txManager.commit(status);
|
||||
txTemplate.execute(() -> {
|
||||
cache.clear();
|
||||
assertThat(target.get(key, String.class)).isEqualTo("123");
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user