You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* Updated documentation
- Added more information around policies and ensured the documentation
of "forChildrenOf" were correctly renamed to "forThisOrChildrenOf"
- Added methods to the AeroMapper to get the policies explicitly if
desired.
- Documented the ability to pass 2 parameters to setters of properties
or keys, and added a unit test to the same
- Corrected a bug in the NonJavaMapperApplication unit test.
- Removed appropriate TODOs
- Added sections on batch loading, custom type conversions, etc
- Removed some obsolete TODOs
- Added a couple of images to help explain concepts.
- Extra logging to try to resolve test failure
- Fixed simpleDateFormat for timezone/24 hour format.
- Moved the TODO list out of the documentaion
@@ -200,7 +200,7 @@ If multiple configuration files are used and the same class is defined in multip
200
200
After the specified policy, there are 3 possible options:
201
201
202
202
- `forAll()`: The passed policy is used for all classes. This is similar to setting the defaultReadPolicy on the IAerospikeClient but allows it to be set after the client is created.
203
-
- `forChildrenOf(Class<?> class)`: The passed policy is used for the passed class and all subclasses of the passed class.
203
+
- `forThisOrChildrenOf(Class<?> class)`: The passed policy is used for the passed class and all subclasses of the passed class.
204
204
- `forClasses(Class<?> ... classes)`: The passed policy is used for the passed class(es), but no subclasses.
205
205
206
206
It is entirely possible that a class falls into more than one category, in which case the most specific policy is used. If no policy is specified, the defaultReadPolicy passed to the IAerospikeClient is used. For example, if there are classes A, B, C with C being a subclass of B, a definition could be for example:
In this case the `forAll()` would apply to A,B,C, the `forChildrenOf` would apply to B,C and `forClasses` would apply to C. So the policies used for each class would be:
218
+
In this case the `forAll()` would apply to A,B,C, the `forThisOrChildrenOf` would apply to B,C and `forClasses` would apply to C. So the policies used for each class would be:
219
219
220
220
- A: `readPolicy1`
221
221
- B: `readPolicy2`
222
222
- C: `readPolicy3`
223
223
224
224
Note that each operation can also optionally take a policy if it is desired to change any of the policy settings on the fly. The explicitly provided policy will override any other settings, such as `durableDelete` on the `@AerospikeRecord`
225
-
225
+
226
+
if it is desired to change one part of a policy but keep the rest as the defaults set up with these policies, the appropriate policy can be read with `getReadPolicy`, `getWritePolicy`, `getBatchPolicy`, `getScanPolicy` and `getQueryPolicy` methods on the AeroMapper. For example, if we needed a policy which was preiously set up on a Customer class but needed to change the `durableDelete` property, we could do
In summary, the policy which will be used for a call are: (lower number is a higher priority)
235
+
236
+
1. Policy passed as a parameter
237
+
2. Policy passed to `forClasses` method
238
+
3. Policy passed to `forThisOrChildrenOf` method
239
+
4. Policy passed to `forAll` method
240
+
5. AerospikeClient.getXxxxPolicyDefault
226
241
227
242
---
228
243
@@ -456,6 +471,131 @@ public int getCraziness() {
456
471
457
472
This will create a bin in the database with the name "bob".
458
473
474
+
It is possible for the setter to take an additional parameter too, providing this additional parameter is either a `Key` or `Value` object. This will be the key of the last object being loaded.
475
+
476
+
So, for example, if we have an A object which embeds a B, when the setter forB is called the second parameter will represent A's key:
477
+
478
+
```java
479
+
@AerospikeRecord(namespace = "test", set = "A", mapAll = false)
480
+
public class A {
481
+
@AerospikeBin
482
+
private String key;
483
+
private String value1;
484
+
private long value2;
485
+
486
+
@AerospikeGetter(name = "v1")
487
+
public String getValue1() {
488
+
return value1;
489
+
}
490
+
@AerospikeSetter(name = "v1")
491
+
public void setValue1(String value1, Value owningKey) {
492
+
// owningKey.getObject() will be a String of "B-1"
493
+
this.value1 = value1;
494
+
}
495
+
496
+
@AerospikeGetter(name = "v2")
497
+
public long getValue2() {
498
+
return value2;
499
+
}
500
+
501
+
@AerospikeSetter(name = "v2")
502
+
public void setValue2(long value2, Key key) {
503
+
// Key will have namespace="test", setName = "B", key.userKey.getObject() = "B-1"
504
+
this.value2 = value2;
505
+
}
506
+
}
507
+
508
+
@AerospikeRecord(namespace = "test", set = "B")
509
+
public class B {
510
+
@AerospikeKey
511
+
private String key;
512
+
@AerospikeEmbed
513
+
private A a;
514
+
}
515
+
516
+
@Test
517
+
public void test() {
518
+
A a = new A();
519
+
a.key = "A-1";
520
+
a.value1 = "value1";
521
+
a.value2 = 1000;
522
+
523
+
B b = new B();
524
+
b.key = "B-1";
525
+
b.a = a;
526
+
527
+
AeroMapper mapper = new AeroMapper.Builder(client).build();
528
+
mapper.save(b);
529
+
B b2 = mapper.read(B.class, b.key);
530
+
531
+
}
532
+
```
533
+
534
+
This can be useful in situations where the full key does not need to be stored in subordinate parts of the record. Consider a time-series use case where transactions are stored in a transaction container. The transactions for a single day might be grouped into a single transaction container, and the time of the transaction in microseconds may be the primary key of the transaction. If we model this with the transactions in the transaction container, the key for the transaction record could simply be the number of microseconds since the start of the day, as the microseconds representing the start of the day would be contained in the day number used as the transaction container key.
535
+
536
+
Since this information is redundant, it could be stripped out, shortening the length of the transaction key and hence saving storage space. However, when we wish to rebuild the transaction, we need the key of the transaction container to be able to derive the microseconds of the key to the start of the day to reform the appropriate transaction key.
537
+
538
+
----
539
+
540
+
## Default Mappings of Java Data type
541
+
Here are how standard Java types are mapped to Aerospike types:
542
+
| Java Type | Aerospike Type |
543
+
| --- | --- |
544
+
| byte | integral numeric |
545
+
| char | integral numeric |
546
+
| short | integral numeric |
547
+
| int | integral numeric |
548
+
| long | integral numeric |
549
+
| boolean | integral numeric |
550
+
| Byte | integral numeric |
551
+
| Character | integral numeric |
552
+
| Short | integral numeric |
553
+
| Integer | integral numeric |
554
+
| Long | integral numeric |
555
+
| Boolean | integral numeric |
556
+
| float | double numeric |
557
+
| double | double numeric |
558
+
| Float | double numeric |
559
+
| Double | double numeric |
560
+
| java.util.Date | integral numeric |
561
+
| java.time.Instant | integral numeric |
562
+
| String | String |
563
+
| byte[] | BLOB |
564
+
| enums | String |
565
+
| Arrays (int[], String[], Customer[], etc) | List |
566
+
| List<?> | List or Map |
567
+
| Map<?,?> | Map |
568
+
| Object Reference (@AerospikeRecord) | List or Map |
569
+
570
+
These types are built into the converter. However, if you wish to change them, you can use a (Custom Object Converter)]custom-object-converter]. For example, if you want Dates stored in the database as a string, you could do:
571
+
572
+
```java
573
+
public static class DateConverter {
574
+
private static final ThreadLocal<SimpleDateFormat> dateFormatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("dd-MM-yyyy HH:mm:ss.SSS zzzZ"));
575
+
@ToAerospike
576
+
public String toAerospike(Date date) {
577
+
if (date == null) {
578
+
return null;
579
+
}
580
+
return dateFormatter.get().format(date);
581
+
}
582
+
583
+
@FromAerospike
584
+
public Date fromAerospike(String dateStr) throws ParseException {
585
+
if (dateStr == null) {
586
+
return null;
587
+
}
588
+
return dateFormatter.get().parse(dateStr);
589
+
}
590
+
}
591
+
592
+
AeroMapper convertingMapper = new AeroMapper.Builder(client).addConverter(new DateConverter()).build();
593
+
```
594
+
595
+
(Note that SimpleDateFormat is not thread-safe, and hence the use of the ThreadLocal variable)
596
+
597
+
This would affect all dates. If you wanted to affect the format of some dates, create a sub-class Date and have the converter change that to the String format.
598
+
459
599
----
460
600
461
601
## References to other objects
@@ -707,6 +847,30 @@ Note that storing the digest as the referencing key is not compatible with lazy
707
847
708
848
will throw an exception at runtime.
709
849
850
+
#### BatchLoading
851
+
852
+
Note that when objects are stored by non-lazy references, all dependent children objects will be loaded by batch loading. For example, assume there is a complex object graph like:
853
+
854
+

855
+
856
+
Note that some of the objects are embedded and some are references.
857
+
858
+
If we then instantiate a complex object graph like:
859
+
860
+

861
+
862
+
Here you can see the Customer has a lot of dependent objects, where the white objects are being loaded by reference and the grey objects are being embedded into the parent. When the Customer is loaded the entire object graph is loaded. Looking at the calls that are performed to the database, we see:
863
+
864
+
```
865
+
Get: [test:customer:cust1:818d8a436587c36aef4da99d28eaf17e3ce3a0e1] took 0.211ms, record found
866
+
Batch: [4/4 keys] took 0.258ms
867
+
Batch: [6/6 keys] took 0.262ms
868
+
Batch: [2/2 keys] took 0.205ms
869
+
```
870
+
871
+
The first call (the `get`) is for the Customer object, the first batch of 4 is for the Cusomter's 4 accounts (Checking, Savings, Loan, Portfolio), the second batch of 6 items is for the 2 checkbooks and 4 security properties, and the last batch of 2 items is for the 2 branches. The AeroMapper will load all dependent objects it can in one hit, even if they're of different classes. This includes elements within LIsts, Arrays and Maps as well as straight dependent objects. This can make loading complex object graphs very efficient.
872
+
873
+
710
874
### Aggregating by Embedding
711
875
The other way object relationships can be modeled is by embedding the child object(s) inside the parent object. For example, in some banking systems, Accounts are based off Products. TheProducts are typically versioned but can have changes made to them by banking officers. Hence the product is effectively specific to a particular account, even though it is derived from a global product. Inthiscase, it makes sense to encapsulate the product into the account object.
712
876
@@ -1407,8 +1571,23 @@ Note that if an object is mapped to the actual type (eg Account to Account) then
1407
1571
1408
1572
For this reason, it is strongly recommended that all attributes use a parameterized type, eg `List<Account>` rather than `List`
1409
1573
1410
-
It should be noted that the use of subclasses
1574
+
It should be noted that the use of subclasses can have a minor degradation on performance. When the declared type is the same as the instantiated type, the JavaObjectMapper has already computed the optimal way of accessing that information. If it encounters a sub-class at runtime (i.e. theinstantiatedtypeisnotthesameasthedeclaredtype), it must then work out how to store the passed sub-class. The sub-class information is also typically cached so the performance hit should not be significant, but it is there.
1575
+
1576
+
By the same token, it is always better to use Java generics in collection types to give the Java Object Mapper hints about how to store the data in Aerospike so it can optimize its internal processes.
1577
+
1578
+
For example, say we need a list of Customers as a field on a class. We could declare this as:
1579
+
1580
+
```java
1581
+
public List<Customer> customers;
1582
+
```
1583
+
1584
+
or
1411
1585
1586
+
```java
1587
+
publicList customers;
1588
+
```
1589
+
1590
+
The former is considered better style in Java and also provides the JavaObjectMapper with information about the elements in the list, so it will optimize its workings to know how to store a list of Customers. The latter gives it no type information so it must derive the type -- and hence how to map it to Aerospike -- for every element in this list. This can have a noticeable performance impact for large lists, as well as consuming more database space (asitmuststoretheruntimetypeofeachelementinthelistinadditiontothedata).
1412
1591
1413
1592
1414
1593
----
@@ -1895,18 +2074,3 @@ public <T> T convertToObject(Class<T> clazz, Record record);
1895
2074
1896
2075
Note:At the moment not all CDT operations are supported, and if the underlying CDTs are of the wrong type, a different API call may be used. For example, if you invoke `getByKeyRange` on items represented in the database as a list, `getByValueRange` is invoked instead as a list has no key.
1897
2076
1898
-
1899
-
----
1900
-
1901
-
## To finish
1902
-
- Add interfaceto adaptiveMap, including changing EmbedType
1903
-
- Document all parameters to annotations and examples of types
1904
-
- Document enums, dates, instants.
1905
-
- Document methods with 2 parameters for keys and setters, the second one either a Key or a Value
1906
-
- Document subclasses and the mapping to tables + references stored as lists
1907
-
- Batch load of child items on Maps and References. Ensure testing of non-parameterized classes too.
1908
-
- Document batch loading
1909
-
- Ensure batch loading option exists in AerospikeReference Configuration
1910
-
- 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
1911
-
- Consider the items on virtual list which return a list to be able to return a map as well (ELEMENT_LIST, ELEMENT_MAP)
1912
-
- 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
0 commit comments