Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,20 @@ protected void testEvictEarly(CacheableService<?> service) {
assertThat(r2).isNotSameAs(r1);
}

protected void testEvictImmediate(CacheableService<?> service) {
Cache cache = this.cm.getCache("testCache");

Object o1 = new Object();
cache.putIfAbsent(o1, -1L);
Object r1 = service.cache(o1);

service.evictImmediate(o1);
assertThat(cache.get(o1)).isNull();

Object r2 = service.cache(o1);
assertThat(r2).isNotSameAs(r1);
}

protected void testEvictException(CacheableService<?> service) {
Object o1 = new Object();
Object r1 = service.cache(o1);
Expand Down Expand Up @@ -281,6 +295,28 @@ protected void testEvictAllEarly(CacheableService<?> service) {
assertThat(r4).isNotSameAs(r2);
}

protected void testEvictAllImmediate(CacheableService<?> service) {
Cache cache = this.cm.getCache("testCache");

Object o1 = new Object();
Object o2 = new Object();
cache.putIfAbsent(o1, -1L);
cache.putIfAbsent(o2, -2L);

Object r1 = service.cache(o1);
Object r2 = service.cache(o2);
assertThat(r2).isNotSameAs(r1);

service.evictAllImmediate(new Object());
assertThat(cache.get(o1)).isNull();
assertThat(cache.get(o2)).isNull();

Object r3 = service.cache(o1);
Object r4 = service.cache(o2);
assertThat(r3).isNotSameAs(r1);
assertThat(r4).isNotSameAs(r2);
}

protected void testConditionalExpression(CacheableService<?> service) {
Object r1 = service.conditional(4);
Object r2 = service.conditional(4);
Expand Down Expand Up @@ -586,6 +622,11 @@ void evictEarly() {
testEvictEarly(this.cs);
}

@Test
void evictImmediate() {
testEvictImmediate(this.cs);
}

@Test
void evictWithException() {
testEvictException(this.cs);
Expand All @@ -601,6 +642,11 @@ void evictAllEarly() {
testEvictAllEarly(this.cs);
}

@Test
void evictAllImmediate() {
testEvictAllImmediate(this.cs);
}

@Test
void evictWithKey() {
testEvictWithKey(this.cs);
Expand Down Expand Up @@ -656,11 +702,21 @@ void classEvictEarly() {
testEvictEarly(this.ccs);
}

@Test
void classEvictImmediate() {
testEvictImmediate(this.ccs);
}

@Test
void classEvictAll() {
testEvictAll(this.ccs, true);
}

@Test
void classEvictAllImmediate() {
testEvictAllImmediate(this.ccs);
}

@Test
void classEvictWithException() {
testEvictException(this.ccs);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ public void evictEarly(Object arg1) {
throw new RuntimeException("exception thrown - evict should still occur");
}

@Override
@CacheEvict(cacheNames = "testCache", key = "#p0", immediate = true)
public void evictImmediate(Object arg1) {
}

@Override
@CacheEvict(cacheNames = "testCache", allEntries = true)
public void evictAll(Object arg1) {
Expand All @@ -107,6 +112,11 @@ public void evictAllEarly(Object arg1) {
throw new RuntimeException("exception thrown - evict should still occur");
}

@Override
@CacheEvict(cacheNames = "testCache", allEntries = true, immediate = true)
public void evictAllImmediate(Object arg1) {
}

@Override
@Cacheable(cacheNames = "testCache", key = "#p0")
public Object key(Object arg1, Object arg2) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,14 @@ public interface CacheableService<T> {

void evictEarly(Object arg1);

void evictImmediate(Object arg1);

void evictAll(Object arg1);

void evictAllEarly(Object arg1);

void evictAllImmediate(Object arg1);

T conditional(int field);

T conditionalSync(int field);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ public void evictEarly(Object arg1) {
throw new RuntimeException("exception thrown - evict should still occur");
}

@Override
@CacheEvict(cacheNames = "testCache", key = "#p0", immediate = true)
public void evictImmediate(Object arg1) {
}

@Override
@CacheEvict(cacheNames = "testCache", allEntries = true)
public void evictAll(Object arg1) {
Expand All @@ -94,6 +99,11 @@ public void evictAllEarly(Object arg1) {
throw new RuntimeException("exception thrown - evict should still occur");
}

@Override
@CacheEvict(cacheNames = "testCache", allEntries = true, immediate = true)
public void evictAllImmediate(Object arg1) {
}

@Override
@Cacheable(cacheNames = "testCache", condition = "#p0 == 3")
public Long conditional(int classField) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,29 @@
*/
boolean beforeInvocation() default false;

/**
* Whether the eviction must be performed immediately, with all affected
* entries expected to be invisible to subsequent lookups as soon as this
* operation returns.
* <p>Setting this attribute to {@code true} causes the framework to invoke
* {@link org.springframework.cache.Cache#evictIfPresent} (for a single key)
* or {@link org.springframework.cache.Cache#invalidate} (when
* {@link #allEntries} is {@code true}) instead of
* {@link org.springframework.cache.Cache#evict} /
* {@link org.springframework.cache.Cache#clear}. The latter pair is allowed
* by contract to perform the removal in an asynchronous or deferred fashion,
* which can lead to a concurrently inserted entry being removed by a late
* eviction — for example, when a Redis-backed cache implements
* {@link org.springframework.cache.Cache#clear} via an asynchronous
* {@code UNLINK} while a subsequent {@code @CachePut} writes a new value.
* <p>Defaults to {@code false}, preserving the previous behavior where the
* immediacy of the eviction implicitly follows {@link #beforeInvocation()}:
* a before-invocation eviction is always immediate, whereas an
* after-invocation eviction may be deferred by the underlying cache
* implementation.
* @since 7.0
* @see org.springframework.cache.Cache#evictIfPresent
* @see org.springframework.cache.Cache#invalidate
*/
boolean immediate() default false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ private CacheEvictOperation parseEvictAnnotation(
builder.setCacheResolver(cacheEvict.cacheResolver());
builder.setCacheWide(cacheEvict.allEntries());
builder.setBeforeInvocation(cacheEvict.beforeInvocation());
builder.setImmediate(cacheEvict.immediate());

defaultConfig.applyDefault(builder);
CacheEvictOperation op = builder.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ private RootBeanDefinition parseDefinitionSource(Element definition, ParserConte
builder.setBeforeInvocation(Boolean.parseBoolean(after.trim()));
}

String immediate = opElement.getAttribute("immediate");
if (StringUtils.hasText(immediate)) {
builder.setImmediate(Boolean.parseBoolean(immediate.trim()));
}

Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, key -> new ArrayList<>(2));
col.add(builder.build());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -674,17 +674,19 @@ private void performCacheEvicts(List<CacheOperationContext> contexts, @Nullable
CacheEvictOperation operation = (CacheEvictOperation) context.metadata.operation;
if (isConditionPassing(context, result)) {
Object key = context.getGeneratedKey();
// A before-invocation eviction is always immediate (backwards-compatible default).
boolean immediate = operation.isImmediate() || operation.isBeforeInvocation();
for (Cache cache : context.getCaches()) {
if (operation.isCacheWide()) {
logInvalidating(context, operation, null);
doClear(cache, operation.isBeforeInvocation());
doClear(cache, immediate);
}
else {
if (key == null) {
key = generateKey(context, result);
}
logInvalidating(context, operation, key);
doEvict(cache, key, operation.isBeforeInvocation());
doEvict(cache, key, immediate);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public class CacheEvictOperation extends CacheOperation {

private final boolean beforeInvocation;

private final boolean immediate;


/**
* Create a new {@link CacheEvictOperation} instance from the given builder.
Expand All @@ -38,6 +40,7 @@ public CacheEvictOperation(CacheEvictOperation.Builder b) {
super(b);
this.cacheWide = b.cacheWide;
this.beforeInvocation = b.beforeInvocation;
this.immediate = b.immediate;
}


Expand All @@ -49,6 +52,10 @@ public boolean isBeforeInvocation() {
return this.beforeInvocation;
}

public boolean isImmediate() {
return this.immediate;
}


/**
* A builder that can be used to create a {@link CacheEvictOperation}.
Expand All @@ -60,6 +67,8 @@ public static class Builder extends CacheOperation.Builder {

private boolean beforeInvocation = false;

private boolean immediate = false;

public void setCacheWide(boolean cacheWide) {
this.cacheWide = cacheWide;
}
Expand All @@ -68,13 +77,19 @@ public void setBeforeInvocation(boolean beforeInvocation) {
this.beforeInvocation = beforeInvocation;
}

public void setImmediate(boolean immediate) {
this.immediate = immediate;
}

@Override
protected StringBuilder getOperationDescription() {
StringBuilder sb = super.getOperationDescription();
sb.append(',');
sb.append(this.cacheWide);
sb.append(',');
sb.append(this.beforeInvocation);
sb.append(',');
sb.append(this.immediate);
return sb;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,14 @@
invoked (default) or before.]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="immediate" type="xsd:boolean" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Whether the eviction must be performed immediately, expecting all affected
entries to be invisible for subsequent lookups as soon as the operation
returns. A before-invocation eviction is always immediate.]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@
<cache:cache-evict method="evict" key="#p0" cache="testCache"/>
<cache:cache-evict method="evictWithException" cache="testCache"/>
<cache:cache-evict method="evictEarly" cache="testCache" before-invocation="true"/>
<cache:cache-evict method="evictImmediate" key="#p0" cache="testCache" immediate="true"/>
<cache:cache-evict method="evictAll" cache="testCache" all-entries="true"/>
<cache:cache-evict method="evictAllEarly" cache="testCache" all-entries="true" before-invocation="true"/>
<cache:cache-evict method="evictAllImmediate" cache="testCache" all-entries="true" immediate="true"/>
</cache:caching>
<cache:caching cache="testCache">
<cache:cache-put method="update"/>
Expand Down Expand Up @@ -75,7 +77,9 @@
<cache:cache-evict method="evictWithException" cache="testCache"/>
<cache:cache-evict method="evictEarly" cache="testCache" before-invocation="true"/>
<cache:cache-evict method="invalidateEarly" key="#p0" cache="testCache" before-invocation="true"/>
<cache:cache-evict method="evictImmediate" key="#p0" cache="testCache" immediate="true"/>
<cache:cache-evict method="evictAll" cache="testCache" all-entries="true"/>
<cache:cache-evict method="evictAllImmediate" cache="testCache" all-entries="true" immediate="true"/>
</cache:caching>
<cache:caching cache="testCache">
<cache:cache-put method="update"/>
Expand Down
Loading