Skip to content

Commit bc96b06

Browse files
committed
增加严格null模式
1 parent eaf4e1c commit bc96b06

File tree

14 files changed

+278
-40
lines changed

14 files changed

+278
-40
lines changed

README-EN.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<dependency>
1212
<groupId>io.github.ms100</groupId>
1313
<artifactId>cache-as-multi</artifactId>
14-
<version>1.1.3</version>
14+
<version>1.2.0</version>
1515
</dependency>
1616
```
1717

@@ -254,9 +254,9 @@ Here are some examples:
254254
the `collection parameter`
255255
and other parameters to calculate the cache key using `KeyGenerator.generate(Object, Method, Object...)`; the same
256256
goes for custom `CacheKeyGenerator`.
257-
3. When used in conjunction with cache-generating annotations, if the return type of the method is `Map`, the value of
258-
the corresponding `element` in the `Map` is `null`,
259-
then `null` will be cached, and if the `element` does not exist in the `Map`, it will not be cached.
257+
3. When used in conjunction with `@Cacheable`,`@CacheResult` and `@CachePut`, if `CacheAsMulti.strictNull()`
258+
is `true` and the return type of the method is `Map`, the value of the corresponding `element` in the `Map`
259+
is `null`, then `null` will be cached, and if the `element` does not exist in the `Map`, it will not be cached.
260260
4. When used in conjunction with `@CachePut` and `@CacheEvict`, if the key parameter of the annotation is configured
261261
with `#result`,
262262
and the return type of the method is `Map`, `null` will be used as the default value to calculate the cache key and

README.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<dependency>
1111
<groupId>io.github.ms100</groupId>
1212
<artifactId>cache-as-multi</artifactId>
13-
<version>1.1.3</version>
13+
<version>1.2.0</version>
1414
</dependency>
1515
```
1616

@@ -215,10 +215,14 @@ class FooService {
215215
```
216216

217217
### 总结和补充
218-
1. `@CacheAsMulti` 注解不能替代 Spring 缓存注解中的 key 参数,例如:`@Cacheable.key()`,也不能替代 `@CacheKey``@CacheValue` 注解。
219-
2. 如果使用自定义的 `KeyGenerator`,则会用【对象集合参数】的每个【元素】和其他参数组成 `Object[]` 传入 `KeyGenerator.generate(Object, Method, Object...)` 计算缓存 key;自定义的 `CacheKeyGenerator` 也一样。
220-
3. 与生成缓存的注解搭配使用时,若方法的返回类型是 `Map`,【元素】在 `Map` 中对应的值为 `null` 就会缓存 `null`,【元素】在 `Map` 中不存在就不缓存。
221-
4.`@CachePut``@CacheEvict` 搭配,注解的 key 参数配置了 `#result` 时,若方法的返回类型是 `Map`,对于 `Map` 中不存在的【元素】会使用 `null` 作为缺省值来计算缓存 key 和 condition、unless 条件。
218+
1. `@CacheAsMulti` 注解不能替代 Spring 缓存注解中的 key 参数,例如:`@Cacheable.key()`
219+
,也不能替代 `@CacheKey``@CacheValue` 注解。
220+
2. 如果使用自定义的 `KeyGenerator`,则会用【对象集合参数】的每个【元素】和其他参数组成 `Object[]`
221+
传入 `KeyGenerator.generate(Object, Method, Object...)` 计算缓存 key;自定义的 `CacheKeyGenerator` 也一样。
222+
3.`@Cacheable``@CacheResult``@CachePut` 注解搭配使用时,若 `CacheAsMulti.strictNull()``true`
223+
且方法的返回类型是 `Map`,【元素】在 `Map` 中对应的值为 `null` 就会缓存 `null`,【元素】在 `Map` 中不存在就不缓存。
224+
4.`@CachePut``@CacheEvict` 搭配,注解的 key 参数配置了 `#result` 时,若方法的返回类型是 `Map`,对于 `Map`
225+
中不存在的【元素】会使用 `null` 作为缺省值来计算缓存 key 和 condition、unless 条件。
222226
5. `@Cacheable.condition()``@Cacheable.unless()` 等条件表达式是用【对象集合参数】中的每个【元素】分别计算,只将不符合的【元素】排除,而不是整个集合。
223227

224228
## 缓存接口及转换

cache-as-multi-sample/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
<dependency>
4646
<groupId>io.github.ms100</groupId>
4747
<artifactId>cache-as-multi</artifactId>
48-
<version>1.1.3</version>
48+
<version>1.2.0</version>
4949
</dependency>
5050
<!-- test -->
5151
<dependency>

cache-as-multi/pom.xml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
<modelVersion>4.0.0</modelVersion>
66
<groupId>io.github.ms100</groupId>
77
<artifactId>cache-as-multi</artifactId>
8-
<version>1.1.3</version>
8+
<version>1.2.0</version>
99

1010
<name>Cache As Multi</name>
1111
<url>https://github.com/ms100/cache-as-multi</url>
1212
<description>
13-
Enhance spring cache annotation and realize the batch data caching.
14-
扩展 Spring Cache 实现批量数据的缓存。
13+
Extend Spring Cache to implement individual caching for each item in batch data, which can improve cache hit
14+
rate and utilization. Additionally, this extension allows the methods for operating on batch data to share the
15+
cache with the methods for operating on individual data.
16+
扩展 Spring Cache 以实现批量数据中每项的单独缓存,从而提升缓存的命中率和使用率,并且可以使操作批量数据的方法与操作单个数据的方法共用缓存。
1517
</description>
1618

1719
<inceptionYear>2023</inceptionYear>

cache-as-multi/src/main/java/io/github/ms100/cacheasmulti/cache/annotation/CacheAsMulti.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@
180180
* <ol>
181181
* <li>@CacheAsMulti 注解不能替代 Spring 缓存注解中的 key 参数,例如:{@link Cacheable#key()},也不能替代 {@link javax.cache.annotation.CacheKey @CacheKey}、{@link javax.cache.annotation.CacheValue @CacheValue} 注解。</li>
182182
* <li>如果使用自定义的 {@link KeyGenerator},则会用【对象集合参数】的每个【元素】和其他参数组成 Object[] 传入 {@link KeyGenerator#generate(Object, Method, Object...)} 计算缓存 key;自定义的 {@link javax.cache.annotation.CacheKeyGenerator CacheKeyGenerator} 也一样。</li>
183-
* <li>与生成缓存的注解搭配使用时,若方法的返回类型是 Map,【元素】在 Map 中对应的值为 null 就会缓存 null,【元素】在 Map 中不存在就不缓存。</li>
183+
* <li>与 @Cacheable、@CacheResult、@CachePut 注解搭配使用时,若 {@link CacheAsMulti#strictNull()} 为 true 且方法的返回类型是 Map,【元素】在 Map 中对应的值为 null 就会缓存 null,【元素】在 Map 中不存在就不缓存。</li>
184184
* <li>与 {@link CachePut} 和 {@link CacheEvict} 搭配,注解的 key 参数配置了 #result 时,若方法的返回类型是 Map,对于 Map 中不存在的【元素】会使用 null 作为缺省值来计算缓存 key 和 condition、unless 条件。</li>
185185
* <li>{@link Cacheable#condition()}、{@link Cacheable#unless()} 等条件表达式是用【对象集合参数】中的每个【元素】分别计算,只将不符合的【元素】排除,而不是整个集合。</li>
186186
* </ol>
@@ -190,7 +190,14 @@
190190
@Target(ElementType.PARAMETER)
191191
@Retention(RetentionPolicy.RUNTIME)
192192
public @interface CacheAsMulti {
193-
193+
/**
194+
* 严格的缓存数据为 null 的策略
195+
* 为 false 时,返回值 map 中未包含的参数项都会被缓存为 null;为 ture 时,返回值 map 中包含的为 null 的参数项才会被缓存为 null
196+
* 例如:参数传入 List[1,2,3],返回 Map{1:"A",3:"C"}。当为 false 时,2 会被缓存为 null;当为 true 时,2 不会被缓存。
197+
*
198+
* @return 默认为 false,如果方法返回值Map里会包含为 null 的元素,请将此值设置为 true
199+
*/
200+
boolean strictNull() default false;
194201
}
195202

196203

cache-as-multi/src/main/java/io/github/ms100/cacheasmulti/cache/annotation/CacheAsMultiParameterDetail.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,19 @@ public class CacheAsMultiParameterDetail {
3232
*/
3333
private final Annotation[] annotations;
3434

35+
/**
36+
* 严格的null模式
37+
*/
38+
@Getter
39+
private final boolean strictNull;
40+
3541
public CacheAsMultiParameterDetail(Method method, int position) {
3642
Parameter parameter = method.getParameters()[position];
3743
this.annotations = parameter.getAnnotations();
3844
this.rawType = parameter.getType();
3945
this.position = position;
46+
CacheAsMulti annotation = parameter.getAnnotation(CacheAsMulti.class);
47+
this.strictNull = annotation.strictNull();
4048
}
4149

4250
public boolean isAnnotationPresent(Class<? extends Annotation> clazz) {

cache-as-multi/src/main/java/io/github/ms100/cacheasmulti/cache/interceptor/AbstractCacheAsMultiOperation.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ public int getCacheAsMultiParameterPosition() {
5151
return parameterDetail.getPosition();
5252
}
5353

54+
public boolean isStrictNull() {
55+
return parameterDetail.isStrictNull();
56+
}
57+
5458
public Collection<?> newCacheAsMultiArg(Collection<?> subCacheAsMultiArg) {
5559
assert cacheAsMultiArgCreator != null;
5660
return cacheAsMultiArgCreator.apply(subCacheAsMultiArg);

cache-as-multi/src/main/java/io/github/ms100/cacheasmulti/cache/interceptor/EnhancedCachingInterceptor.java

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,16 @@ private Object execute(final CacheOperationInvoker invoker, CacheAsMultiOperatio
133133
argValueMap = multiOperation.makeCacheMap(cacheAsMultiArg, returnValue);
134134

135135
if (!CollectionUtils.isEmpty(argValueMap)) {
136-
putCachedItems(contexts.get(CacheableOperation.class), argValueMap);
137-
putCachedItems(contexts.get(CachePutOperation.class), argValueMap);
136+
if (multiOperation.isStrictNull()) {
137+
putCachedItems(contexts.get(CacheableOperation.class), argValueMap);
138+
putCachedItems(contexts.get(CachePutOperation.class), argValueMap);
139+
} else {
140+
putCachedItems(contexts.get(CacheableOperation.class), cacheAsMultiArg, argValueMap);
141+
putCachedItems(contexts.get(CachePutOperation.class), cacheAsMultiArg, argValueMap);
142+
}
143+
} else if (!multiOperation.isStrictNull()) {
144+
putCachedItems(contexts.get(CacheableOperation.class), cacheAsMultiArg, Collections.emptyMap());
145+
putCachedItems(contexts.get(CachePutOperation.class), cacheAsMultiArg, Collections.emptyMap());
138146
}
139147
} else {
140148
Pair<Map<?, ?>, Object> pair = findCachedItems(contexts, invoker);
@@ -153,7 +161,6 @@ private Object execute(final CacheOperationInvoker invoker, CacheAsMultiOperatio
153161

154162
processCacheEvicts(contexts, false, argValueMap);
155163

156-
157164
return returnValue;
158165
}
159166

@@ -248,6 +255,14 @@ private void performCacheEvict(
248255
return invokeWithMissCacheAsMultiArg(cacheableContexts, invoker, missCacheAsMultiArg, argValueMap);
249256
}
250257

258+
/**
259+
* 从缓存里查找
260+
*
261+
* @param context 上下文
262+
* @param subCacheAsMultiArg CacheAsMulti参数
263+
* @param argValueMap 保存参数与值的映射
264+
* @return 未命中缓存的参数
265+
*/
251266
private Collection<?> findInCaches(CacheAsMultiOperationContext context,
252267
Collection<?> subCacheAsMultiArg, Map<Object, Object> argValueMap) {
253268

@@ -276,11 +291,15 @@ private Collection<?> findInCaches(CacheAsMultiOperationContext context,
276291

277292
missKeys.clear();
278293
Collection<Object> newMissCacheAsMultiArg = new ArrayList<>(missCacheAsMultiArg.size());
294+
boolean strictNull = context.getMultiOperation().isStrictNull();
279295
missCacheAsMultiArg.forEach(argItem -> {
280296
Object key = argKeyMap.get(argItem);
281297
ValueWrapper valueWrapper = hitKeyValueWrapperMap.get(key);
282298
if (valueWrapper != null) {
283-
argValueMap.put(argItem, valueWrapper.get());
299+
Object value = valueWrapper.get();
300+
if (value != null || strictNull) {
301+
argValueMap.put(argItem, value);
302+
}
284303
} else {
285304
newMissCacheAsMultiArg.add(argItem);
286305
missKeys.add(key);
@@ -316,32 +335,42 @@ private Collection<?> findInCaches(CacheAsMultiOperationContext context,
316335
Object invokeValues = invokeOperation(firstContext, invoker, missCacheAsMultiArg);
317336

318337
// 如果执行结果为null,缓存也没有任何命中,直接返回null
319-
if (invokeValues == null && argValueMap.size() == 0) {
338+
/*if (invokeValues == null && argValueMap.size() == 0) {
320339
return Pair.of(argValueMap, null);
321-
}
340+
}*/
322341

323342
CacheAsMultiOperation<?> multiOperation = firstContext.getMultiOperation();
324343
Map<?, ?> missArgValueMap = multiOperation.makeCacheMap(missCacheAsMultiArg, invokeValues);
325344

326345
// 如果invokeValues是null或者空map,那missArgValueMap也是null或者空map
327346
if (!CollectionUtils.isEmpty(missArgValueMap)) {
328347
// 缓存数据
329-
putCachedItems(contexts, missArgValueMap);
348+
if (multiOperation.isStrictNull()) {
349+
putCachedItems(contexts, missArgValueMap);
350+
} else {
351+
putCachedItems(contexts, missCacheAsMultiArg, missArgValueMap);
352+
}
330353

331354
// 如果缓存都未命中,直接返回执行结果
332355
if (argValueMap.size() == 0) {
333356
return Pair.of(missArgValueMap, invokeValues);
334357
}
335358

336359
argValueMap.putAll(missArgValueMap);
360+
} else if (!multiOperation.isStrictNull()) {
361+
putCachedItems(contexts, missCacheAsMultiArg, Collections.emptyMap());
337362
}
338363

339364
return Pair.of(argValueMap, multiOperation.makeReturnObject(firstContext.getCacheAsMultiArg(), argValueMap));
340365
}
341366

342367
private void putCachedItems(Collection<CacheAsMultiOperationContext> contexts, Map<?, ?> argValueMap) {
368+
putCachedItems(contexts, argValueMap.keySet(), argValueMap);
369+
}
370+
371+
private void putCachedItems(Collection<CacheAsMultiOperationContext> contexts, Collection<?> subCacheAsMultiArg, Map<?, ?> argValueMap) {
343372
for (CacheAsMultiOperationContext context : contexts) {
344-
Pair<Collection<?>, Collection<?>> pair = splitIsConditionPassing(context, argValueMap.keySet(), argValueMap);
373+
Pair<Collection<?>, Collection<?>> pair = splitIsConditionPassing(context, subCacheAsMultiArg, argValueMap);
345374
if (pair.getLeft().isEmpty()) {
346375
continue;
347376
}

cache-as-multi/src/main/java/io/github/ms100/cacheasmulti/jcache/interceptor/CacheResultAsMultiInterceptor.java

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,11 @@ private Collection<?> findInCache(CacheAsMultiOperationContext<CacheResultAsMult
7272
CacheResultAsMultiOperation multiOperation = context.getMultiOperation();
7373
// 从缓存中取出的值
7474
final Map<Object, ValueWrapper> hitKeyValueWrapperMap;
75-
if (!multiOperation.isAlwaysInvoked()) {
75+
if (multiOperation.isAlwaysInvoked()) {
76+
hitKeyValueWrapperMap = null;
77+
} else {
7678
EnhancedCache cache = resolveCache(context);
7779
hitKeyValueWrapperMap = doMultiGet(cache, argKeyMap.values());
78-
} else {
79-
hitKeyValueWrapperMap = null;
8080
}
8181

8282
// 没有找到缓存的参数
@@ -85,10 +85,14 @@ private Collection<?> findInCache(CacheAsMultiOperationContext<CacheResultAsMult
8585
missCacheAsMultiArg = cacheAsMultiArg;
8686
} else {
8787
Collection<Object> newMissCacheAsMultiArg = new ArrayList<>(cacheAsMultiArg.size());
88+
boolean strictNull = multiOperation.isStrictNull();
8889
argKeyMap.forEach((argItem, key) -> {
8990
ValueWrapper valueWrapper = hitKeyValueWrapperMap.get(key);
9091
if (valueWrapper != null) {
91-
argValueMap.put(argItem, valueWrapper.get());
92+
Object value = valueWrapper.get();
93+
if (value != null || strictNull) {
94+
argValueMap.put(argItem, value);
95+
}
9296
} else {
9397
newMissCacheAsMultiArg.add(argItem);
9498
}
@@ -110,9 +114,9 @@ private Object invokeWithMissCacheAsMultiArg(CacheAsMultiOperationContext<CacheR
110114
Object invokeValues = invokeOperation(context, invoker, missCacheAsMultiArg);
111115

112116
// 如果执行结果为null,缓存也没有任何命中,直接返回null
113-
if (invokeValues == null && argValueMap.size() == 0) {
117+
/*if (invokeValues == null && argValueMap.size() == 0) {
114118
return null;
115-
}
119+
}*/
116120

117121
CacheResultAsMultiOperation multiOperation = context.getMultiOperation();
118122
Map<?, ?> missArgValueMap = multiOperation.makeCacheMap(missCacheAsMultiArg, invokeValues);
@@ -121,8 +125,11 @@ private Object invokeWithMissCacheAsMultiArg(CacheAsMultiOperationContext<CacheR
121125
// 需要缓存的数据
122126
Map<Object, Object> missKeyValueMap = CollectionUtils.newHashMap(missCacheAsMultiArg.size());
123127
// 关于值不存在的参数,invoke返回的结果中值为null就缓存null,不存在就不缓存。
124-
missArgValueMap.forEach((argItem, value) -> missKeyValueMap.put(context.generateKey(argItem), value));
125-
128+
if (multiOperation.isStrictNull()) {
129+
missArgValueMap.forEach((argItem, value) -> missKeyValueMap.put(context.generateKey(argItem), value));
130+
} else {
131+
missCacheAsMultiArg.forEach(argItem -> missKeyValueMap.put(context.generateKey(argItem), missArgValueMap.get(argItem)));
132+
}
126133
EnhancedCache cache = resolveCache(context);
127134
// 缓存数据
128135
doMultiPut(cache, missKeyValueMap);
@@ -133,6 +140,13 @@ private Object invokeWithMissCacheAsMultiArg(CacheAsMultiOperationContext<CacheR
133140
}
134141

135142
argValueMap.putAll(missArgValueMap);
143+
} else if (!multiOperation.isStrictNull()) {
144+
// 需要缓存的数据
145+
Map<Object, Object> missKeyValueMap = CollectionUtils.newHashMap(missCacheAsMultiArg.size());
146+
missCacheAsMultiArg.forEach(argItem -> missKeyValueMap.put(context.generateKey(argItem), null));
147+
EnhancedCache cache = resolveCache(context);
148+
// 缓存数据
149+
doMultiPut(cache, missKeyValueMap);
136150
}
137151

138152
Collection<?> cacheAsMultiArg = (Collection<?>) context.getCacheAsMultiArg();

cache-as-multi/src/test/java/io/github/ms100/cacheasmulti/cache/CacheAsMultiTest.java

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
package io.github.ms100.cacheasmulti.cache;
22

3+
import io.github.ms100.cacheasmulti.cache.service.BoxService;
34
import io.github.ms100.cacheasmulti.cache.service.FarService;
45
import io.github.ms100.cacheasmulti.cache.service.NewBarServiceImpl;
56
import org.junit.jupiter.api.Assertions;
67
import org.junit.jupiter.api.Test;
78
import org.springframework.beans.factory.annotation.Autowired;
89
import org.springframework.boot.test.context.SpringBootTest;
9-
import org.springframework.cache.annotation.EnableCaching;
10-
import org.springframework.core.Ordered;
1110

1211
import java.util.ArrayList;
12+
import java.util.Arrays;
1313
import java.util.HashSet;
1414
import java.util.List;
1515
import java.util.Set;
@@ -19,12 +19,12 @@
1919
*/
2020
@SpringBootTest
2121
class CacheAsMultiTest {
22-
2322
@Autowired
2423
private NewBarServiceImpl barService;
25-
2624
@Autowired
2725
private FarService farService;
26+
@Autowired
27+
private BoxService boxService;
2828

2929
private final Integer id = 3;
3030

@@ -114,4 +114,46 @@ void putBar() {
114114
System.out.println(str);
115115
Assertions.assertNotEquals("BBB", str);
116116
}
117+
118+
@Test
119+
void getMultiBox() {
120+
boxService.delMultiBox(ids);
121+
boxService.getMultiBox(ids);
122+
boxService.getMultiBox(ids);
123+
boxService.getMultiBox(ids);
124+
Set<Integer> ids2 = new HashSet<>(Arrays.asList(1, 3, 5, 7));
125+
boxService.delMultiBox(ids2);
126+
System.out.println(boxService.getMultiBox(ids2));
127+
System.out.println(boxService.getMultiBox(ids2));
128+
}
129+
130+
@Test
131+
void putMultiBox() {
132+
boxService.delMultiBox(ids);
133+
boxService.putMultiBox(ids);
134+
boxService.getMultiBox(ids);
135+
Set<Integer> ids2 = new HashSet<>(Arrays.asList(1, 3, 5, 7));
136+
boxService.delMultiBox(ids2);
137+
System.out.println(boxService.putMultiBox(ids2));
138+
System.out.println(boxService.getMultiBox(ids2));
139+
}
140+
141+
@Test
142+
void getMultiBox2() {
143+
boxService.delMultiBox2(ids, "a");
144+
boxService.getMultiBox2(ids, "a");
145+
boxService.getMultiBox2(ids, "a");
146+
boxService.getMultiBox2(ids, "a");
147+
Set<Integer> ids2 = new HashSet<>(Arrays.asList(1, 3, 5, 7));
148+
boxService.delMultiBox2(ids2, "a");
149+
System.out.println(boxService.getMultiBox2(ids2, "a"));
150+
System.out.println(boxService.getMultiBox2(ids2, "a"));
151+
}
152+
153+
@Test
154+
void putMultiBox2() {
155+
boxService.delMultiBox2(ids, "a");
156+
boxService.putMultiBox2(ids, "a");
157+
boxService.getMultiBox2(ids, "a");
158+
}
117159
}

0 commit comments

Comments
 (0)