Skip to content

Commit 18f8a38

Browse files
committed
Documentation for virtual lists
1 parent 8d24eb9 commit 18f8a38

File tree

2 files changed

+180
-4
lines changed

2 files changed

+180
-4
lines changed

README.md

Lines changed: 170 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1728,10 +1728,177 @@ The reference structure is used when the object being referenced is not to be em
17281728

17291729
When mapping a Java object to Aerospike the most common operations to do are to save the whole object and load the whole object. The AeroMapper is set up primarily for these use cases. However, there are cases where it makes sense to manipulate objects directly in the database, particularly when it comes to manipulating lists and maps. This functionality is provided via virtual lists.
17301730

1731+
Consider a TODO list, where there are Items which contain the items to be performed and a container for these items:
1732+
1733+
```java
1734+
@AerospikeRecord(namespace = "test", set = "item")
1735+
public class Item {
1736+
@AerospikeKey
1737+
private int id;
1738+
private Date due;
1739+
private String desc;
1740+
public Item(int id, Date due, String desc) {
1741+
super();
1742+
this.id = id;
1743+
this.due = due;
1744+
this.desc = desc;
1745+
}
1746+
1747+
public Item() {
1748+
}
1749+
}
1750+
1751+
@AerospikeRecord(namespace = "test", set = "container")
1752+
public class Container {
1753+
@AerospikeKey
1754+
private int id;
1755+
private String name;
1756+
@AerospikeEmbed(type = EmbedType.MAP, elementType = EmbedType.LIST)
1757+
private List<Item> items;
1758+
1759+
public Container() {
1760+
this.items = new ArrayList<>();
1761+
}
1762+
}
1763+
````
1764+
1765+
Note that in this case the items are embedded into the container and not refrenced. This is what is needed for virtual lists, they must have a list of items in the database associated with a single record.
1766+
1767+
These items can be populated using the functionally presented above. For example:
1768+
1769+
```javs
1770+
Container container = new Container();
1771+
container.id = 1;
1772+
container.name = "container";
1773+
1774+
container.items.add(new Item(100, new Date(), "Item 1"));
1775+
container.items.add(new Item(200, new Date(), "Item 2"));
1776+
container.items.add(new Item(300, new Date(), "Item 3"));
1777+
container.items.add(new Item(400, new Date(), "Item 4"));
1778+
1779+
AeroMapper mapper = new AeroMapper.Builder(client).build();
1780+
mapper.save(container);
1781+
````
1782+
1783+
This yields a container with 4 items as expected:
1784+
1785+
```
1786+
id: 1
1787+
items: KEY_ORDERED_MAP('{
1788+
100:["Item 1", 1618442036607],
1789+
200:["Item 2", 1618442036607],
1790+
300:["Item 3", 1618442036607],
1791+
400:["Item 4", 1618442036607]}')
1792+
name: "container"
1793+
```
1794+
1795+
Note that whilst in this case the list is pre-populated with information, this is not a requirement for using virtual list.
1796+
1797+
A virtual list is created through the mapper:
1798+
1799+
```java
1800+
VirtualList<Item> list = mapper.asBackedList(container, "items", Item.class);
1801+
```
1802+
1803+
The container is passed as the first parameter, and is used for 2 things: The class type (so the annotations and field definitions can be discovered) and the primary key. It is possible to pass these 2 parameters instead of explicitly passing an object.
1804+
1805+
Once a virtual list has been created, methods to manipulate the list can be executed. For example:
1806+
1807+
```java
1808+
list.append(new Item(500, new Date(), "Item5"));
1809+
```
1810+
1811+
After this, the list in the database looks like:
1812+
1813+
```
1814+
id: 1
1815+
items: KEY_ORDERED_MAP('{
1816+
100:["Item 1", 1618442036607],
1817+
200:["Item 2", 1618442036607],
1818+
300:["Item 3", 1618442036607],
1819+
400:["Item 4", 1618442036607],
1820+
500:["Item5", 1618442991205]}')
1821+
name: "container"
1822+
```
1823+
1824+
Note however that the list in the object in memory still contains only 4 items. *Virtual lists affect only the database representation of the data and not the Java POJO.*
1825+
eVirutal Lists tend to use the (Operate)[https://www.aerospike.com/docs/client/java/usage/kvs/multiops.html] command which allows multiple operations to be performed on the same key at the same time. As a consequence, multiple commands can be done on a list with a single Aerospike operation. For example:
1826+
1827+
```java
1828+
List<Item> results = (List<Item>) list.beginMultiOperation()
1829+
.append(new Item(600, new Date(), "Item6"))
1830+
.removeByKey(200)
1831+
.getByKeyRange(100, 450)
1832+
.end();
1833+
```
1834+
1835+
This operation will add a new item (600) into the list, remove key 200 and get any keys between 100 (inclusive) and 450 (exclusive). As a result, the data in the database is:
1836+
1837+
```
1838+
id: 1
1839+
items: KEY_ORDERED_MAP('{
1840+
100:["Item 1", 1618442036607],
1841+
300:["Item 3", 1618442036607],
1842+
400:["Item 4", 1618442036607],
1843+
500:["Item5", 1618442991205],
1844+
600:["Item6", 1618445996551]}')
1845+
name: "container"
1846+
```
1847+
1848+
The result of the call is the result of the last read operation in the list of calls if one exists, otherwise it is the last write operation. So in this case, the result will be the result of the `getByKeyRange` call, which is 3 items: 100, 300, 400.
1849+
1850+
However, if we changed the call to be:
1851+
1852+
```java
1853+
List<Item> results = (List<Item>) list.beginMultiOperation()
1854+
.append(new Item(600, new Date(), "Item6"))
1855+
.removeByKey(200)
1856+
.end();
1857+
```
1858+
1859+
Then the result would be the result of the `removeByKey`, which by default is null. (Write operations pass a ReturnType of NONE to CDT operations by default)
1860+
1861+
However, if we wanted a particular operation in the list to return it's result, we can flag it with `asResult()`. For example:
1862+
1863+
```java
1864+
List<Item> results = (List<Item>) list.beginMultiOperation()
1865+
.append(new Item(600, new Date(), "Item6"))
1866+
.removeByKey(200).asResult()
1867+
.removeByKey(500)
1868+
.end();
1869+
```
1870+
1871+
In this case, the element removed with with the `removeByKey(200)` will be returned, giving the data associated with item 200..
1872+
1873+
The type of the result (where supported) can also be changed with a call to `asResultOfType()`. For example:
1874+
1875+
```java
1876+
long count = (long)list.beginMultiOperation()
1877+
.append(new Item(600, new Date(), "Item6"))
1878+
.removeByKey(200)
1879+
.removeByKeyRange(20, 350).asResultOfType(ReturnType.COUNT)
1880+
.getByKeyRange(100, 450)
1881+
.end();
1882+
```
1883+
1884+
The return type of the method is now going to be a long as it represents the count of elements removed (2 in this case). Note that this example is not very practical -- there is no point in calling `getByKeyRange(...)` in this call as the result is ignored.
1885+
1886+
Also note that virtual lists allow operations only on the list, not on other bins on the same record. To do this, you would have to use the underlying native Aerospike API. There are however convenience methods on the AeroMapper which can help map between Aerospike and Java formats.. For example:
1887+
1888+
```java
1889+
public <T> List<Object> convertToList(@NotNull T instance);
1890+
public <T> Map<String, Object> convertToMap(@NotNull T instance);
1891+
public <T> T convertToObject(Class<T> clazz, List<Object> record);
1892+
public <T> T convertToObject(Class<T> clazz, Map<String,Object> record);
1893+
public <T> T convertToObject(Class<T> clazz, Record record);
1894+
```
1895+
1896+
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+
1898+
17311899
----
17321900

17331901
## To finish
1734-
- Document virtual lists
17351902
- Add interface to adaptiveMap, including changing EmbedType
17361903
- Document all parameters to annotations and examples of types
17371904
- Document enums, dates, instants.
@@ -1742,4 +1909,5 @@ When mapping a Java object to Aerospike the most common operations to do are to
17421909
- Ensure batch loading option exists in AerospikeReference Configuration
17431910
- 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
17441911
- Consider the items on virtual list which return a list to be able to return a map as well (ELEMENT_LIST, ELEMENT_MAP)
1745-
- 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
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
1913+
- Create a batch get method which loads sub-objects in parallel

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package com.aerospike.mapper;
22

3-
import static org.junit.Assert.assertEquals;
4-
53
import java.util.ArrayList;
64
import java.util.Date;
75
import java.util.List;
@@ -66,13 +64,23 @@ public void testListOfReferences() {
6664
// perform a single operation. NOTE: This does NOT change the backed item, just the database
6765
list.append(new Item(500, new Date(), "Item5"));
6866

67+
/*
6968
List<Item> results = (List<Item>) list.beginMultiOperation()
7069
.append(new Item(600, new Date(), "Item6"))
7170
.removeByKey(200)
7271
.getByKeyRange(100, 450)
7372
.end();
7473
7574
System.out.println(results.size());
75+
*/
76+
long count = (long)list.beginMultiOperation()
77+
.append(new Item(600, new Date(), "Item6"))
78+
.removeByKey(200)
79+
.removeByKeyRange(20, 350).asResultOfType(ReturnType.COUNT)
80+
.getByKeyRange(100, 450)
81+
.end();
82+
83+
System.out.println(count);
7684
}
7785

7886
}

0 commit comments

Comments
 (0)