Skip to content

Commit cb5da70

Browse files
authored
Added batch loading of objects (#7)
Added batch loading of objects which allows all sub-objects to be loaded in parallel.
1 parent 53e0ef8 commit cb5da70

File tree

5 files changed

+147
-14
lines changed

5 files changed

+147
-14
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1910,4 +1910,3 @@ Note: At the moment not all CDT operations are supported, and if the underlying
19101910
- handle object graph circularities (A->B->C). Be careful of: A->B(Lazy), A->C->B: B should end up fully hydrated in both instances, not lazy in both instances
19111911
- Consider the items on virtual list which return a list to be able to return a map as well (ELEMENT_LIST, ELEMENT_MAP)
19121912
- Test a constructor which requires a sub-object. For example, Account has a Property, Property has an Address. All 3 a referenced objects. Constructor for Property requires Address
1913-
- Create a batch get method which loads sub-objects in parallel

src/main/java/com/aerospike/mapper/annotations/AerospikeEmbed.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import java.lang.annotation.Target;
77

88
/**
9-
* Bins marked with AerospikeExclude will not be mapped to the database, irrespective of other annotations.
9+
* Bins marked with AerospikeEmbed will have the objects they reference embedded in the parent object, either as a list or a map
1010
*/
1111
@Retention(RetentionPolicy.RUNTIME)
1212
@Target(ElementType.FIELD)

src/main/java/com/aerospike/mapper/tools/AeroMapper.java

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.io.File;
44
import java.io.IOException;
5+
import java.lang.reflect.Array;
56
import java.util.ArrayList;
67
import java.util.List;
78
import java.util.Map;
@@ -248,7 +249,6 @@ public <T> T translateFromAerospike(@NotNull Object obj, @NotNull Class<T> expec
248249
return result;
249250
}
250251

251-
252252
/**
253253
* Save each object in the database. This method will perform a REPLACE on the existing record so any existing
254254
* data will be overwritten by the data in the passed object. This is a convenience method for
@@ -339,6 +339,13 @@ public <T> T read(Policy readPolicy, @NotNull Class<T> clazz, @NotNull Object us
339339
return read(readPolicy, clazz, key, entry, resolveDependencies);
340340
}
341341

342+
/**
343+
* Read a record from the repository and map it to an instance of the passed class.
344+
* @param clazz - The type of be returned.
345+
* @param userKey - The key of the record. The namespace and set will be derived from the values specified on the passed class.
346+
* @return The returned mapped record.
347+
* @throws AerospikeException an AerospikeException will be thrown in case of an error.
348+
*/
342349
public <T> T read(@NotNull Class<T> clazz, @NotNull Object userKey) throws AerospikeException {
343350
return this.read(clazz, userKey, true);
344351
}
@@ -364,7 +371,7 @@ private <T> T read(Policy readPolicy, @NotNull Class<T> clazz, @NotNull Key key,
364371
} else {
365372
try {
366373
ThreadLocalKeySaver.save(key);
367-
T result = (T) convertToObject(clazz, record, entry, resolveDepenencies);
374+
T result = convertToObject(clazz, record, entry, resolveDepenencies);
368375
return result;
369376
} catch (ReflectiveOperationException e) {
370377
throw new AerospikeException(e);
@@ -375,7 +382,68 @@ private <T> T read(Policy readPolicy, @NotNull Class<T> clazz, @NotNull Key key,
375382
}
376383
}
377384

378-
385+
/**
386+
* Read a batch of records from the repository and map them to an instance of the passed class.
387+
* @param clazz - The type of be returned.
388+
* @param userKeys - The keys of the record. The namespace and set will be derived from the values specified on the passed class.
389+
* @return The returned mapped records.
390+
* @throws AerospikeException an AerospikeException will be thrown in case of an error.
391+
*/
392+
public <T> T[] read(@NotNull Class<T> clazz, @NotNull Object ... userKeys) throws AerospikeException {
393+
return this.read(null, clazz, userKeys);
394+
}
395+
396+
/**
397+
* Read a batch of records from the repository and map them to an instance of the passed class.
398+
* @param batchPolicy A given batch policy.
399+
* @param clazz - The type of be returned.
400+
* @param userKeys - The keys of the record. The namespace and set will be derived from the values specified on the passed class.
401+
* @return The returned mapped records.
402+
* @throws AerospikeException an AerospikeException will be thrown in case of an error.
403+
*/
404+
public <T> T[] read(BatchPolicy batchPolicy, @NotNull Class<T> clazz, @NotNull Object ... userKeys) throws AerospikeException {
405+
ClassCacheEntry<T> entry = getEntryAndValidateNamespace(clazz);
406+
String set = entry.getSetName();
407+
Key[] keys = new Key[userKeys.length];
408+
for (int i = 0; i < userKeys.length; i++) {
409+
if (userKeys[i] == null) {
410+
throw new AerospikeException("Cannot pass null to object " + i + " in multi-read call");
411+
}
412+
else {
413+
keys[i] = new Key(entry.getNamespace(), set, Value.get(entry.translateKeyToAerospikeKey(userKeys[i])));
414+
}
415+
}
416+
417+
return this.readBatch(batchPolicy, clazz, keys, entry);
418+
}
419+
420+
private <T> T[] readBatch(BatchPolicy batchPolicy, @NotNull Class<T> clazz, @NotNull Key[] keys, @NotNull ClassCacheEntry<T> entry) {
421+
if (batchPolicy == null) {
422+
batchPolicy = entry.getBatchPolicy();
423+
}
424+
Record[] records = mClient.get(batchPolicy, keys);
425+
T[] results = (T[])Array.newInstance(clazz, records.length);
426+
for (int i = 0; i < records.length; i++) {
427+
if (records[i] == null) {
428+
results[i] = null;
429+
}
430+
else {
431+
try {
432+
ThreadLocalKeySaver.save(keys[i]);
433+
T result = convertToObject(clazz, records[i], entry, false);
434+
results[i] = result;
435+
} catch (ReflectiveOperationException e) {
436+
throw new AerospikeException(e);
437+
}
438+
finally {
439+
ThreadLocalKeySaver.clear();
440+
}
441+
}
442+
}
443+
resolveDependencies(entry);
444+
return results;
445+
}
446+
379447
public <T> boolean delete(@NotNull Class<T> clazz, @NotNull Object userKey) throws AerospikeException {
380448
return this.delete(null, clazz, userKey);
381449
}

src/test/java/com/aerospike/mapper/AeroMapperBaseTest.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,22 @@ public void clearCache() {
3535
ClassCache.getInstance().clear();
3636

3737
}
38-
38+
3939
public void compare(Object original, Object read) {
40+
this.compare(original, read, false);
41+
}
42+
43+
public void compare(Object original, Object read, boolean showObjects) {
4044
try {
4145
ObjectWriter objectWriter = new ObjectMapper().writerWithDefaultPrettyPrinter();
4246
String readString = objectWriter.writeValueAsString(read);
43-
System.out.println(readString);
47+
if (showObjects) {
48+
System.out.println("------ Read Data -----\n" + readString);
49+
}
4450
String originalObject = objectWriter.writeValueAsString(original);
51+
if (showObjects) {
52+
System.out.println("------ Original Data -----\n" + originalObject);
53+
}
4554
assertEquals(originalObject, readString);
4655
} catch (JsonProcessingException jpe) {
4756
throw new RuntimeException(jpe);

src/test/java/com/aerospike/mapper/BatchLoadTest.java

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,81 @@
33
import com.aerospike.mapper.annotations.AerospikeKey;
44
import com.aerospike.mapper.annotations.AerospikeRecord;
55
import com.aerospike.mapper.annotations.ParamFrom;
6+
import com.aerospike.mapper.tools.AeroMapper;
7+
import org.junit.Test;
8+
9+
import java.util.ArrayList;
10+
import java.util.Arrays;
11+
import java.util.List;
612

713
public class BatchLoadTest extends AeroMapperBaseTest {
8-
@AerospikeRecord(namespace = "test", set = "C")
9-
public static class C {
14+
15+
@AerospikeRecord(namespace = "test", set = "B")
16+
public static class B {
1017
@AerospikeKey
1118
public int id;
1219
public String name;
1320

14-
public C(@ParamFrom("id") int id, @ParamFrom("name") String name) {
15-
super();
21+
public B(@ParamFrom("id") int id, @ParamFrom("name") String name) {
1622
this.id = id;
1723
this.name = name;
1824
}
1925
}
20-
21-
@AerospikeRecord(namespace = "test", set = "B")
22-
public static class B {
26+
27+
@AerospikeRecord(namespace = "test", set = "A")
28+
public static class A {
2329
@AerospikeKey
2430
public int id;
31+
public String name;
32+
public List<B> data;
33+
34+
public A(int id, String name) {
35+
this.id = id;
36+
this.name = name;
37+
data = new ArrayList<>();
38+
}
39+
40+
public void setBList(List<B> bees) {
41+
data = bees;
42+
}
43+
44+
public A() {}
45+
}
46+
47+
@Test
48+
public void testBatchLoad() {
49+
AeroMapper mapper = new AeroMapper.Builder(client).build();
50+
51+
B[] bees = new B[100];
52+
for (int i = 0; i < 100; i++) {
53+
bees[i] = new B(i, "B-" + i);
54+
}
55+
56+
A[] as = new A[10];
57+
for (int i = 0; i < 10; i++) {
58+
as[i] = new A(100 + i, "A-" + i);
59+
as[i].setBList(Arrays.asList(
60+
Arrays.copyOfRange(bees, i * 10, (i + 1) * 10)));
61+
}
62+
63+
mapper.save((Object[])bees);
64+
mapper.save((Object[])as);
65+
66+
System.out.println("--- Reading single object (bees[1]) ---");
67+
B resultB = mapper.read(B.class, bees[1].id);
68+
compare(bees[1], resultB);
69+
70+
System.out.println("--- Reading single object (a[1]) ---");
71+
A resultA = mapper.read(A.class, as[1].id);
72+
compare(as[1], resultA);
73+
74+
System.out.println("--- Reading batch object with 6 keys ---");
75+
A[] results = mapper.read(A.class, as[4].id, as[7].id, as[5].id, as[0].id, as[1].id, 3000);
76+
compare(results[0], as[4]);
77+
compare(results[1], as[7]);
78+
compare(results[2], as[5]);
79+
compare(results[3], as[0]);
80+
compare(results[4], as[1]);
81+
compare(results[5], null);
2582
}
2683
}

0 commit comments

Comments
 (0)