Skip to content

Commit ca3aeef

Browse files
committed
bulk operation update
1 parent 27a859c commit ca3aeef

File tree

13 files changed

+280
-85
lines changed

13 files changed

+280
-85
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3434
- Better timeout detection in watch operations
3535
- Multi-collection messaging error handling and lock release
3636
- Connection management in message rejection handler
37+
- **Bulk operations now return proper operation counts**: `runBulk()` now returns statistics including `num_inserted`, `num_matched`, `num_modified`, `num_deleted`, `num_upserts`, and `upsertedIds`
3738

3839
### Performance
3940
- Added collection name caching to reduce reflection overhead

docs/releases/CHANGELOG-6.0.1.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,33 @@ public class MyEntity {
101101
- Fixed exclusive message lock release
102102
- Enhanced connection management in rejection handler
103103

104+
### Bulk Operations
105+
106+
**Bulk Operations Now Return Proper Statistics**
107+
- Fixed `MorphiumBulkContext.runBulk()` to return operation statistics instead of null/empty results
108+
- All three driver implementations now properly collect and aggregate operation counts:
109+
- `InMemoryDriver` - Fixed inline bulk context implementation
110+
- `SingleMongoConnectDriver` - Fixed inline bulk context implementation
111+
- `PooledDriver` - Fixed inline bulk context implementation
112+
113+
**Returned Statistics:**
114+
```java
115+
Map<String, Object> result = bulkContext.runBulk();
116+
// Returns:
117+
// {
118+
// "num_inserted": <count>,
119+
// "num_matched": <count>,
120+
// "num_modified": <count>,
121+
// "num_deleted": <count>,
122+
// "num_upserts": <count>,
123+
// "upsertedIds": [<list of IDs>] // Only if upserts occurred
124+
// }
125+
```
126+
127+
**Impact:** Applications can now track bulk operation results for monitoring, logging, and validation purposes.
128+
129+
**Test Coverage:** Added comprehensive test `BulkOperationTest.bulkTestReturnCounts()` verifying all count returns.
130+
104131
### Performance Improvements
105132

106133
**Collection Name Caching**
@@ -150,6 +177,9 @@ public class MyEntity {
150177

151178
**Driver:**
152179
- `src/main/java/de/caluga/morphium/driver/wire/SingleMongoConnection.java` - Timeout handling
180+
- `src/main/java/de/caluga/morphium/driver/inmem/InMemoryDriver.java` - Bulk operations return counts
181+
- `src/main/java/de/caluga/morphium/driver/wire/SingleMongoConnectDriver.java` - Bulk operations return counts
182+
- `src/main/java/de/caluga/morphium/driver/wire/PooledDriver.java` - Bulk operations return counts
153183

154184
**Messaging:**
155185
- `src/main/java/de/caluga/morphium/messaging/Msg.java` - UseIfNull usage
@@ -159,6 +189,7 @@ public class MyEntity {
159189
- `src/test/java/de/caluga/test/mongo/suite/data/ComplexObject.java` - UseIfNull usage
160190
- `src/test/java/de/caluga/test/mongo/suite/base/UseIfNullTest.java` - New test suite (5 tests)
161191
- `src/test/java/de/caluga/test/mongo/suite/base/UseIfNullDistinctionTest.java` - New bidirectional tests (4 tests)
192+
- `src/test/java/de/caluga/test/mongo/suite/base/BulkOperationTest.java` - Added bulkTestReturnCounts() test
162193

163194
### Compatibility
164195

src/main/java/de/caluga/morphium/AnnotationAndReflectionHelper.java

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -38,22 +38,22 @@
3838
public class AnnotationAndReflectionHelper {
3939

4040
private final Logger logger = getLogger(AnnotationAndReflectionHelper.class);
41-
private final Map<Class<?>, Class<? >> realClassCache;
42-
private final Map<Class<?>, List<Field >> fieldListCache;
43-
private final ConcurrentHashMap<Class<?>, Map<Class<? extends Annotation>, Annotation >> annotationCache;
44-
private final Map<Class<?>, Map<String, String >> fieldNameCache;
41+
private final Map < Class<?>, Class<? >> realClassCache;
42+
private final Map < Class<?>, List<Field >> fieldListCache;
43+
private final ConcurrentHashMap < Class<?>, Map<Class<? extends Annotation >, Annotation >> annotationCache;
44+
private final Map < Class<?>, Map<String, String >> fieldNameCache;
4545
private static ConcurrentHashMap<String, String> classNameByType;
4646
private Map<String, Field> fieldCache;
4747
private Map<String, List<String>> fieldAnnotationListCache;
48-
private Map<Class<?>, Map<Class<? extends Annotation>, Method >> lifeCycleMethods;
49-
private Map<Class<?>, Boolean> hasAdditionalData;
48+
private Map<Class<?>, Map < Class<? extends Annotation >, Method >> lifeCycleMethods;
49+
private Map < Class<?>, Boolean > hasAdditionalData;
5050
private boolean ccc;
5151

5252
public AnnotationAndReflectionHelper(boolean convertCamelCase) {
5353
this(convertCamelCase, new HashMap<>());
5454
}
5555

56-
public AnnotationAndReflectionHelper(boolean convertCamelCase, Map<Class<?>, Class<? >> realClassCache) {
56+
public AnnotationAndReflectionHelper(boolean convertCamelCase, Map < Class<?>, Class<? >> realClassCache) {
5757
this.ccc = convertCamelCase;
5858
this.realClassCache = realClassCache;
5959
this.fieldListCache = new ConcurrentHashMap<>();
@@ -85,13 +85,13 @@ public void disableConvertCamelCase() {
8585
private void init() {
8686
//initializing type IDs
8787
try (ScanResult scanResult =
88-
new ClassGraph()
88+
new ClassGraph()
8989
// .verbose() // Enable verbose logging
9090
.enableAnnotationInfo()
9191
// .enableAllInfo() // Scan classes, methods, fields, annotations
9292
.scan()) {
9393
ClassInfoList entities =
94-
scanResult.getClassesWithAnnotation(Entity.class.getName());
94+
scanResult.getClassesWithAnnotation(Entity.class.getName());
9595
entities.addAll(scanResult.getClassesWithAnnotation(Embedded.class.getName()));
9696
logger.info("Found " + entities.size() + " entities in classpath");
9797

@@ -143,11 +143,11 @@ public Class getClassForTypeId(String typeId) throws ClassNotFoundException {
143143
return Class.forName(typeId);
144144
}
145145

146-
public <T extends Annotation> boolean isAnnotationPresentInHierarchy(final Class<?> aClass, final Class<? extends T> annotationClass) {
146+
public <T extends Annotation> boolean isAnnotationPresentInHierarchy(final Class<?> aClass, final Class <? extends T > annotationClass) {
147147
return getAnnotationFromHierarchy(aClass, annotationClass) != null;
148148
}
149149

150-
public boolean isAnnotationOnAnyField(final Class<?> aClass, final Class<? extends Annotation>annotationClass) {
150+
public boolean isAnnotationOnAnyField(final Class<?> aClass, final Class <? extends Annotation > annotationClass) {
151151
if (aClass == null || Map.class.isAssignableFrom(aClass)) return false;
152152

153153
for (Field f : getAllFields(aClass)) {
@@ -164,11 +164,11 @@ public boolean isAnnotationOnAnyField(final Class<?> aClass, final Class<? exten
164164
* @param superClass class
165165
* @return the Annotation
166166
*/
167-
public <T extends Annotation> T getAnnotationFromHierarchy(final Class<?> superClass, final Class<? extends T> annotationClass) {
167+
public <T extends Annotation> T getAnnotationFromHierarchy(final Class<?> superClass, final Class <? extends T > annotationClass) {
168168
if (superClass == null) { return null; }
169169

170170
final Class<?> aClass = getRealClass(superClass);
171-
Map<Class<? extends Annotation>, Annotation> cacheForClass = annotationCache.get(aClass);
171+
Map < Class <? extends Annotation>, Annotation > cacheForClass = annotationCache.get(aClass);
172172

173173
if (cacheForClass != null) {
174174
// Must be done with containsKey to enable caching of null
@@ -187,7 +187,7 @@ public <T extends Annotation> T getAnnotationFromHierarchy(final Class<?> superC
187187
return annotation;
188188
}
189189

190-
private <T extends Annotation> T annotationOfClassHierarchy(Class<?> aClass, Class<? extends T> annotationClass) {
190+
private <T extends Annotation> T annotationOfClassHierarchy(Class<?> aClass, Class <? extends T > annotationClass) {
191191
T annotation = null;
192192
Class<?> tmpClass = aClass;
193193

@@ -204,7 +204,7 @@ private <T extends Annotation> T annotationOfClassHierarchy(Class<?> aClass, Cla
204204
}
205205

206206
//check interfaces if nothing was found yet
207-
ArrayDeque<Class<?>> interfaces = new ArrayDeque<>();
207+
ArrayDeque < Class<?>> interfaces = new ArrayDeque<>();
208208
Collections.addAll(interfaces, aClass.getInterfaces());
209209

210210
while (!interfaces.isEmpty()) {
@@ -221,16 +221,16 @@ private <T extends Annotation> T annotationOfClassHierarchy(Class<?> aClass, Cla
221221
return null;
222222
}
223223

224-
public <T extends Annotation> T getAnnotationFromClass(final Class<?> cls, final Class<? extends T> annotationClass) {
224+
public <T extends Annotation> T getAnnotationFromClass(final Class<?> cls, final Class <? extends T > annotationClass) {
225225
final Class<?> aClass = getRealClass(cls);
226226
return aClass.getAnnotation(annotationClass);
227227
}
228228

229-
public <T> Class<? extends T> getRealClass(final Class<? extends T> superClass) {
229+
public <T> Class <? extends T > getRealClass(final Class <? extends T > superClass) {
230230
Class realClass = realClassCache.get(superClass);
231231

232232
if (realClass != null) {
233-
return (Class<? extends T>) realClass;
233+
return (Class <? extends T > ) realClass;
234234
}
235235

236236
if (isProxy(superClass)) {
@@ -293,7 +293,6 @@ public String getMongoFieldName(Class clz, String field, boolean ignoreUnknownFi
293293

294294
if ((inf.contains(List.class)) || inf.contains(Map.class) || inf.contains(Collection.class) || inf.contains(Set.class) || clz.isArray()) {
295295
//not diving into maps
296-
//TODO: check for generics type and dive into collecion/Maps
297296
} else {
298297
Field f = getField(cls, field);
299298

@@ -338,7 +337,7 @@ public String getMongoFieldName(Class clz, String field, boolean ignoreUnknownFi
338337
Embedded emb = getAnnotationFromHierarchy(cls, Embedded.class);
339338

340339
if ((ccc && ent != null && ent.translateCamelCase())
341-
|| (ccc && emb != null && emb.translateCamelCase())) {
340+
|| (ccc && emb != null && emb.translateCamelCase())) {
342341
ret = convertCamelCase(ret);
343342
}
344343
}
@@ -834,17 +833,17 @@ public Field getIdField(Object o) {
834833
* @param cls - the class to geht ghe Fields from
835834
* @return List of Strings, each a field name (as described in @Property or determined by name)
836835
*/
837-
public List<String> getFields(Class cls, Class<? extends Annotation>... annotations) {
836+
public List<String> getFields(Class cls, Class <? extends Annotation > ... annotations) {
838837
return getFields(cls, false, annotations);
839838
}
840839

841840
@SuppressWarnings("CommentedOutCode")
842-
public List<String> getFields(Class cls, boolean ignoreEntity, Class<? extends Annotation>... annotations) {
841+
public List<String> getFields(Class cls, boolean ignoreEntity, Class <? extends Annotation > ... annotations) {
843842
if (cls == null) { return new ArrayList<>(); }
844843

845844
StringBuilder stringBuilder = new StringBuilder(cls.toString());
846845

847-
for (Class<? extends Annotation> a : annotations) {
846+
for (Class <? extends Annotation > a : annotations) {
848847
stringBuilder.append("/");
849848
stringBuilder.append(a.toString());
850849
}
@@ -915,7 +914,7 @@ public List<String> getFields(Class cls, boolean ignoreEntity, Class<? extends A
915914
if (annotations.length > 0) {
916915
boolean found = false;
917916

918-
for (Class<? extends Annotation> a : annotations) {
917+
for (Class <? extends Annotation > a : annotations) {
919918
if (f.isAnnotationPresent(a)) {
920919
found = true;
921920
break;
@@ -1043,7 +1042,7 @@ public Double getDoubleValue(Object o, String fld) {
10431042
return (Double) getValue(o, fld);
10441043
}
10451044

1046-
public List<Annotation> getAllAnnotationsFromHierachy(Class<?> cls, Class<? extends Annotation>... anCls) {
1045+
public List<Annotation> getAllAnnotationsFromHierachy(Class<?> cls, Class <? extends Annotation > ... anCls) {
10471046
cls = getRealClass(cls);
10481047
List<Annotation> ret = new ArrayList<>();
10491048
Class<?> z = cls;
@@ -1054,7 +1053,7 @@ public List<Annotation> getAllAnnotationsFromHierachy(Class<?> cls, Class<? exte
10541053
ret.addAll(Arrays.asList(z.getAnnotations()));
10551054
} else {
10561055
for (Annotation a : z.getAnnotations()) {
1057-
for (Class<? extends Annotation> ac : anCls) {
1056+
for (Class <? extends Annotation > ac : anCls) {
10581057
if (a.annotationType().equals(ac)) {
10591058
ret.add(a);
10601059
}
@@ -1128,11 +1127,11 @@ public String getCreationTimeField(Class<?> cls) {
11281127
return lst.get(0);
11291128
}
11301129

1131-
public void callLifecycleMethod(Class<? extends Annotation> type, Object on) {
1130+
public void callLifecycleMethod(Class <? extends Annotation > type, Object on) {
11321131
callLifecycleMethod(type, on, new ArrayList());
11331132
}
11341133

1135-
private void callLifecycleMethod(Class<? extends Annotation> type, Object on, List calledOn) {
1134+
private void callLifecycleMethod(Class <? extends Annotation > type, Object on, List calledOn) {
11361135
if (on == null) {
11371136
return;
11381137
}
@@ -1173,7 +1172,7 @@ private void callLifecycleMethod(Class<? extends Annotation> type, Object on, Li
11731172
Field field = getField(on.getClass(), f);
11741173

11751174
if ((isAnnotationPresentInHierarchy(field.getType(), Entity.class) || isAnnotationPresentInHierarchy(field.getType(), Embedded.class)) &&
1176-
isAnnotationPresentInHierarchy(field.getType(), Lifecycle.class)) {
1175+
isAnnotationPresentInHierarchy(field.getType(), Lifecycle.class)) {
11771176
field.setAccessible(true);
11781177

11791178
try {
@@ -1193,7 +1192,7 @@ private void callLifecycleMethod(Class<? extends Annotation> type, Object on, Li
11931192
throw AnnotationAndReflectionException.of(e);
11941193
} catch (InvocationTargetException e) {
11951194
if (e.getCause().getClass().equals(MorphiumAccessVetoException.class)) {
1196-
throw(RuntimeException) e.getCause();
1195+
throw (RuntimeException) e.getCause();
11971196
}
11981197

11991198
throw AnnotationAndReflectionException.of(e);
@@ -1203,7 +1202,7 @@ private void callLifecycleMethod(Class<? extends Annotation> type, Object on, Li
12031202
return;
12041203
}
12051204

1206-
Map<Class<? extends Annotation>, Method> methods = new HashMap<>();
1205+
Map < Class <? extends Annotation>, Method> methods = new HashMap<>();
12071206

12081207
//Methods must be public
12091208
for (Method m : cls.getMethods()) {
@@ -1212,7 +1211,7 @@ private void callLifecycleMethod(Class<? extends Annotation> type, Object on, Li
12121211
}
12131212
}
12141213

1215-
Map<Class<?>, Map<Class<? extends Annotation>, Method >> lc = lifeCycleMethods;
1214+
Map < Class<?>, Map < Class <? extends Annotation >, Method >> lc = lifeCycleMethods;
12161215
lc.put(cls, methods);
12171216
if (methods.get(type) != null) {
12181217
try {
@@ -1229,13 +1228,13 @@ public boolean isAsyncWrite(Class<?> cls) {
12291228
return wb != null && wb.value();
12301229
}
12311230

1232-
private <T> boolean isProxy(Class<? extends T> aClass) {
1231+
private <T> boolean isProxy(Class <? extends T > aClass) {
12331232
if (aClass == null) { return false; }
12341233

12351234
return aClass.getName().contains("$$EnhancerByCGLIB$$");
12361235
}
12371236

1238-
private <T> Class<?> realClassOf(Class<? extends T> superClass) {
1237+
private <T> Class<?> realClassOf(Class <? extends T > superClass) {
12391238
try {
12401239
return Class.forName(superClass.getName().substring(0, superClass.getName().indexOf("$$")));
12411240
} catch (ClassNotFoundException e) {

src/main/java/de/caluga/morphium/cache/CacheSyncListener.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
* Date: 14.07.18
66
* Time: 22:39
77
* <p>
8-
* TODO: Add documentation here
98
*/
109
public interface CacheSyncListener {
1110
/**

0 commit comments

Comments
 (0)