Browse Source

Implement JCacheCache#putIfAbsent as atomic operation

This commit modifies putIfAbsent to use an EntryProcessor that
guarantees that the operation is atomic.

Closes gh-21591
pull/31253/head
Stéphane Nicoll 1 year ago
parent
commit
f79bc7b14d
  1. 22
      spring-context-support/src/main/java/org/springframework/cache/jcache/JCacheCache.java
  2. 21
      spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheEhCacheApiTests.java

22
spring-context-support/src/main/java/org/springframework/cache/jcache/JCacheCache.java vendored

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2023 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.
@ -98,8 +98,8 @@ public class JCacheCache extends AbstractValueAdaptingCache { @@ -98,8 +98,8 @@ public class JCacheCache extends AbstractValueAdaptingCache {
@Override
@Nullable
public ValueWrapper putIfAbsent(Object key, @Nullable Object value) {
boolean set = this.cache.putIfAbsent(key, toStoreValue(value));
return (set ? null : get(key));
Object previous = this.cache.invoke(key, PutIfAbsentEntryProcessor.INSTANCE, toStoreValue(value));
return (previous != null ? toValueWrapper(previous) : null);
}
@Override
@ -125,6 +125,22 @@ public class JCacheCache extends AbstractValueAdaptingCache { @@ -125,6 +125,22 @@ public class JCacheCache extends AbstractValueAdaptingCache {
}
private static class PutIfAbsentEntryProcessor implements EntryProcessor<Object, Object, Object> {
private static final PutIfAbsentEntryProcessor INSTANCE = new PutIfAbsentEntryProcessor();
@Override
@Nullable
public Object process(MutableEntry<Object, Object> entry, Object... arguments) throws EntryProcessorException {
Object existingValue = entry.getValue();
if (existingValue == null) {
entry.setValue(arguments[0]);
}
return existingValue;
}
}
private class ValueLoaderEntryProcessor<T> implements EntryProcessor<Object, Object, T> {
@SuppressWarnings("unchecked")

21
spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheEhCacheApiTests.java vendored

@ -24,9 +24,12 @@ import javax.cache.spi.CachingProvider; @@ -24,9 +24,12 @@ import javax.cache.spi.CachingProvider;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.testfixture.cache.AbstractValueAdaptingCacheTests;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Stephane Nicoll
*/
@ -79,4 +82,22 @@ public class JCacheEhCacheApiTests extends AbstractValueAdaptingCacheTests<JCach @@ -79,4 +82,22 @@ public class JCacheEhCacheApiTests extends AbstractValueAdaptingCacheTests<JCach
return this.nativeCache;
}
@Test
void testPutIfAbsentNullValue() {
JCacheCache cache = getCache(true);
String key = createRandomKey();
String value = null;
assertThat(cache.get(key)).isNull();
assertThat(cache.putIfAbsent(key, value)).isNull();
assertThat(cache.get(key).get()).isEqualTo(value);
org.springframework.cache.Cache.ValueWrapper wrapper = cache.putIfAbsent(key, "anotherValue");
// A value is set but is 'null'
assertThat(wrapper).isNotNull();
assertThat(wrapper.get()).isNull();
// not changed
assertThat(cache.get(key).get()).isEqualTo(value);
}
}

Loading…
Cancel
Save