{"id":20661545,"url":"https://github.com/aerospike/java-object-mapper","last_synced_at":"2025-04-19T15:25:22.900Z","repository":{"id":38360168,"uuid":"327095771","full_name":"aerospike/java-object-mapper","owner":"aerospike","description":"The Java Object Mapper is a simple, light-weight framework used to map POJOs to the Aerospike database. Using simple annotations or a configuration YAML file to describe how to map the data to Aerospike, the project takes the tedium out of mapping the data through the powerful, low level interface.","archived":false,"fork":false,"pushed_at":"2025-04-10T07:55:05.000Z","size":755,"stargazers_count":18,"open_issues_count":7,"forks_count":14,"subscribers_count":55,"default_branch":"main","last_synced_at":"2025-04-10T08:43:42.673Z","etag":null,"topics":["aerospike","object-mapper"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aerospike.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-01-05T19:16:01.000Z","updated_at":"2025-03-07T06:10:01.000Z","dependencies_parsed_at":"2025-03-22T10:23:34.197Z","dependency_job_id":"652775a4-7343-4eca-b78a-80091e74109c","html_url":"https://github.com/aerospike/java-object-mapper","commit_stats":null,"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aerospike%2Fjava-object-mapper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aerospike%2Fjava-object-mapper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aerospike%2Fjava-object-mapper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aerospike%2Fjava-object-mapper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aerospike","download_url":"https://codeload.github.com/aerospike/java-object-mapper/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249725734,"owners_count":21316251,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aerospike","object-mapper"],"created_at":"2024-11-16T19:10:08.227Z","updated_at":"2025-04-19T15:25:22.890Z","avatar_url":"https://github.com/aerospike.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Aerospike Java Object Mapper\n[![Build project](https://github.com/aerospike/java-object-mapper/actions/workflows/build.yml/badge.svg)](https://github.com/aerospike/java-object-mapper/actions/workflows/build.yml)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.aerospike/java-object-mapper/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.aerospike/java-object-mapper/)\n[![javadoc](https://javadoc.io/badge2/com.aerospike/java-object-mapper/javadoc.svg)](https://javadoc.io/doc/com.aerospike/java-object-mapper)\n\n[Aerospike](https://www.aerospike.com) is one of, if not the fastest, NoSQL database in the world. It presents a Java API which is comprehensive and powerful, but requires a measure of boilerplate code to map the data from Java POJOs to the database. The aim of this repository is to lower the amount of code required when mapping POJOs to Aerospike and back as well as reducing some of the brittleness of the code.\n\n## Documentation\n\nThe documentation for this project can be found on [javadoc.io](https://www.javadoc.io/doc/com.aerospike/java-object-mapper).\n\n# Table of contents\n1. [Compatibility with Aerospike Clients](#Compatibility-with-Aerospike-Clients)\n2. [Motivation and a simple example](#Motivation-and-a-simple-example)\n3. [Getting Started](#Getting-Started)\n4. [Constructors](#Constructors)\n   + 4.1. [Constructor Factories](#Constructor-Factories)\n5. [Keys](#Keys)\n6. [Fields](#Fields)\n7. [Properties](#Properties)\n8. [References to other objects](#References-to-other-objects)\n    + 8.1. [Associating by Reference](#Associating-by-Reference)\n        + 8.1.1. [Batch Loading](#Batch-Loading)\n    + 8.2. [Aggregating by Embedding](#Aggregating-by-Embedding)\n        + 8.2.1. [Versioning Lists](#Versioning-Lists)\n        + 8.2.2. [List Ordinals](#List-Ordinals)\n        + 8.2.3. [The importance of Generic Types](#The-importance-of-Generic-Types)\n9. [Advanced Features](#Advanced-Features)\n    + 9.1. [Placeholder replacement](#Placeholder-replacement)\n    + 9.2. [Subclasses](#Subclasses)\n        + 9.2.1. [Data Inheritance](#Data-Inheritance)\n        + 9.2.2. [Subclass Inheritance](#Subclass-Inheritance)\n        + 9.2.3 [Using Interfaces](#Using-Interfaces)        \n    + 9.3. [Custom Object Converters](#Custom-Object-Converters)\n10. [External Configuration File](#External-Configuration-File)\n    + 10.1. [File Structure](#File-Structure)\n        + 10.1.1. [Key Structure](#Key-Structure)\n        + 10.1.2. [Bin Structure](#Bin-Structure)\n        + 10.1.3. [Embed Structure](#Embed-Structure)\n        + 10.1.4. [Reference Structure](#Reference-Structure)\n11. [Virtual Lists](#Virtual-Lists)\n12. [Scans](#Scans)\n13. [Queries](#Queries)\n\n\u003cdetails\u003e\n  \u003csummary\u003eCompatibility with Aerospike Clients\u003c/summary\u003e\n\n| Java Object Mapper Version | Aerospike Client | Aerospike Reactor Client |\n|----------------------------|------------------|--------------------------|\n| 2.5.2                      | 9.0.x (jdk8)     | 9.0.x                    |\n| 2.5.0, 2.5.1               | 8.1.x (jdk8)     | 8.1.x                    |\n| 2.4.x                      | 8.1.x (jdk8)     | 7.1.x                    |\n| 2.1.x, 2.2.x, 2.3.x        | 6.1.x            | 6.1.x                    |\n| 2.0.x                      | 5.1.x            | 5.1.x                    |\n| 1.2.x, 1.3.x, 1.4.x        | 5.1.x            | 5.0.x                    |\n| 1.1.x                      | 5.0.x            |                          |\n\n\u003c/details\u003e\n\n# Installing the Mapper\nThe easiest way to use the mapper is through Maven or Gradle. For Maven, pull it in from Maven Central:\n```\n\u003c!-- https://mvnrepository.com/artifact/com.aerospike/java-object-mapper --\u003e\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.aerospike\u003c/groupId\u003e\n    \u003cartifactId\u003ejava-object-mapper\u003c/artifactId\u003e\n    \u003cversion\u003e2.5.2\u003c/version\u003e\n\u003c/dependency\u003e\n```\nFor Gradle, you can use\n```\n// https://mvnrepository.com/artifact/com.aerospike/java-object-mapper\nimplementation group: 'com.aerospike', name: 'java-object-mapper', version: '2.5.2'\n```\n\n# Motivation and a simple example\nConsider a simple class:\n\n```java\npublic class Person { \n    private String ssn;\n    private String firstName;\n    private String lastName;\n    private int age;\n    private Date dob;\n    \n    public String getSsn() { \n        return ssn;\n    }\n    public void setSsn(String ssn) { \n        this.ssn = ssn;\n    }\n\n    public String getFirstName() { \n        return firstName;\n    }\n    public void setFirstName(String firstName) { \n        this.firstName = firstName;\n    }\n\n    public String getLastName() { \n        return lastName;\n    }\n    public void setLastName(String lastName) { \n        this.lastName = lastName;\n    }\n\n    public int getAge() { \n        return age;\n    }\n    public void setAge(int age) { \n        this.age = age;\n    }\n    \n    public Date getDob() { \n        return dob;\n    }\n    public void setDob(Date dob) { \n        this.dob = dob;\n    }\n}\t\n```\n \nTo store an instance of this class into Aerospike requires code similar to:\n\n```java\npublic void save(Person person, IAerospikeClient client) {\n\tlong dobAsLong = (person.dob == null) ? 0 : person.dob.getTime();\n\tclient.put( null, new Key(\"test\", \"people\", person.ssn,\n\t\tnew Bin(\"ssn\", Value.get(person.getSsn())),\n\t\tnew Bin(\"lstNme\", Value.get(person.getLastName())),\n\t\tnew Bin(\"frstNme\", Value.get(person.getFirstName())),\n\t\tnew Bin(\"age\", Value.get(person.getAge())),\n\t\tnew Bin(\"dob\", Value.get(dobAsLong)));\n}\t\t\n```\n\nSimilarly, reading an object requires significant code:\n\n```java\npublic Person get(String ssn, IAerospikeClient client) {\n\tRecord record = client.get(null, new Key(\"test\", \"people\", ssn);\n\tPerson person = new Person();\n\tperson.setSsn(ssn);\n\tperson.setFirstName(record.getString(\"frstNme\"));\n\tperson.setLastName(record.getString(\"lstNme\"));\n\tperson.setAge(record.getInt(\"age\");\n\tlong dobAsLong = record.getLong(\"dob\");\n\tperson.setDoB(dobAsLong == 0 ? null : new Date(dobAsLong));\n\treturn person;\n}\n```\n\nThis code is brittle. It has information such as the namespace name, the set name, and the names of the bins in multiple places. These should all be extracted as constants so they're only referenced once, but this adds more boilerplate code. \n\nAdditionally, there is complexity not shown in this simple example. Aerospike does not natively support all of Java types. Mapping a ``java.util.Date`` to the database requires additional code to convert to an Aerospike representation and back for example. Sub-objects which also need to be stored in the database must be handled separately. Changing the representation of the information between the database and the POJO requires additional work, such as storing a String representation of a date in Aerospike instead of a numeric representation.  \n\nThis repository aims to overcome these issues and more by relying on annotations on the POJOs to describe how to map the data to Aerospike and back. For example, the same functionality is provided by this code:\n\n```java\n@AerospikeRecord(namespace=\"test\", set=\"people\")\npublic class Person {\n\t\n    @AerospikeKey\n    private String ssn; \n    @AerospikeBin(name=\"frstNme\")\n    private String firstName;\n    \n    @AerospikeBin(name=\"lstNme\")\n    private String lastName;\n    private int age;\n    private Date dob;\n    \n    public String getSsn() { \n        return ssn;\n    }\n    public void setSsn(String ssn) { \n        this.ssn = ssn;\n    }\n\n    public String getFirstName() { \n        return firstName;\n    }\n    public void setFirstName(String firstName) { \n        this.firstName = firstName;\n    }\n\n    public String getLastName() { \n        return lastName;\n    }\n    public void setLastName(String lastName) { \n        this.lastName = lastName;\n    }\n\n    public int getAge() { \n        return age;\n    }\n    public void setAge(int age) { \n        this.age = age;\n    }\t\n    \n    public Date getDob() { \n        return dob;\n    }\n    public void setDob(Date dob) { \n        this.dob = dob;\n    }\n}\n```\n\nTo write person to Aerospike, simple use:\n\n```java\nPerson p = new Person();\np.setFirstName(\"John\");\np.setLastName(\"Doe\");\np.setSsn(\"123456789\");\np.setAge(17);\n\nAerospikeClient client = new AerospikeClient(\"aerospike hostname\",3000);\nAeroMapper mapper = new AeroMapper.Builder(client).build();\nmapper.save(p);\n```\n \nTo read:\n \n```java\nPerson person = mapper.read(Person.class, \"123456789\");\n```\n \nTo delete:\n \n```java\nmapper.delete(person);\n```\n\n----\n\n## Getting Started\nThe first thing that needs to be done is to create an instance of the AeroMapper class. This is achieved through the Builder class which allows you to specify\nvarious options. Once the options have been specified, `build()` is called to get an instance of the AeroMapper. Thus, the simplest usage is:\n\n``` java\nAeroMapper mapper = new AeroMapper.Builder(client).build();\n```\n\nThe Builder constructor simply takes an IAerospikeClient which it uses for access to the database. Other options can be added to the mapper between the constructor for the Builder and the invocation of the build() method. These options include:\n\n`.addConverter(Object converter)`: Registers a class as a custom converter, which allows programmatic control over how data types are mapped to and from Aerospike. This custom converter must have @ToAerospike and @FromAerospike annotated methods. For more information, see [Custom Object Converters](#custom-object-converters) below.\n\n`.preLoadClass(Class\u003c?\u003e)`: Used to load a class before it is needed. The process of loading a class for the first time can be moderately expensive -- there is lots of introspection which goes on to determine how to map the classes to and from the database with the help of the annotations or configuration file. The results of this process are cached so it only has to happen once, and as few introspection calls as possible are called during the actual transformation. If a class is not preloaded, this computation will happen the first time an instance of that class is encountered, resulting in slowdown on the first call.\n\nAnother reason to preload a class is situations where an abstract superclass might be read without the subclasses being seen by the AeroMapper first. For example, a list of `Animal` might be stored in the database, but `Animal` is an abstract class with concrete subclasses like `Dog`, `Cat`, etc. If the first call of the AeroMapper is to read a list of `Animal` from the database, there is not enough information to resolve the concrete sub-classes without preloading them.\n\n`.preLoadClasses(Class\u003c?\u003e ...)`: Use to preload several classes before they are called. This is a convenience mechanism which calls `.preLoadClass` for each of the classes in the list.\n\n`.preLoadClassesFromPackage(String | Class\u003c?\u003e)`: Preload all the classes in the specified package which are annotated with `@AerospikeRecord`. The package can be specified by passing a string of the package name or by passing a class in that package. The latter method is preferred as this is less brittle as code is refactored. Note that if a class is passed this class is used only for the package name and does not necessarily need to be a class annotated with `@AerospikeRecord`. Creating a 'marker' class in the package with no functionality and passing to this method is a good way of preventing breaking the preloading as classes are moved around.\n\n`withConfigurationFile`: Whilst mapping information from POJOs via annotations is efficient and has the mapping code inline with the POJO code, there are times when this is not available. For example, if an external library with POJOs is being used and it is desired to map those POJOs to the database, there is no easy way of annotating the source code. Another case this applies is if different mapping parameters are needed between different environments. For example, embedded objects might be stored in a map in development for ease of debugging, but stored in a list in production for compaction of stored data. In these cases an external configuration YAML file can be used to specify how to map the data to the database. See [External Configuration File](#external-configuration-file) for more details. There is an overload of this method which takes an additional boolean parameter -- if this is `true` and the configuration file is not valid, errors will be logged to `stderr` and the process continue. It is normally not recommended to set this parameter to true.\n\nIf multiple configuration files are used and the same class is defined in multiple configuration files, the definitions in the first configuration file for a class will be used. \n\n`withConfiguration`: Similar to the `withConfigurationFile` above, this allows configuration to be externally specified. In this case, the configuration is passed as a YAML string.\n\n`withReadPolicy`, `withWritePolicy`, `withBatchPolicy`, `withScanPolicy`, `withQueryPolicy`: This allows setting of the appropriate policy type. The following discussion uses read policies, but applies equally to all the other policies.\n\nAfter the specified policy, there are 3 possible options: \n\n- `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. \n- `forThisOrChildrenOf(Class\u003c?\u003e class)`: The passed policy is used for the passed class and all subclasses of the passed class.\n- `forClasses(Class\u003c?\u003e... classes)`: The passed policy is used for the passed class(es), but no subclasses.\n\nIt 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:\n\n```java\nPolicy readPolicy1, readPolicy2, readPolicy3;\n// ... code to set up the policies goes here...\nAeroMapper.Builder(client)\n          .withReadPolicy(readPolicy1).forAll()\n          .withReadPolicy(readPolicy2).forThisOrChildrenOf(B.class)\n          .withReadPolicy(readPolicy3).forClasses(C.class)\n          .build();\n```\n\nIn 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:\n\n- A: `readPolicy1`\n- B: `readPolicy2`\n- C: `readPolicy3`\n           \nNote 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`\n\nIf 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 need a policy which was previously set up on a Customer class but need to change the `durableDelete` property, we could do\n\n```java\nWritePolicy writePolicy = new WritePolicy(mapper.getWritePolicy(Customer.class));\nwritePolicy.durableDelete = true;\nmapper.delete(writePolicy, myCustomer);\n```\nNote that the `getXxxPolicy` methods return the actual underlying policy rather than a copy of it, so it is best to instantiate a new instance of this object before changing it.\n\nIn summary, the policy which will be used for a call are: (lower number is a higher priority)\n\n1. Policy passed as a parameter\n2. Policy passed to  `forClasses` method\n3. Policy passed to `forThisOrChildrenOf` method\n4. Policy passed to `forAll` method\n5. AerospikeClient.getXxxxPolicyDefault\n\n---\n\n## Constructors\nGiven that the AeroMapper is designed to read and write information to an Aerospike database, it must be able to create objects when the data has been read from the database. To construct an object, it will typically use the default (no argument) constructor. \n\nHowever, there are times when this is not desirable, for example when the class declares final fields which must be mapped to the constructor. For example, consider the following class:\n\n```java\n@AerospikeRecord(namespace = \"test\", set = \"testSet\")\npublic class ConstructedClass {\n\t@AerospikeKey\n\tpublic final int id;\n\tpublic final int age;\n\tpublic final String name;\n\tpublic final Date date;\n\t\n\tpublic ConstructedClass(int id, int age, String name, Date date) {\n\t\tsuper();\n\t\tthis.id = id;\n\t\tthis.age = age;\n\t\tthis.name = name;\n\t\tthis.date = date;\n\t}\n}\n```\n\nAs it stands, this class cannot be used with the AeroMapper because there is no valid constructor to invoke when an object needs to be created. There is a constructor but it does not contain enough information to map the record on the database to the parameters of the constructor. (Remember that at runtime method and argument names are typically lost and become \"arg1\", \"arg2\" and so on). We can use this constructor in one of two ways:\n\n1. We specify '-parameters' to javac, which will prevent it stripping out the names to the constructor\n2. We can to provide this missing information with annotations:\n\n```java\n@AerospikeRecord(namespace = \"test\", set = \"testSet\")\npublic class ConstructedClass {\n\t@AerospikeKey\n\tpublic final int id;\n\tpublic final int age;\n\tpublic final String name;\n\tpublic final Date date;\n\t\n\tpublic ConstructedClass(@ParamFrom(\"id\") int id,\n                            @ParamFrom(\"age\") int age,\n                            @ParamFrom(\"name\") String name,\n                            @ParamFrom(\"date\") Date date) {\n\t\tsuper();\n\t\tthis.id = id;\n\t\tthis.age = age;\n\t\tthis.name = name;\n\t\tthis.date = date;\n\t}\n}\n```\n\nNow there is enough information to be able to construct an instance of this class from a database record. Note that the names of the @ParamFrom annotation (or the argument names if using -parameters) are the bin names, not the underlying field names. So if you have a field declared as\n\n```java\n@AerospikeBin(name = \"shrtNm\")\nprivate String fieldWithAVeryLongName;\n```\n\nthen the constructor might look line:\n\n```java\npublic FieldNameTest(@ParamFrom(\"shrtNm\") String fieldWithAVeryLongName) {\n\tthis.fieldWithAVeryLongName = fieldWithAVeryLongName;\n}\n```\n\nNote that not all the fields in the class need to be specified in the constructor (unless needed to satisfy the Java compiler, eg setting any final fields). Any values not passed in the constructor will be explicitly set. For example:\n\n```java\n@AerospikeRecord(namespace = \"test\", set = \"testSet\") \npublic class ConstructedClass2 {\n\t@AerospikeKey\n\tpublic final int id;\n\tpublic final int a;\n\tpublic int b;\n\tpublic int c;\n\t\n\tpublic ConstructedClass2(@ParamFrom(\"id\") int id, @ParamFrom(\"a\") int a) {\n\t\tthis.id = id;\n\t\tthis.a = a;\n\t}\n}\n```\n\nWhen an instance of the ConstructedClass2 is read from the database, the constructor will be invoked and `a` and `id` set via the constructor, then `b` and `c` will be set by direct field access.\n\nNote that whilst these examples show only final fields being set, this is not a requirement. The constructor can set any or all fields.\n\nIf there are multiple constructors on the class, the one to be used by the AeroMapper should be annotated with @AerospikeConstructor:\n\n```java\n@AerospikeRecord(namespace = \"test\", set = \"testSet\") \npublic class ConstructedClass2 {\n\t@AerospikeKey\n\tpublic final int id;\n\tpublic final int a;\n\tpublic int b;\n\tpublic int c;\n\t\n\tpublic ConstructedClass2(@ParamFrom(\"id\") int id, @ParamFrom(\"a\") int a) {\n\t\tthis.id = id;\n\t\tthis.a = a;\n\t}\n\t@AerospikeConstructor\n\tpublic ConstructedClass2(@ParamFrom(\"id\") int id, @ParamFrom(\"a\") int a, @ParamFrom(\"b\") int b) {\n\t\tthis.id = id;\n\t\tthis.a = a;\n\t\tthis.b = b;\n\t}\n}\n```\n\nIn this case, the 3 argument constructor will be used. Note that you must annotate the desired constructor with @AerospikeConstructor on any class with multiple constructors, irrespective of how many of those constructors have the @ParamFrom annotations on their arguments. If more than 1 constructor is annotated with @AerospikeConstructor on a class an exception will be thrown the first time the mapper sees that class.\n\nIf no constructor is annotated with @AerospikeConstructor, the default no-argument constructor will be used. If there is no no-argument constructor but only one constructor on the class, that constructor will be used. If there is no default constructor and multiple other constructors but no @AerospikeConstructor annotated constructor has been declared, an exception will be thrown when the class is first used.\n\n### Constructor Factories\n\nSometimes it is required to use a method to create an object instead of a constructor. For example, if an object is generated by a protobuf compiler it is created by calling `myClass.Builder.newBuilder()`. In these cases a factory class and a factory method can be used. \n\nAs an example:\n\n```java\npublic class Factory {\n\tpublic static A createA() {\n\t\tA newA = new A();\n\t\tnewA.factory = \"factory created\";\n\t\treturn newA;\n\t}\n}\n\n@AerospikeRecord(namespace = \"test\", set = \"A\", factoryMethod = \"createA\", factoryClass = \"com.aerospike.mapper.Factory\")\npublic class A {\n\tpublic String name;\n\tpublic int age;\n\t@AerospikeKey\n\tpublic int id;\n\t@AerospikeExclude\n\tpublic String factory;\n\t\n\tA() {}\n\n\tpublic A(String name, int age, int id) {\n\t\tsuper();\n\t\tthis.name = name;\n\t\tthis.id = id;\n\t}\n}\n```\n\nWhen the Object Mapper needs to create a new instance of `A`, it will call the `createA` method on `com.aerospike.mapper.Factory` class. This method has a few requirements:\n\n1. The method on the class must be static\n2. The method can take zero parameters, one parameter or two parameters. If it takes one parameter, this can be either a `java.lang.Class` or `java.util.Map` and if it takes 2 parameters these must be a `java.lang.Class` followed by a `java.util.Map`. The `Class` parameter represents the type being instantiated, and the `Map` is a map of the attributes the Object Mapper knows will require instantiating. Hence the map is effectively a `Map\u003cString, Object\u003e`.\n\nNote that you cannot specify a `factoryMethod` without a `factoryClass` or vice versa; either both must be specified or neither.\n\n---\n \n## Keys\nThe key to an Aerospike record can be specified either as a field or a property. Remember that Aerospike keys can be Strings, integer types and binary types only.\n\nTo use a field as the key, simply mark the field with the AerospikeKey annotation:\n\n```java\n@AerospikeKey\nprivate int personId;\n```\n\nIf a function is to be used as a key, the function must be declared as to have no parameters and return non-void. The visibility of the method does not matter.\n\n```java\n@AerospikeKey\npublic String getKey() {\n\treturn this.keyPart1 + \":\" + this.keyPart2;\n}\n```\n\nNote that it is not required to have a key on an object annotated with @AerospikeRecord. This is because an object can be embedded in another object (as a map or list) and hence not require a key to identify it to the database.\n\nAlso, the existence of `@AerospikeKey` on a field does not imply that the field will get stored in the database explicitly. Use `@AerospikeBin` or `mapAll` attribute to ensure that the key gets mapped to the database too.\n\nBy default, the key will always be stored in a separate column in the database. So for a class defined as\n\n```java\n@AerospikeRecord(namespace = \"test\", set = \"testSet\")\npublic static class A {\n    @AerospikeKey\n    private long key;\n    private String value;\n}\n```\n\nthere will be a bin in the database called `key`, whose value will be the same as the value used in the primary key. This is because Aerospike does not implicitly store the value of the key in the database, but rather uses a hash of the primary key as a unique representation. So the value in the database might look like:\n\n```\naql\u003e select * from test.testSet\n+-----+--------+\n| key | value  |\n+-----+--------+\n| 1   | \"test\" |\n+-----+--------+\n```\n\nIf it is desired to force the primary key to be stored in the database and NOT have key added explicitly as a column then two things must be set:\n\n1. The `@AerospikeRecord` annotation must have `sendKey = true`\n2. The `@AerospikeKey` annotation must have `storeAsBin = false`\n\nSo the object would look like:\n\n```java\n@AerospikeRecord(namespace = \"test\", set = \"testSet\", sendKey = true)\npublic static class A {\n    @AerospikeKey(storeAsBin = false)\n    private long key;\n    private String value;\n}\n```\n\nWhen data is inserted, the field `key` is not saved, but rather the key is saved as the primary key. When the value is read from the database, the stored primary key is put back into the `key` field. So the data in the database might be:\n\n```\naql\u003e select * from test.testSet\n+----+--------+\n| PK | value  |\n+----+--------+\n| 1  | \"test\" |\n+----+--------+\n```\n\n\n----\n\n## Fields\nFields in Java can be mapped to the database irrespective of the visibility of the field. To do so, simply specify the bin to map to with the @AerospikeBin annotation:\n\n```java\n@AerospikeBin(name = \"vrsn\")\nprivate int version;\n```\n\nThis will map the contents of the version field to a `vrsn` bin in Aerospike. \n\nIf the name of the bin matches the name of the field in Java, the name can be omitted:\n\n```java\n@AerospikeBin\nprivate int age;\n```\n\nThis will appear in Aerospike as the `age` bin.\n\nBy default, all fields will be mapped to the database. Fields can be excluded with the @AerospikeExclude annotation, and renamed with the @AerospikeBin annotation. If it is desired to save only bins annotated with @AerospikeBin, use `mapAll = false` on the @AerospikeRecord. For example:\n\n```java\n@AerospikeRecord(namespace = NAMESPACE, set = \"testSet\")\npublic static class Test {\n\tpublic int a;\n\tpublic int b;\n\tpublic int c;\n\tpublic int d;\n}\n```\n\nThis saves the record with 4 bins, a,b,c,d. To save only fields a,b,c you can do either:\n\n```java\n@AerospikeRecord(namespace = NAMESPACE, set = \"testSet\")\npublic static class Test {\n\tpublic int a;\n\tpublic int b;\n\tpublic int c;\n\t@AerospikeExclude\n\tpublic int d;\n}\n```\n\nor\n\n```java\n@AerospikeRecord(namespace = NAMESPACE, set = \"testSet\", mapAll = false)\npublic static class Test {\n\t@AerospikeBin\n\tpublic int a;\n\t@AerospikeBin\n\tpublic int b;\n\t@AerospikeBin\n\tpublic int c;\n\tpublic int d;\n}\n```\n\nIf a field is marked with both @AerospikeExclude and @AerospikeBin, the bin will _not_ be mapped to the database.\n\nYou can force the name of a particular bin or bins by specifying them in an AerospikeBin:\n\n```java\n@AerospikeRecord(namespace = NAMESPACE, set = \"testSet\")\npublic static class Test {\n\tpublic int a;\n\tpublic int b;\n\t@AerospikeBin(name = \"longCname\")\n\tpublic int c;\n\tpublic int d;\n}\n```\nThis will save 4 fields in the database, a, b, longCname, d.\n\n----\n\n## Properties\nA pair of methods comprising a getter and setter can also be mapped to a field in the database. These should be annotated with @AerospikeGetter and @AerospikeSetter respectively and the name attribute of these annotations must be provided. The getter must take no arguments and return something, and the setter must return void and take 1 parameter of the same type as the getter return value. Both a setter and a getter must be provided, an exception will be thrown otherwise.\n\nLet's look at an example:\n\n```java\n@AerospikeSetter(name=\"bob\")\npublic void setCraziness(int value) {\n\tunmapped = value/3;\n}\n@AerospikeGetter(name=\"bob\")\npublic int getCraziness() {\n\treturn unmapped*3;\n}\n```\n\nThis will create a bin in the database with the name \"bob\".\n\nIt 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. \n\nSo, for example, if we have an A object which embeds a B, when the setter for B is called the second parameter will represent A's key:\n\n```java\n@AerospikeRecord(namespace = \"test\", set = \"A\", mapAll = false)\npublic class A {\n\t@AerospikeBin\n\tprivate String key;\n\tprivate String value1;\n\tprivate long value2;\n\t\n\t@AerospikeGetter(name = \"v1\")\n\tpublic String getValue1() {\n\t\treturn value1;\n\t}\n\t@AerospikeSetter(name = \"v1\")\n\tpublic void setValue1(String value1, Value owningKey) {\n\t\t// owningKey.getObject() will be a String of \"B-1\"\n\t\tthis.value1 = value1;\n\t}\n\t\n\t@AerospikeGetter(name = \"v2\")\n\tpublic long getValue2() {\n\t\treturn value2;\n\t}\n\t\n\t@AerospikeSetter(name = \"v2\")\n\tpublic void setValue2(long value2, Key key) {\n\t\t// Key will have namespace=\"test\", setName = \"B\", key.userKey.getObject() = \"B-1\"\n\t\tthis.value2 = value2;\n\t}\n}\n\n@AerospikeRecord(namespace = \"test\", set = \"B\")\npublic class B {\n\t@AerospikeKey \n\tprivate String key;\n\t@AerospikeEmbed\n\tprivate A a;\n}\n\n@Test\npublic void test() {\n\tA a = new A();\n\ta.key = \"A-1\";\n\ta.value1 = \"value1\";\n\ta.value2 = 1000;\n\t\n\tB b = new B();\n\tb.key = \"B-1\";\n\tb.a = a;\n\t\n\tAeroMapper mapper = new AeroMapper.Builder(client).build();\n\tmapper.save(b);\n\tB b2 = mapper.read(B.class, b.key);\n\t\n}\n```\n\nThis 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.\n\nSince 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.\n\n----\n\n## Default Mappings of Java Data type\nHere are how standard Java types are mapped to Aerospike types:\n| Java Type | Aerospike Type |\n| --- | --- |\n| byte | integral numeric |\n| char | integral numeric |\n| short | integral numeric |\n| int | integral numeric |\n| long | integral numeric |\n| boolean | integral numeric |\n| Byte | integral numeric |\n| Character | integral numeric |\n| Short | integral numeric |\n| Integer | integral numeric |\n| Long | integral numeric |\n| Boolean | integral numeric |\n| float | double numeric |\n| double | double numeric |\n| Float | double numeric |\n| Double | double numeric |\n| java.util.Date | integral numeric |\n| java.time.Instant | integral numeric |\n| String | String |\n| byte[] | BLOB |\n| enums | String |\n| Arrays (int[], String[], Customer[], etc) | List |\n| List\u003c?\u003e | List or Map |\n| Map\u003c?,?\u003e | Map |\n| Object Reference (@AerospikeRecord) | List or Map |\n\nThese types are built into the converter. However, if you wish to change them, you can use a [Custom Object Converter](#custom-object-converters). For example, if you want Dates stored in the database as a string, you could do:\n\n```java\npublic static class DateConverter {\n    \tprivate static final ThreadLocal\u003cSimpleDateFormat\u003e dateFormatter = ThreadLocal.withInitial(() -\u003e\n                new SimpleDateFormat(\"dd-MM-yyyy HH:mm:ss.SSS zzzZ\"));\n    @ToAerospike\n    public String toAerospike(Date date) {\n    \tif (date == null) {\n    \t\treturn null;\n    \t}\n\t\treturn dateFormatter.get().format(date);\n    }\n\n    @FromAerospike\n    public Date fromAerospike(String dateStr) throws ParseException {\n    \tif (dateStr == null) {\n    \t\treturn null;\n    \t}\n    \treturn dateFormatter.get().parse(dateStr);\n    }\n}\n\nAeroMapper convertingMapper = new AeroMapper.Builder(client).addConverter(new DateConverter()).build();\n```\n\n(Note that SimpleDateFormat is not thread-safe, and hence the use of the ThreadLocal variable)\n\nThis 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.\n\n----\n\n## References to other objects\nThe mapper has 2 ways of mapping child objects associated with parent objects: by reference, or embedding them. Further, embedded objects can be stored either as lists or maps. All of this is controlled by annotations on the owning (parent) class.\n\nLet's see this with and example. Let's define 2 classes, `Parent` and `Child`:\n\n```java\n@AerospikeRecord(namespace = \"test\", set = \"parent\")\npublic static class Parent {\n\t@AerospikeKey\n\tint id;\n\tString name;\n\t\n\t@AerospikeEmbed(type = EmbedType.MAP)\n\tpublic Child mapEmbedChild;\n\t\n\t@AerospikeEmbed(type = EmbedType.LIST)\n\tpublic Child listEmbedChild;\n\n\t@AerospikeReference\n\tpublic Child refChild;\n\n\tpublic Parent(int id, String name, Child child) {\n\t\tsuper();\n\t\tthis.id = id;\n\t\tthis.name = name;\n\t\tthis.mapEmbedChild = child;\n\t\tthis.listEmbedChild = child;\n\t\tthis.refChild = child;\n\t}\n}\n\n@AerospikeRecord(namespace = \"test\", set = \"child\")\npublic static class Child {\n\t@AerospikeKey\n\tint id;\n\tString name;\n\tDate date;\n\n\tpublic Child(int id, String name, Date date) {\n\t\tsuper();\n\t\tthis.id = id;\n\t\tthis.name = name;\n\t\tthis.date = date;\n\t}\n}\n```\n\nThis is obviously a contrived example -- we're storing 3 copies of the same `Child` in 3 different ways. The only difference in the way the child is referenced is the annotation: `@AerospikeEmbed(type = EmbedType.MAP)` will store the child in a map as part of the parent, `@AerospikeEmbed(type = EmbedType.LIST)` will store this child as a list, and `@AerospikeReference` will not store the child at all, but rather the key of the child so it can be loaded when needed.\n\nTo make use of these definitions, we create a parent and a child:\n\n```java\nChild child = new Child(123, \"child\", new Date());\nParent parent = new Parent(10, \"parent\", child);\nmapper.save(parent);\n\n// Since the child is referenced, it needs to be saved explicitly in the database\n// If it were only embedded, it would not be necessary to save explicitly.\nmapper.save(child);\n```\n\nThe object is now saved in Aerospike. Looking at the objects in the database, we see:\n\n```\naql\u003e select * from test.child\n*************************** 1. row ***************************\ndate: 1610640142173\nid: 1\nname: \"child\"\n\n1 row in set (0.791 secs)\n\nOK\n\naql\u003e select * from test.parent\n*************************** 1. row ***************************\nid: 10\nlistEmbedChild: LIST('[1610640142173, 1, \"child\"]')\nmapEmbedChild: MAP('{\"name\":\"child\", \"date\":1610640142173, \"id\":1}')\nname: \"parent\"\nrefChild: 123\n\n1 row in set (0.785 secs)\n\nOK\n```\n\nLet's dig into these further.\n\n### Associating by Reference\nA reference is used when the referenced object needs to exist as a separate entity to the referencing entity. For example, a person might have accounts, and the accounts are to be stored in their own set. They are not to be encapsulated into the person (as business logic might dictate actions are to occur on accounts irrespective of their owners).\n\nTo indicate that the second object is to be referenced, use the @AerospikeReference annotation:\n\n```java\n@AerospikeRecord(namespace = \"test\", set = \"account\")\npublic class Account {\n\t@AerospikeKey\n\tpublic long id;\n\tpublic String title;\n\tpublic int balance;\n}\n\n@AerospikeRecord(namespace=\"test\", set=\"people\", mapAll = false)\npublic class Person {\n\t\n\t@AerospikeKey\n\t@AerospikeBin(name=\"ssn\")\n\tpublic String ssn; \n\t@AerospikeBin\n\tpublic String firstName;\n    \n\t@AerospikeBin(name=\"lastName\")\n\tpublic String lastName;\n    \n\t@AerospikeBin(name=\"age\")\n\tpublic int age;\n\n\t@AerospikeBin(name = \"primAcc\")\n\t@AerospikeReference\n\tpublic Account primaryAccount;\n}\n\nAccount account = new Account();\naccount.id = 103;\naccount.title = \"Primary Savings Account\";\naccount.balance = 137;\n\nPerson person = new Person();\nperson.ssn = \"123-456-7890\";\nperson.firstName = \"John\";\nperson.lastName = \"Doe\";\nperson.age = 43;\nperson.primaryAccount = account;\n\nmapper.save(account);\nmapper.save(person);\n```\n\nThis code results in the following data in Aerospike:\n\n```\naql\u003e select * from test.account\n*************************** 1. row ***************************\nbalance: 137\nid: 103\ntitle: \"Primary Savings Account\"\n\naql\u003e select * from test.people\n*************************** 1. row ***************************\nage: 43\nfirstName: \"John\"\nlastName: \"Doe\"\nprimAcc: 103\nssn: \"123-456-7890\"\n```\n\nNote: the fields in this example are public for the sake of brevity. In reality, the class would have the fields private and appropriate accessors and mutators defined. But the annotations could still stay on the fields.\n\nSince the account is being saved externally to the person, it must be saved as a separate call to mapper.save(...).\n\nHowever, to load the data, only one call is necessary:\n\n```java\nPerson loadedPerson = mapper.read(Person.class, \"123-456-7890\");\nSystem.out.printf(\"ssn = %s, name = %s %s, balance = %d\",\n\t\tloadedPerson.ssn, loadedPerson.firstName, loadedPerson.lastName,\n\t\tloadedPerson.primaryAccount.balance);\n```\n\nwhich results in:\n\n```\nssn = 123-456-7890, name = John Doe, balance = 137\n```\n\nand an object graph of\n\n```\nloadedPerson : Person\n-  firstName : \"John\"\n-  LastName : \"Doe\"\n-  PrimaryAccount : Account\n  - balance : 137\n  - id : 103\n  - title : \"Primary Savings Account\" \n```\n\nAll dependent objects which are @AerospikeRecord will be loaded, in an arbitrarily deep nested graph.\n\nIf it is desired for the objects NOT to load dependent data, the reference can be marked with ```lazy = true```\n\n```java\n@AerospikeBin(name = \"primAcc\")\n@AerospikeReference(lazy = true)\npublic Account primaryAccount;\n```\n\nin this case, when the person is loaded a the child data will NOT be loaded from the database. However, a child object (Account in this case) will be created with the id set to the value which would have been loaded. (ie ```loadedPerson.primaryAccount.id``` will be populate, but not other fields will be). So the Person and Account objects in Java would look like\n\n```\nloadedPerson : Person\n-  firstName : \"John\"\n-  LastName : \"Doe\"\n-  PrimaryAccount : Account\n  - balance : 0\n  - id : 103\n  - title : null \n```\n\nNote that if a reference to an AerospikeRecord annotated object exists, but the reference has neither @AerospikeReference nor @AerospikeEmbed (see below), then it is assumed it will be @AerospikeReference(lazy = false).\n\nThere are times when it makes sense to store the digest of the child record as the reference rather than it's primary key. For example, if the native primary key is of significant length then storing a fixed 20-byte digest makes sense. This can be accomplished by adding `type = ReferenceType.DIGEST` to the @AerospikeReference. For example:\n\n```java\n@AerospikeRecord(namespace = \"test\", set = \"people\")\npublic static class Person {\n    @AerospikeKey\n    public String ssn;\n    public String firstName;\n    public String lastName;\n    public int age;\n\n    @AerospikeBin(name = \"primAcc\")\n    @AerospikeReference(type = ReferenceType.DIGEST)\n    public Account primaryAccount;\n} \n```\n\nThis is will store the digest of the primary account in the database instead of the id: \n\n```\n*************************** 1. row ***************************\naccts: LIST('[101, 102]')\nage: 43\nfirstName: \"John\"\nlastName: \"Doe\"\nprimAcc: 03 A7 08 92 E3 77 BC 2A 12 68 0F A8 55 7D 41 BA 42 6C 04 69\nssn: \"123-456-7890\"\n```\n\nNote that storing the digest as the referencing key is not compatible with lazy loading of records as the object mapper has nowhere in the object model to store the referenced id in the lazy-loaded object. Hence\n\n```java\n@AerospikeReference(type = ReferenceType.DIGEST, lazy = true)\n```\n\nwill throw an exception at runtime. \n\n#### Batch Loading\n\nNote 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:\n\n![Object Diagram](/images/complexObjectGraph.png)\n\nNote that some of the objects are embedded and some are references.\n\nIf we then instantiate a complex object graph like:\n\n![Object Graph](/images/objectInstantiation.png)\n\nHere 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:\n\n```\nGet: [test:customer:cust1:818d8a436587c36aef4da99d28eaf17e3ce3a0e1] took 0.211ms, record found\nBatch: [4/4 keys] took 0.258ms\nBatch: [6/6 keys] took 0.262ms\nBatch: [2/2 keys] took 0.205ms\n```\n\nThe 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.\n\n\n### Aggregating by Embedding\nThe 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. The Products 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. In this case, it makes sense to encapsulate the product into the account object.\n\nSince Aerospike records can have bins (columns) which are lists and maps, we can choose to represent the underlying product in one of two ways, using a list or a map. There are pros and cons of each.\n\nConsider a simple account and product:\n\n```java \n@AerospikeRecord(namespace = \"test\", set = \"product\") \npublic static class Product {\n\tpublic String productId;\n\tpublic int version;\n\tpublic String name;\n\tpublic Date createdDate;\n}\n\n@AerospikeRecord(namespace = \"test\", set = \"account\")\npublic static class Account {\n\t@AerospikeKey\n\tpublic long id;\n\tpublic String title;\n\tpublic int balance;\n\t@AerospikeEmbed\n\tpublic Product product;\n}\n\nProduct product = new Product();\nproduct.createdDate = new Date();\nproduct.name = \"Sample Product\";\nproduct.productId = \"SP-1\";\nproduct.version = 1;\n\nAccount account = new Account();\naccount.id = 123;\naccount.title = \"Test Account\";\naccount.balance = 111;\naccount.product = product;\n\nmapper.save(account);\n```\n\nThis creates the following record in Aerospike:\n\n```\naql\u003e select * from test.account\n*************************** 1. row ***************************\nbalance: 111\nid: 123\nproduct: MAP('{\"productId\":\"SP-1\", \"name\":\"Sample Product\", \"createdDate\":1609894948221, \"version\":1}')\ntitle: \"Test Account\"\n```\n\nNote that the product definition is fully encapsulated inside the account with all the fields stored in a map. Since the product does not need to be selected in it's own right (it can only be accessed by reading it from the account) there is no need for the product to have an @AerospikeKey, nor was there any need to save the product in it's own right. Hence this product definition as it stands would _not_ be suitable to be a reference, it must be embedded. To increase flexibility, it is recommended that all objects are given an @AerospikeKey, even if they are to be embedded.\n\nSimilarly, the Product does not need to specify either a set or a namespace in the @AerospikeRecord annotation as they are not being stored in Aerospike in their own right.\n\nBy default, embedding the child information is placed into a map, with the product bin names as the keys to the map and the values as the data in the product. This results in a very readable sub-record, but it's wasteful on space. If we have 1,000,000 accounts in our database each of which has this product, the strings \"productId\", \"name\", \"createdDate\" and \"version\" will be repeated 1,000,000 times.\n\nSo the other way embedded data can be stored in Aerospike is using a list. Simply change the @AerospikeEmbed annotation to be:\n\n```java\npublic class Account {\n\t@AerospikeKey\n\tpublic long id;\n\tpublic String title;\n\tpublic int balance;\n\t@AerospikeEmbed(type = EmbedType.LIST)\n\tpublic Product product;\n}\n```\n\nIn this case, the embedded object will be stored as a list of values, sorted alphabetically:\n\n```\naql\u003e select * from test.account\n*************************** 1. row ***************************\nbalance: 111\nid: 123\nproduct: LIST('[1609895951160, \"Sample Product\", \"SP-1\", 1]')\ntitle: \"Test Account\"\n```\n\nThe elements in the list are (in order): createdDate, name, productId, version.\n\nThis is far more compact and wastes less space, but has an issue: How do you add new items to the product? The answer is to use versioning.\n\n#### Versioning Lists\n\nMaps and Aerospike records are self-describing -- each value has a name, so it is obvious how to map the data to the database and back. For example, if we have a class\n\n```java\n@AerospikeRecord(namespace = \"test\", set = \"testSet\")\npublic class IntContainer {\n\tpublic int a;\n\tpublic int b;\n\tpublic int c;\n}\n```\n\nthis will be stored in a map as:\n\n```\nMAP('{\"a\":1, \"b\":2, \"c\":3}')\n```\n\nIf we later change the `IntContainer` class to remove the field `a` and add in `d` we get the class:\n\n```java\n@AerospikeRecord(namespace = \"test\", set = \"testSet\")\npublic class IntContainer {\n\tpublic int b;\n\tpublic int c;\n\tpublic int d;\n}\n```\n\nwhich is stored as\n\n```\nMAP('{\"b\":2, \"c\":3, \"d\":4}')\n```\n\nIf we had records in the database which were written with the original version of the class and read with the new version of the class, the value of field `a` in the database will be ignored and the value `d` which is not present in the database will be set to 0. So we end up with:\n\n```\nb = 2\nc = 3\nd = 0\n```\n\nHowever, if we store the sub-object as a list, the record in the database will be stored as:\n\n```\nLIST('[1, 2, 3]')\n```\n\nThere is no information in the record to describe which field in the list maps to the values in the Aerospike POJO. So when we upgrade the object to the second version and try to read the record, we end up with\n\n```\nb = 1\nc = 2\nd = 3\n```\n \nThis is obviously sub-optimal. Changing the underlying object has effectively invalidated the existing data in the database. Given that application maintenance in normal development lifecycles will result in changes to the object model, there has a better way to store the data.\n\nThe first thing that is needed is to tell the AerospikeMapper that the data has been versioned. This can be done with the `version` attribute on the @AerospikeNamespace. If this is not specified it will default to 1. When it is changed, it should be incremented by one, and never reduced.\n\nFor example, version 1 (implicitly) is:\n\n```java \n@AerospikeRecord(namespace = \"test\", set = \"testSet\")\npublic static class IntContainer {\n\tpublic int a;\n\tpublic int b;\n\tpublic int c;\n}\n```\n\nand version 2 is:\n\n```java \n@AerospikeRecord(namespace = \"test\", set = \"testSet\", version = 2)\npublic static class IntContainer {\n\tpublic int b;\n\tpublic int c;\n\tpublic int d;\n}\n```\n\nThis still doesn't give us useful information to be able to map prior versions of the record. Hence, there needs to be further information which defines which fields exist in which versions of the object:\n\n```java \n@AerospikeRecord(namespace = \"test\", set = \"testSet\", version = 2)\npublic static class IntContainer {\n    \t@AerospikeVersion(max = 1)\n    \tpublic int a;\n    \tpublic int b;\n    \tpublic int c;\n    \t@AerospikeVersion(min = 2)\n    \tpublic int d;\n}\n```\n\nNow this object can be stored in the database. As the version is 2, any information stored in field `a` with a maximum version of 1 will not be saved. The record saved in the database will look like:\n\n```\nLIST('[2, 3, 4, \"@V2\"]')\n```\n\nNote that a new element has been written which describes the version of the record which was written. When the record is read, this version will tell us which data maps to which fields. Let's say there are 2 records in the database, one written with version 1 and one written with version 2:\n\n```\n*************************** 1. row ***************************\ncontainer: LIST('[1, 2, 3]')\nid: 1\n*************************** 2. row ***************************\ncontainer: LIST('[2, 3, 4, \"@V2\"]')\nid: 2\n```\n\nWhen reading these records, the results would look like:\n\n```\n1: \n   a = 0\n   b = 2\n   c = 3\n   d = 0\n   \n2:\n   a = 0\n   b = 2\n   c = 3\n   d = 4\n```\n\nThe first object (with key `1`) has `d` = 0 since `d` was not written to the database. `a` is also 0 even though it was written to the database in the original record because version 2 of the object should not have field `a`. (The current version of the object is 2 and `a` has a maximum version of 1). The second object (with key `2`) again has `a` being 0 as it was not written to the database as well as not being valid for this version of the object.\n\nNote: This versioning assumes that the application version of the object will never regress. So, for example, it is not possible to read a version 2 database record with a version 1 application object.\n  \n\n#### List Ordinals\n\nThe order of the elements in a list can be controlled. By default, all the elements in the list are ordered by the name of the fields, but -- unlike maps and bins -- sometimes there is value in changing the order of values in a list. Consider for example a financial services company who stores credit card transactions, with the transactions embedded in the account that owns them. They may be embedded in a map with the transaction id as a key, and the transaction details as a list. For example:\n\n```java\npublic static enum AccountType {\n\tSAVINGS, CHEQUING\n}\n\n@AerospikeRecord(namespace = \"test\", set = \"accounts\") \npublic static class Accounts {\n\t@AerospikeKey\n\tpublic int id;\n\tpublic String name;\n\tpublic AccountType type;\n\t@AerospikeEmbed(elementType = EmbedType.LIST)\n\tpublic Map\u003cString, Transactions\u003e transactions;\n\t\n\tpublic Accounts() {\n\t\tthis.transactions = new HashMap\u003c\u003e();\n\t}\n\tpublic Accounts(int id, String name, AccountType type) {\n\t\tthis();\n\t\tthis.id = id;\n\t\tthis.name = name;\n\t\tthis.type = type;\n\t}\n}\n\n@AerospikeRecord(namespace = \"test\", set = \"txns\")\npublic static class Transactions {\n\tpublic String txnId;\n\tpublic Instant date;\n\tpublic double amt;\n\tpublic String merchant;\n\tpublic Transactions() {}\n\tpublic Transactions(String txnId, Instant date, double amt, String merchant) {\n\t\tsuper();\n\t\tthis.txnId = txnId;\n\t\tthis.date = date;\n\t\tthis.amt = amt;\n\t\tthis.merchant = merchant;\n\t}\n}\n\n@Test\npublic void testAccounts() {\n\tAccounts account = new Accounts(1, \"Savings Account\", AccountType.SAVINGS);\n\tTransactions txn1 = new Transactions(\"Txn1\", Instant.now(), 100.0, \"Bob's store\");\n\tTransactions txn2 = new Transactions(\"Txn2\", Instant.now().minus(Duration.ofHours(8)), 134.99, \"Kim's store\");\n\tTransactions txn3 = new Transactions(\"Txn3\", Instant.now().minus(Duration.ofHours(20)), 75.43, \"Sue's store\");\n\t\n\taccount.transactions.put(txn1.txnId, txn1);\n\taccount.transactions.put(txn2.txnId, txn2);\n\taccount.transactions.put(txn3.txnId, txn3);\n\t\n\tmapper.save(account);\n}\n```\n\nThis gets saved in the database as:\n\n```\nid: 1\nname: \"Savings Account\"\ntransactions: MAP('{\"Txn1\":[100, 1610478132904000000, \"Bob's store\", \"Txn1\"], \n\t\t\t\t\"Txn2\":[134.99, 1610449332904000000, \"Kim's store\", \"Txn2\"], \n\t\t\t\t\"Txn3\":[75.43000000000001, 1610406132907000000, \"Sue's store\", \"Txn3\"]}')\ntype: \"SAVINGS\"\n```\n\nHere the transaction time is the second attribute in each list, and the amount is the first attribute. However, a common request is to be able to extract transaction by time. For example, in fraud detection systems, there may be a need to load the N most recent transactions. If the transactions were to be stored with the transaction time as the first element in the list, efficient CDT operations in Aerospike such as `getByValueRange(...)` can be used.\n\nThis ordering can be controlled by the @AerospikeOrdinal annotation:\n\n```java\n@AerospikeRecord(namespace = \"test\", set = \"txns\")\npublic static class Transactions {\n\tpublic String txnId;\n\t@AerospikeOrdinal(value = 1)\n\tpublic Instant date;\n\tpublic double amt;\n\tpublic String merchant;\n\tpublic Transactions() {}\n\tpublic Transactions(String txnId, Instant date, double amt, String merchant) {\n\t\tsuper();\n\t\tthis.txnId = txnId;\n\t\tthis.date = date;\n\t\tthis.amt = amt;\n\t\tthis.merchant = merchant;\n\t}\n}\n```\n\nNow the data will be saved in a different format with the transaction time the first element in the list:\n\n``` \nid: 1\nname: \"Savings Account\"\ntransactions: MAP('{\"Txn1\":[1610478716965000000, 100, \"Bob's store\", \"Txn1\"], \n\t\t\t\t\"Txn2\":[1610449916965000000, 134.99, \"Kim's store\", \"Txn2\"], \n\t\t\t\t\"Txn3\":[1610406716967000000, 75.43000000000001, \"Sue's store\", \"Txn3\"]}')\ntype: \"SAVINGS\"\n```\n\nMultiple ordinals can be specified for a single class, but these must be sequential. So if it is desired to have the first 3 fields in a list specified, they must have @AerospikeOrdinal values of 1,2 and 3.\n\n**Note**: Ordinal fields cannot be versioned.\n  \n#### The importance of Generic Types\n\nWhen using the object mapper, it is important to use generics to describe the types as fully as possible. For example instead of `List accounts;` this should be `List\u003cAccount\u003e accounts;`. Not only is this best practices for Java, but it gives the AeroMapper hints about what is mapped so it can optimize the type and minimize the amount of reflection needed at runtime and hence minimize the performance cost. \n\nFor example, assume there is a mapped type \"B\", and another \"A\" which has a list of B's:\n\n```java\n@AerospikeRecord(namespace = \"test\", set = \"A\")\npublic static class A {\n\t@AerospikeKey\n\tpublic int id;\n\tpublic List\u003cB\u003e listB;\n\t\n\tpublic A() {\n\t\tlistB = new ArrayList\u003c\u003e();\n\t}\n}\n\n@AerospikeRecord(namespace = \"test\", set = \"B\")\npublic static class B {\n\t@AerospikeKey\n\tpublic int id;\n\tpublic String name;\n}\n```\n\nIn this case, the AeroMapper knows that the `listB` object contains either B's or sub-classes of B's. If they're B's, it knows the type (it assumes they're of the declared type by default) and hence needs no extra information to describe it. If an element is a subclass of B it would include the type name in the object reference. In this case we store a B:\n\n```java\n\tB b = new B();\n\tb.id = 2;\n\tb.name = \"test\";\n\tmapper.save(b);\n\t\n\tA a = new A();\n\ta.id = 1;\n\ta.listB.add(b);\n\tmapper.save(a);\n```\n\n\nBut in this case, the object is of the declared type (B) so this needs no type information. Hence, the object stored in the database is:\n\n```\nid: 1\nlistB: LIST('[2]')\n```\n\nHowever, if the class A was declared as:\n\n```java\npublic static class A {\n\t@AerospikeKey\n\tpublic int id;\n\tpublic List listB;\n\t\n\t\n\tpublic A() {\n\t\tlistB = new ArrayList\u003c\u003e();\n\t}\n}\n```\n\n(Note the only difference is that the `List\u003cB\u003e listB` has now become `List listB`).\n\nIn this case, the AeroMapper no longer has any type information so it needs to store full type information against each element in the list:\n\n```\nid: 1\nlistB: LIST('[[2, \"@T:B\"]]')\n```\n\nNote that the element is annotated with `@T:` and the short name of the type. (The short name of the type must be unique within the system, and can be changed using the `shortName` attribute of the `AerospikeRecord` annotation.\n\n----\n\n## Advanced Features\n### Placeholder replacement\nSometimes it is desirable to have the parameters to the annotations not being hard coded. For example, it might be desirable to have different namespaces for dev, test, staging and production. Annotations in Java must have constant parameters, so they cannot be pulled from environment variables, system properties, etc.\n\nTo work around this, the parameters to annotations which are strings can be driven by environment variables or system properties using a special syntax. This is particularly prevalent for namespace names, set names and bin names.\n\nFor an environment variable the syntax is: ``\"#{ENV_VAR_NAME}\"`` or ``#{ENV_VAR_NAME:default_value}``. For system properties, the syntax is ``${system.property.name}\"`` or ``${system.property.name:default_value}\"``.\n\nFor example:\n\n```java\n@AerospikeRecord(namespace=\"test\", set=\"${people.set.name:people}\")\npublic class Person {\n```\n\nIn this case, if the ``people.set.name`` system parameter is set, that value will be used for the set name. If it is not set, ``people`` will be used as the set name. The system property can be set on the command line in this case using syntax similar to:\n\n```\n-Dpeople.set.name=person\n```\n\nAn example using an environment variable:\n\n```java\n@AerospikeBin(name=\"#{ACCOUNT_TITLE_BIN_NAME}\")\nprivate String title;\n```\n\nIn this case, if the environment variable ``ACCOUNT_TITLE_BIN_NAME`` is set, that will be the name of the bin which is used. If it is not set, it will be like the annotation does not specify the ``name`` parameter at all, which means that the field name (``title``) will be used for the bin name.\n\n----\n\n### Subclasses\nThe AeroMapper also supports mapping object hierarchies. To see this, consider the following class hierarchy:\n\n![Hierarchy](/images/classHierarchy.png)\n\nThere are 2 abstract classes here: BaseClass which every business class in the hierarchy will inherit from, and Account which is an abstract superclass of all the different sort of Accounts (at the moment just Savings and Checking). In terms of mapping data, the Customer class will be mapped to it's own set in Aerospike. However, when considering the Checking and Savings accounts there are 2 different strategies which can be used for mapping the data:\n\n1. Both Account types are mapped to the same set (eg Account) and co-mingled with one another. \n2. Checking and Savings are written to independent sets, holding only records of that type.\n\nThe AeroMapper supports both strategies for resolving subclasses, as well as being able to inherit just data fields from superclasses.\n\n\n#### Data Inheritance\n\nConsider the Customer class which inherits from the BaseClass:\n\n```java\n@AerospikeRecord\npublic static class BaseClass {\n\tprivate Date lastReadTime;\n\tprivate final Date creationTime;\n\t\n\tpublic BaseClass() {\n\t\tthis.creationTime = new Date();\n\t}\n}\n\n@AerospikeRecord(set = \"customer\", namespace = \"test\")\npublic static class Customer extends BaseClass {\n\t@AerospikeKey\n\t@AerospikeBin(name = \"id\")\n\tprivate final String customerId;\n\tprivate final String name;\n\t@AerospikeBin(name = \"date\")\n\tprivate Date dateJoined;\n\t@AerospikeReference\n\tprivate List\u003cAccount\u003e accounts;\n\n\tpublic Customer(String customerId, String name) {\n\t\tthis(customerId, name, new Date());\n\t}\n\t\n\t@AerospikeConstructor\n\tpublic Customer(@ParamFrom(\"id\") String customerId, @ParamFrom(\"name\") String name, @ParamFrom(\"date\") Date dateJoined) {\n\t\tthis.customerId = customerId;\n\t\tthis.name = name;\n\t\tthis.dateJoined = dateJoined;\n\t\tthis.accounts = new ArrayList\u003c\u003e();\n\t\tthis.accountMap = new HashMap\u003c\u003e();\n\t}\n\tpublic Date getDateJoined() {\n\t\treturn dateJoined;\n\t}\n\tpublic void setDateJoined(Date dateJoined) {\n\t\tthis.dateJoined = dateJoined;\n\t}\n\tpublic List\u003cAccount\u003e getAccounts() {\n\t\treturn accounts;\n\t}\n\tpublic void setAccounts(List\u003cAccount\u003e accounts) {\n\t\tthis.accounts = accounts;\n\t}\n\tpublic String getCustomerId() {\n\t\treturn customerId;\n\t}\n\tpublic String getName() {\n\t\treturn name;\n\t}\n}\n```\n\nIn this case, the data from both classes (BaseClass and Customer) will be aggregated into the Customer record resulting in a record like:\n\n```\naql\u003e select * from test.customer\n*************************** 1. row ***************************\naccounts: LIST('[[\"SVNG1\", \"SVG\"], [\"CHK2\", \"CHK\"]]')\ndate: 1614025827020\nid: \"cust1\"\nname: \"Tim\"\ncreationTime: 1614025827020\n```\n\nNote that the data here contains both the data for the child class (Customer) and the superclass (BaseClass)\n \n#### Subclass Inheritance\n\nAs the first example, let's roll the Checking and Savings account up to the Account set.\n\n```\n@AerospikeRecord(namespace = \"test\", set = \"subaccs\", ttl=3600, durableDelete = true, sendKey = true)\npublic static class Account extends BaseClass {\n\t@AerospikeKey\n\tprotected String id;\n\tprotected long balance;\n}\n\n@AerospikeRecord(shortName = \"SVG\")\npublic static class Savings extends Account {\n\tprivate long numDeposits;\n\tprivate float interestRate;\n}\n\n@AerospikeRecord(shortName = \"CHK\")\npublic static class Checking extends Account {\n\tprivate int checksWritten;\n}\n```\n\nIn this case the 2 subclasses (Checking and Savings) do not define their own set so they will inherit the namespace and set from the closest superclass which has a namespace and set (in this case Account). Since this set will now contain both Savings and Checking accounts, we need some way of differentiating them. By default the name of the class will be added: `Savings` and `Checking` respectively. However, to keep these names short, we can specify `shortName`s for these classes, `SVG` and `CHK` respectively.\n\nIf a class defines the same set and namespace as it's closest parent with a set and namespace, the effect will be as if the child class did not define the set and namespace. That is, the following 2 sections of code will have exactly the same effect:\n\n```java\n@AerospikeRecord(namespace = \"test\", set = \"subaccs\", ttl=3600, durableDelete = true, sendKey = true)\npublic static class Account extends BaseClass {\n\t@AerospikeKey\n\tprotected String id;\n\tprotected long balance;\n}\n\n@AerospikeRecord(shortName = \"SVG\")\npublic static class Savings extends Account {\n\tprivate long numDeposits;\n\tprivate float interestRate;\n}\n```\n\nand\n\n```java\n@AerospikeRecord(namespace = \"test\", set = \"subaccs\", ttl=3600, durableDelete = true, sendKey = true)\npublic static class Account extends BaseClass {\n\t@AerospikeKey\n\tprotected String id;\n\tprotected long balance;\n}\n\n@AerospikeRecord(namespace = \"test\", set = \"subaccs\", shortName = \"SVG\")\npublic static class Savings extends Account {\n\tprivate long numDeposits;\n\tprivate float interestRate;\n}\n```\nIt should be noted that the names used to refer to the class (whether it is a `shortName` or the normal class name) must be globally unique in the system.\n\nLet's look at a rather contrived example, which shows how these are used in practice. We will define 2 Savings and 2 Checking accounts and save them to the database, as well as one Account. In a real application the Account class would likely be `abstract` and hence would not be saved to the database in it's own right, but it's a useful exercise to understand how things are mapped.\n\n```java\nSavings savingsAccount1 = new Savings();\nsavingsAccount1.interestRate = 0.03f;\nsavingsAccount1.numDeposits = 17;\nsavingsAccount1.id = \"SVNG1\";\nsavingsAccount1.balance = 200;\nmapper.save(savingsAccount1);\n\nSavings savingsAccount2 = new Savings();\nsavingsAccount2.interestRate = 0.045f;\nsavingsAccount2.numDeposits = 11;\nsavingsAccount2.id = \"SVNG2\";\nsavingsAccount2.balance = 99;\nmapper.save(savingsAccount2);\n\nChecking checkingAccount1 = new Checking();\ncheckingAccount1.checksWritten = 4;\ncheckingAccount1.id = \"CHK1\";\ncheckingAccount1.balance = 600;\nmapper.save(checkingAccount1);\n\nChecking checkingAccount2 = new Checking();\ncheckingAccount2.checksWritten = 23;\ncheckingAccount2.id = \"CHK2\";\ncheckingAccount2.balance = 10902;\nmapper.save(checkingAccount2);\n\t\t\nAccount account = new Account();\naccount.balance = 927;\naccount.id = \"Account1\";\nmapper.save(account);\n```\n\nRunning this and querying the data gives:\n\n```\naql\u003e set output raw\nOUTPUT = RAW\naql\u003e select * from test.subaccs\n*************************** 1. row ***************************\ninterestRate: 0.02999999932944775\nnumDeposits: 17\nbalance: 200\nid: \"SVNG1\"\ncreationTime: 1614231134837\n*************************** 2. row ***************************\nchecksWritten: 23\nbalance: 10902\nid: \"CHK2\"\ncreationTime: 1614231134852\n*************************** 3. row ***************************\ninterestRate: 0.04500000178813934\nnumDeposits: 11\nbalance: 99\nid: \"SVNG2\"\ncreationTime: 1614231134851\n*************************** 4. row ***************************\nchecksWritten: 4\nbalance: 600\nid: \"CHK1\"\ncreationTime: 1614231134852\nPK: \"Account1\"\n*************************** 5. row ***************************\nbalance: 927\nid: \"Account1\"\ncreationTime: 1614231134853\n```\n\nAs you can see, the savings and checking accounts are intermingled in the same set, each with the appropriate fields.\n\nIn order to show how these items are referenced, we create a contrived Container class:\n\n```java\n@AerospikeRecord(namespace = \"test\", set = \"container\")\nprivate class Container {\n\t@AerospikeKey\n\tprivate long id;\n\tprivate Account account;\n\tprivate Savings savings;\n\tprivate Checking checking;\n\tprivate List\u003cAccount\u003e accountList = new ArrayList\u003c\u003e();\n\tprivate Account primaryAccount;\n}\n```\n\nAnd then populate and save it:\n\n```java\nContainer container = new Container();\ncontainer.account = account;\ncontainer.checking = checkingAccount1;\ncontainer.savings = savingsAccount1;\ncontainer.primaryAccount = savingsAccount1;\ncontainer.accountList.add(account);\ncontainer.accountList.add(savingsAccount1);\ncontainer.accountList.add(checkingAccount1);\nmapper.save(container);\n```\n\nthen looking at the database we see:\n\n```\naql\u003e select * from test.container\n*************************** 1. row ***************************\naccount: \"Account1\"\naccountList: LIST('[\"Account1\", [\"SVNG1\", \"SVG\"], [\"CHK1\", \"CHK\"]]')\nchecking: \"CHK1\"\nid: 0\nprimaryAccount: LIST('[\"SVNG1\", \"SVG\"]')\nsavings: \"SVNG1\"\n```\n\nNote that if an object is mapped to the actual type (eg Account to Account) then the reference simply contains the id. However, if a subclass is mapped to a variable declared as the supertype (eg Savings to Account) then the reference must contain the type of the subclass as well as the key, and hence is contained within a list. If the AeroMapper didn't do this, when it went to load the record from the database it would not know which class to instantiate and hence could not determine the how to map the data to the record.\n\nFor this reason, it is strongly recommended that all attributes use a parameterized type, eg `List\u003cAccount\u003e` rather than `List`\n\nIt 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 Java Object Mapper has already computed the optimal way of accessing that information. If it encounters a sub-class at runtime (i.e. the instantiated type is not the same as the declared type), 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.\n\nBy 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.\n\nFor example, say we need a list of Customers as a field on a class. We could declare this as:\n\n```java\npublic List\u003cCustomer\u003e customers;\n```\n\nor\n\n```java\npublic List customers;\n```\n\nThe former is considered better style in Java and also provides the Java Object Mapper 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 (as it must store the runtime type of each element in the list in addition to the data).\n\n### Using Interfaces\nSometimes it is better to have an interface to group common types rather an an abstract superclass. In this case the Object Mapper supports placing the `@AerospikeReocrd` annotation on the interface and it will behave as if the annotation was on a superclass. There are multiple different was of placing the `@AerospikeRecord` annotation on a single class, and the order the Object Mapper looks for them in is:\n1. Configuration file\n2. Class level definition\n3. First parent class with `@AerospikeRecord` annotation (of any ancestor)\n4. Interface with `@AerospikeRecord` annotation (first one found)\n\nOnce the Object Mapper finds an appropriate annotation it ignores any further annotations and uses the definitions on the first one found.\n\n----\n\n### Custom Object Converters\nSometimes, the representation of the data in Aerospike and the representation in Java should be very different. Consider a class which represents a playing card and another class which represents a poker hand:\n\n```java\npublic enum Suit {\n    CLUBS, DIAMONDS, HEARTS, SPADES\n}\n\n@AerospikeRecord(namespace = NAMESPACE, set = \"card\")\npublic class Card {\n    public char rank;\n    public Suit suit;\n\n    public Card() {}\n    public Card(char rank, Suit suit) {\n        super();\n        this.rank = rank;\n        this.suit = suit;\n    }\n}\n\n@AerospikeRecord(namespace = NAMESPACE, set = \"poker\")\npublic class PokerHand {\n    @AerospikeEmbed\n    public Card playerCard1;\n    @AerospikeEmbed\n    public Card playerCard2;\n    @AerospikeEmbed\n    public List\u003cCard\u003e tableCards;\n    @AerospikeKey\n    public String id;\n\n    public PokerHand(String id, Card playerCard1, Card playerCard2, List\u003cCard\u003e tableCards) {\n        super();\n        this.playerCard1 = playerCard1;\n        this.playerCard2 = playerCard2;\n        this.tableCards = tableCards;\n        this.id = id;\n    }\n    \n    public PokerHand() {}\n}\n```\n\nThe program to create and save a poker hand might look like:\n\n```java \nPokerHand blackjackHand = new PokerHand(\n        \"1\",\n        new Card('6', Suit.SPADES),\n        new Card('9', Suit.HEARTS),\n        Arrays.asList(new Card('4', Suit.CLUBS), new Card('A', Suit.HEARTS)));\n\nAeroMapper mapper = new AeroMapper.Builder(client)\n        .build();\n\nmapper.save(blackjackHand);\n```\n\nThis works, but creates a fairly verbose representation of the card in the database:\n\n```\nid: \"1\"\nplayerCard1: MAP('{\"rank\":54, \"suit\":\"SPADES\"}')\nplayerCard2: MAP('{\"rank\":57, \"suit\":\"HEARTS\"}')\ntableCards: LIST('[{\"rank\":52, \"suit\":\"CLUBS\"}, {\"rank\":65, \"suit\":\"HEARTS\"}]')\n```\n\nWhy not store the whole class as a simple 2 character string, one character which is the rank, and the second is the suit?\n\nIn this case, we have to create a custom object converter:\n\n```java\npublic static class CardConverter {\n    @ToAerospike\n    public String toAerospike(Card card) {\n        return card.rank + card.suit.name().substring(0, 1);\n    }\n\n    @FromAerospike\n    public Card fromAerospike(String card) {\n        if (card.length() != 2) throw new AerospikeException(\"Unknown card: \" + card);\n\n        char rank = card.charAt(0);\n        switch (card.charAt(1)) {\n            case 'C': return new Card(rank, Suit.CLUBS);\n            case 'D': return new Card(rank, Suit.DIAMONDS);\n            case 'H': return new Card(rank, Suit.HEARTS);\n            case 'S': return new Card(rank, Suit.SPADES);\n            default:\n                throw new AerospikeException(\"unknown suit: \" + card);\n        }\n    }\n}\n```\n\nThe custom converter must have a method annotated with @ToAerospike and another with @FromAerospike. The @ToAerospike method takes 1 parameter which is the representation of the card in POJO format (the `Card` type in the case) and returns the representation used to store the data in Aerospike (`String` in this case). The @FromAerospike similarly takes the Aerospike representation and returns the POJO representation. The return type of the @FromAerospike method must match the parameter type of the @ToAerospike method and vice versa. When determining how to convert a type, the AeroMapper will see if it matches the parameter to the @ToAerospike method and invoke this method.\n\nNote that custom converters take priority over in-built converters. So if it is preferred to store a java.util.Date in the database as a String instead of a number for example, this can be done using a custom type converter.\n\nBefore the AeroMapper can use the custom converter, it must be told about it. This is done in the the builder:\n\n```java\nmapper = new AeroMapper.Builder(client)\n        .addConverter(new CardConverter())\n        .build();\n```\n\nNow when the object is stored in Aerospike, it is stored in a far more concise format:\n\n```\n*************************** 1. row ***************************\nid: \"1\"\nplayerCard1: \"6S\"\nplayerCard2: \"9H\"\ntableCards: LIST('[\"4C\", \"AH\"]')\n```\n\nIt should be noted that since the inbuilt converter system in the AeroMapper no longer needs to know about the structure of the card, the card object itself can be simplified. Instead of:\n\n```java\n@AerospikeRecord(namespace = NAMESPACE, set = \"card\")\npublic static class Card {\n    public char rank;\n    public Suit suit;\n\n    public Card() {}\n    public Card(char rank, Suit suit) {\n        super();\n        this.rank = rank;\n        this.suit = suit;\n    }\n}\n```\n\nit can now become:\n\n```java\npublic static class Card {\n    public char rank;\n    public Suit suit;\n\n    public Card(char rank, Suit suit) {\n        super();\n        this.rank = rank;\n        this.suit = suit;\n    }\n}\n```\n\nNotice the removal of the annotation and the no-argument constructor. The referencing type can now become simpler too, as the Card class is seen as a primitive type, not an associated object. Instead of\n\n```java\n@AerospikeRecord(namespace = NAMESPACE, set = \"poker\")\npublic static class PokerHand { \n    @AerospikeEmbed\n    public Card playerCard1;\n    @AerospikeEmbed\n    public Card playerCard2;\n    @AerospikeEmbed\n    public List\u003cCard\u003e tableCards;\n    @AerospikeKey\n    public String id;\n\n    public PokerHand(String id, Card playerCard1, Card playerCard2, List\u003cCard\u003e tableCards) {\n        super();\n        this.playerCard1 = playerCard1;\n        this.playerCard2 = playerCard2;\n        this.tableCards = tableCards;\n        this.id = id;\n    }\n    \n    public PokerHand() {}\n}\n```\n\nIt can simply become:\n\n```java\n@AerospikeRecord(namespace = NAMESPACE, set = \"poker\")\npublic static class PokerHand {\n    public Card playerCard1;\n    public Card playerCard2;\n    public List\u003cCard\u003e tableCards;\n    @AerospikeKey\n    public String id;\n\n    public PokerHand(String id, Card playerCard1, Card playerCard2, List\u003cCard\u003e tableCards) {\n        super();\n        this.playerCard1 = playerCard1;\n        this.playerCard2 = playerCard2;\n        this.tableCards = tableCards;\n        this.id = id;\n    }\n    \n    public PokerHand() {}\n}\n```\n\n----\n\n## External Configuration File\nAn configuration file in YAML format can be created and passed to the builder either as a File object containing the YAML file or as a string containing the YAML. Note that passing a string representing a filename does not work -- it should be explicitly turned into a file using `new File(fileName)` for example. \n\nAll of the properties which can be specified via annotations can also be specified via the configuration file. If the same property exists in both a configuration file and an annotation, the value in the configuration file is used in preference to the value in the annotation. This allows for changing the way data is mapped in different environments by specifying a different configuration file. For example, in a development environment it might be desirable for an embedded object to be stored as a map for ease of debugging, but then in test, staging and prod environments it might be useful to store the same object as a list to prevent bloating of the data.\n\nThe syntax of the builder allows for multiple configuration files to be specified. If the same class definition appears in 2 different configuration files, the first one encountered for that class will be taken and subsequent ones ignored, even if those subsequent ones contain additional information not specified in the first one.\n\nAn example configuration file might contain:\n\n```yaml\n---\nclasses:\n  - class: com.aerospike.mapper.AeroMapperConfigurationYamlTest$DataClass\n    namespace: test\n    set: dataClass\n    key:\n      field: id\n    bins:\n      - field: date\n        name: d1\n  - class: com.aerospike.mapper.AeroMapperConfigurationYamlTest$ContainerClass\n    namespace: test\n    set: containers\n    key:\n      field: id\n    bins:\n      - field: dataClasses\n        embed:\n          type: MAP\n          elementType: LIST\n        name: data\n```\n(Note: DataClass and ContainerClasss were defined as static inner classes inside AeroMapperConfigurationYamlTest, hence the need for the long class name. In real production applications this isn't likely to be needed)\n \n### File Structure\nThe structure of the file is: \n \nTop level is an array of classes. Each class has:\n- **class**: the name of the class. This must match the full class name to which you want to apply the configuration\n- **namespace**: The namespace to map this class to. Can be unspecified if the class is only ever used for embedding in another object\n- **set**: The set to map this class to. Can be unspecified if the class is only ever used for embedding in another object\n- **factoryClass**: The class of the factory to use, if any. If this is specified, so must a factoryMethod.\n- **factoryMethod**: The static method of the factory to use, if any. If this is specified, so must a factoryClass. See [Constructor Factories](#constructor-factories) for more information.\n- **durableDelete** (boolean): If set to `true`, any deletes on this class will use [durable deletes](https://www.aerospike.com/docs/guide/durable_deletes.html). If not set, it will use the flag from the policy for this class\n - **mapAll** (boolean, default `true`): If true, all fields of this class will automatically be mapped to the database. Fields can be excluded using `exclude` on the bin config. If this is set to false, only the fields specified with an explicit bin configuration will be stored.\n - **sendKey** (boolean): If true, the key of the record will be stored in Aerospike. See [send key](https://www.aerospike.com/docs/guide/policies.html#send-key) for more details. If this is false, the key will not be stored in Aerospike. If not set, the `sendKey` field from the policy will be used.\n - **ttl**: the time to live for the record, mapped to the expiration time on the policy. If not set, the expiration from the policy will be used.\n - **shortName**: When this class name must be stored in the database, this is the name to store instead of the full class names. This is used particularly for sub-classes. For example, if an Account class has a Checking class and Savings class as subclasses, an object might store a reference to an Account (compiled type of Account), but this really is a Checking account (runtime type of Checking). If the reference to the account is persisted, a list storing the key and the type will be saved, and this name will be used as the type.\n - **key**: a [key structure](key-structure), specified below\n - **bins**: a list of [bin structure](bin-structure), specified below\n - **version**: The version of the record. Must be an integer with a positive value. If not specified, will default to 1. See [Versioning Links](#versioning-lists) for more details. \n\n#### Key Structure\nThe key structure is used to specify the key to a record. Keys are optional in some situations. For example, if Object A embeds an Object B, B does not need a key as it is not stored in Aerospike in its own right.\n\nThe key structure contains:\n- **field**: The name of the field which to which this key is mapped. If this is provided, the getter and setter cannot be provided.\n- **storeAsBin**: Store the primary key as a bin in the database, alternatively it is recommended to use the `sendKey` facility related to Aerospike to save the key in the record's metadata (and set this flag to false). When the record is read, the value will be pulled back and placed in the key field.\n- **getter**: The getter method used to populate the key. This must be used in conjunction with a setter method, and excludes the use of the field attribute.\n- **setter**: The setter method used to map data back to the Java key. This is used in conjunction with the getter method and precludes the use of the field attribute. Note that the return type of the getter must match the type of the first parameter of the setter, and the setter can have either 1 or 2 parameters, with the second (optional) parameter being either of type [com.aerospike.client.Key](https://www.aerospike.com/apidocs/java/com/aerospike/client/Key.html) or Object.\n\n#### Bin Structure\nThe bin structure contains:\n- **embed**: An [embed structure](#embed-structure) used for specifying that the contents of this bin should be included in the parent record, rather than being a reference to a child record. There can only be one embed structure per field, and if an embed structure is present, a [reference structure](#reference-structure) cannot be. If a field refers to another AerospikeRecord, either in a collection or in it's own right, and neither an embed or reference structure is specified, a reference will be assumed by default.\n- **exclude**: A boolean value as to whether this bin should be mapped to the database. Defaults to true.\n- **field**: The name of the field which to which this bin is mapped. If this is provided, the getter and setter cannot be provided.\n- **getter**: The getter method used to populate the bin. This must be used in conjunction with a setter method, and excludes the use of the field attribute.\n- **name**: The name of the bin to map to. If this is not provided and a field is, this will default to the field name. The name must be provided if this bin maps to a getter/setter combination.\n- **ordinal**: For items mapped as lists, this ordinal specifies the location of this bin in the list. If this is not provided, the position of the bins in the list will be determined by alphabetical ordering.\n- **reference**: A [reference structure](#reference-structure) detailing that a child object referenced by this bin should be stored as the key of the child rather than embedding it in the parent object. The use of a reference precludes the use of the embed attribute, and if neither is specified then reference is assumed as the default.\n- **setter**: The setter method used to map data back to the Java POJO. This is used in conjunction with the getter method and precludes the use of the field attribute. Note that the return type of the getter must match the type of the first parameter of the setter, and the setter can have either 1 or 2 parameters, with the second (optional) parameter being either of type [com.aerospike.client.Key](https://www.aerospike.com/apidocs/java/com/aerospike/client/Key.html) or Object.\n\n#### Embed Structure\nThe embed structure is used when a child object should be fully contained in the parent object without needing to be stored in the database as a separate record. For example, it might be that Customer object contains an Address, but the Address is not stored in a separate table in Aerospike, but rather put into the database as part of the customer record.\n\nThe Embed structure contains:\n- **type**: The type of the top level reference. If this is just a reference to another object, eg \n\n```java\npublic class Customer {\n\t...\n\t@AerospikeEmbed\n\tAddress address;\n```\n\nthen the type refers to how the child will be stored in the parent. There are 2 options: LIST or MAP. Maps are more readable in that each bin in the child object is named, but this name consumes space in the database and hence this is the less efficient storage structure.\n\nIf the top level reference is a container class (List or Map), then this type refers to how the list or map is represented in the database. For example, \n\n```java\npublic class Customer {\n\t...\n\tList\u003cAddress\u003e address;\n```\n\nIf this has a type of LIST, then the addresses in Aerospike will be stored in a list. Lists preserve the ordering in the original list. However, it can also be stored as a MAP, in which case the key of the sub-object (Address in this case) becomes the map key and the elements become the value in the map. In this case the list ordering is NOT preserved upon retrieval -- the map elements are stored in a K_ORDERED map, so the elements will be returned sorted by their key.\n\n- **elementType**:  If the top level reference is a container (List or Map), this type specifies how the children objects are to be stored in Aerospike. For example, if `type = MAP` and `elementType = LIST` for the list of Customers in the above example, the bin in Aerospike will contain a K_ORDERED map, each of which will have an Address as the value, and the elements of the address will be stored in a list.\n\n- **saveKey**: Boolean, defaults to false. This is useful when storing a list of elements as a LIST inside a MAP. Given the map key is the key of the record, it is often redundant to have the key stored separately in the list of values for the underlying object. However, if it is desired to have the key again in the list, set this value to true.\n\n#### Reference Structure\nThe reference structure is used when the object being referenced is not to be embedded in the owning object, but rather is to be stored in a separate table. \n- **lazy**: Boolean, defaults to false. When the parent object is loaded, references marked as lazy are NOT loaded. Instead a placeholder object is created with only the primary key information populated, so those objects can be loaded later.\n- **batchLoad**: Boolean, defaults to true. When the parent object is loaded, all non-lazy children will also be loaded. If there are several children, it is more efficient to load them from the database using a batch load. if this flag is set to false, children will not be loaded via a batch load. Note that if the parent object has 2 or less children to load, it will single thread the batch load as this is typically more performant than doing a very small batch. Otherwise the batchPolicy on the parent class will dictate how many nodes are hit in the batch at once.\n- **type**: Either ID or DIGEST, defaults to ID. The ID option stores the primary key of the referred object in the referencer, the DIGEST stores the digest instead. Note that DIGEST is not compatible with `lazy=true` as there is nowhere to store the digest. (For example, if the primary key of the object is a long, the digest is 20 bytes, without dynamically creating proxies or subtypes at runtime there is nowhere to store these 20 bytes. Dynamically creating objects like this is not performant so is not allowed).\n\n### Configuration through code\n\nIt is also possible to configure classes through code. This is very useful in situations where external libraries (whose source code is not available) are used and providing all the information in an external configuration file is overkill. This configuration is performed when building the Object Mapper. Let's look at this with an example:\n\n```java\n@Data\n@AerospikeRecord(namespace = \"test\")\npublic class A {\n    @AerospikeKey\n    private long id;\n    @AerospikeEmbed(type = AerospikeEmbed.EmbedType.LIST)\n    private List\u003cB\u003e b;\n    private String aData;\n}\n    \n@Data\npublic class B {\n    private C c;\n    private String bData;\n}\n    \n@Data\npublic class C {\n    private String id;\n    private String cData;\n}\n```\n\nIn this example, let's assume that the source code is available for class `A` but not for either `B` or `C`. If we run this as is, the Object Mapper will not know how to handle the child classes. It will determine that B should be mapped as it's referenced directly from A, but has no idea what to do with C. Using a default builder will throw a `NotSerializableException`. \n\nTo solve this, we can introduce some configuration in the builder:\n```java\nClassConfig classConfigC = new ClassConfig.Builder(C.class)\n        .withKeyField(\"id\")\n        .build();\nClassConfig classConfigB = new ClassConfig.Builder(B.class)\n        .withFieldNamed(\"c\").beingEmbeddedAs(AerospikeEmbed.EmbedType.MAP)\n        .build();\nAeroMapper mapper = new AeroMapper.Builder(client)\n        .withClassConfigurations(classConfigB, classConfigC)\n        .build();\n```\n\nIn this case we've told the mapper that `B.class` should be treated as an `AerospikeRecord` (`.withConfigurationForClass(B.class)`) and that the 'c' field in that class should be embedded as a MAP. The class `C` is also set to be a mapped class and that the key of that class is to be the field `id`. The class needs to have a key as it's being stored in a map, and objects being stored in a map must be identified by a key.\n\n## Virtual Lists\n\nWhen 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.  \n\nConsider a TODO list, where there are Items which contain the items to be performed and a container for these items:\n\n```java\n@AerospikeRecord(namespace = \"test\", set = \"item\")\npublic class Item {\n\t@AerospikeKey\n\tprivate int id;\n\tprivate Date due;\n\tprivate String desc;\n\tpublic Item(int id, Date due, String desc) {\n\t\tsuper();\n\t\tthis.id = id;\n\t\tthis.due = due;\n\t\tthis.desc = desc;\n\t}\n\t\n\tpublic Item() {\n\t}\n}\n\n@AerospikeRecord(namespace = \"test\", set = \"container\")\npublic class Container {\n\t@AerospikeKey\n\tprivate int id;\n\tprivate String name;\n\t@AerospikeEmbed(type = EmbedType.MAP, elementType = EmbedType.LIST)\n\tprivate List\u003cItem\u003e items;\n\t\n\tpublic Container() {\n\t\tthis.items = new ArrayList\u003c\u003e();\n\t}\n}\n````\n\nNote that in this case the items are embedded into the container and not referenced. This is what is needed for virtual lists, they must have a list of items in the database associated with a single record.\n\nThese items can be populated using the functionally presented above. For example:\n\n```javs\nContainer container = new Container();\ncontainer.id = 1;\ncontainer.name = \"container\";\n\ncontainer.items.add(new Item(100, new Date(), \"Item 1\"));\ncontainer.items.add(new Item(200, new Date(), \"Item 2\"));\ncontainer.items.add(new Item(300, new Date(), \"Item 3\"));\ncontainer.items.add(new Item(400, new Date(), \"Item 4\"));\n\nAeroMapper mapper = new AeroMapper.Builder(client).build();\nmapper.save(container);\n````\n\nThis yields a container with 4 items as expected:\n\n```\nid: 1\nitems: KEY_ORDERED_MAP('{\n\t100:[\"Item 1\", 1618442036607], \n\t200:[\"Item 2\", 1618442036607], \n\t300:[\"Item 3\", 1618442036607], \n\t400:[\"Item 4\", 1618442036607]}')\nname: \"container\"\n```\n\nNote that whilst in this case the list is pre-populated with information, this is not a requirement for using virtual list.\n\nA virtual list is created through the mapper:\n\n```java\nVirtualList\u003cItem\u003e list = mapper.asBackedList(container, \"items\", Item.class);\n```\n\nThe 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.\n\nOnce a virtual list has been created, methods to manipulate the list can be executed. For example:\n\n```java\nlist.append(new Item(500, new Date(), \"Item5\"));\n```\n\nAfter this, the list in the database looks like:\n\n```\nid: 1\nitems: KEY_ORDERED_MAP('{\n\t100:[\"Item 1\", 1618442036607], \n\t200:[\"Item 2\", 1618442036607], \n\t300:[\"Item 3\", 1618442036607], \n\t400:[\"Item 4\", 1618442036607], \n\t500:[\"Item5\", 1618442991205]}')\nname: \"container\"\n```\n\nNote 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.*\nVirtual 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:\n\n```java\nList\u003cItem\u003e results = (List\u003cItem\u003e) list.beginMultiOperation()\n\t\t.append(new Item(600, new Date(), \"Item6\"))\n\t\t.removeByKey(200)\n\t\t.getByKeyRange(100, 450)\n\t.end();\n```\n\nThis 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:\n\n```\nid: 1\nitems: KEY_ORDERED_MAP('{\n\t100:[\"Item 1\", 1618442036607], \n\t300:[\"Item 3\", 1618442036607], \n\t400:[\"Item 4\", 1618442036607], \n\t500:[\"Item5\", 1618442991205], \n\t600:[\"Item6\", 1618445996551]}')\nname: \"container\"\n```\n\nThe 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.\n\nHowever, if we changed the call to be:\n\n```java\nList\u003cItem\u003e results = (List\u003cItem\u003e) list.beginMultiOperation()\n\t\t.append(new Item(600, new Date(), \"Item6\"))\n\t\t.removeByKey(200)\n\t.end();\n```\n\nThen 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)\n\nHowever, if we wanted a particular operation in the list to return its result, we can flag it with `asResult()`. For example:\n\n```java\nList\u003cItem\u003e results = (List\u003cItem\u003e) list.beginMultiOperation()\n\t\t.append(new Item(600, new Date(), \"Item6\"))\n\t\t.removeByKey(200).asResult()\n\t\t.removeByKey(500)\n\t.end();\n```\n\nIn this case, the element removed with with the `removeByKey(200)` will be returned, giving the data associated with item 200.\n\nThe type of the result (where supported) can also be changed with a call to `asResultOfType()`. For example:\n\n```java\nlong count = (long)list.beginMultiOperation()\n\t\t.append(new Item(600, new Date(), \"Item6\"))\n\t\t.removeByKey(200)\n\t\t.removeByKeyRange(20, 350).asResultOfType(ReturnType.COUNT)\n\t\t.getByKeyRange(100, 450)\n\t.end();\n```\n\nThe 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.\n\nAlso 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:\n\n```java\npublic \u003cT\u003e List\u003cObject\u003e convertToList(@NotNull T instance);\npublic \u003cT\u003e Map\u003cString, Object\u003e convertToMap(@NotNull T instance);\npublic \u003cT\u003e T convertToObject(Class\u003cT\u003e clazz, List\u003cObject\u003e record);\npublic \u003cT\u003e T convertToObject(Class\u003cT\u003e clazz, Map\u003cString,Object\u003e record);\npublic \u003cT\u003e T convertToObject(Class\u003cT\u003e clazz, Record record);\n```\n\nNote: 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.\n\n## Scans\nScans can be used to process every record in a set. The scan iterates through every item in the set and invokes a callback for every item in the set. For example:\n\n```java\nmapper.scan(Person.class, (person) -\u003e {\n\t// ... process person\n\treturn true;\n});\n```\n\nIf the processing method returns true, the scan continues. However, if the processing method returns false the scan will abort. Note that if the scan policy calls for multi-threading of the scans, the callback method may be invoked by multiple threads at once and hence must be thread safe. If one thread aborts the scan, other threads already in the processing method will finish processing their records.\n\nNote that if you want to process only some records in a set you can attach an Expression on the optional policy passed to the scan. For example, if there is a `Person` class:\n\n```java\n@AerospikeRecord(namespace = \"test\", set = \"testScan\")\npublic class Person {\n\t@AerospikeKey\n\tprivate int id;\n\tprivate String name;\n\tprivate int age;\n\t\n\tpublic Person(@ParamFrom(\"id\") int id, @ParamFrom(\"name\") String name, @ParamFrom(\"age\") int age) {\n\t\tsuper();\n\t\tthis.id = id;\n\t\tthis.name = name;\n\t\tthis.age = age;\n\t}\n\n\tpublic int getId() {\n\t\treturn id;\n\t}\n\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\tpublic int getAge() {\n\t\treturn age;\n\t}\n}\n```\n \nand then several people are inserted:\n \n ```java\nmapper.save(new Person(1, \"Tim\", 312),\n\t\t\tnew Person(2, \"Bob\", 44),\n\t\t\tnew Person(3, \"Sue\", 56),\n\t\t\tnew Person(4, \"Rob\", 23),\n\t\t\tnew Person(5, \"Jim\", 32),\n\t\t\tnew Person(6, \"Bob\", 78));\n ```\n\nAs a contrived example, let's say we want to count the number of people in the set called \"Bob\". We can simply do:\n\n```java\nAtomicInteger counter = new AtomicInteger(0);\nScanPolicy scanPolicy = new ScanPolicy(mapper.getScanPolicy(Person.class));\nscanPolicy.filterExp = Exp.build(Exp.eq(Exp.stringBin(\"name\"), Exp.val(\"Bob\")));\nmapper.scan(scanPolicy, Person.class, (person) -\u003e {\n\tcounter.incrementAndGet();\n\treturn true;\n});\n```\n\nNote that when we altered the ScanPolicy, we had to make a copy of it first. If we fail to do this, the ScanPolicy will be altered for all subsequent calls. To clarify, the **wrong** way to set the scan policy is\n\n```java\nScanPolicy scanPolicy = mapper.getScanPolicy(Person.class);\nscanPolicy.filterExp = Exp.build(Exp.eq(Exp.stringBin(\"name\"), Exp.val(\"Bob\")));\n```\n\nand the **right** way to set an expression is\n\n```java\nScanPolicy scanPolicy = new ScanPolicy(mapper.getScanPolicy(Person.class));\nscanPolicy.filterExp = Exp.build(Exp.eq(Exp.stringBin(\"name\"), Exp.val(\"Bob\")));\n```\n\n## Queries\n\nSimilar to Scans, Queries can processed using the AeroMapper. Syntactically, the only difference between a query and a scan is the addition of a `Filter` on the Query which dictates the criteria of the query. A secondary index must be defined on the Bin referenced in the Filter or an error will be thrown. If no filter is passed, the query will be turned into a scan.\n\nSimilar to Scans, returning `false` on the processing method will abort the Query and process no further records, and additional filter criteria can be added using Expressions on the QueryPolicy.\n\n```java\nmapper.query(A.class, (a) -\u003e {\n\tSystem.out.println(a);\n\tcounter.incrementAndGet();\n\treturn true;\n}, Filter.range(\"age\", 30, 54));\n\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faerospike%2Fjava-object-mapper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faerospike%2Fjava-object-mapper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faerospike%2Fjava-object-mapper/lists"}