Skip to content

Commit 93269d4

Browse files
committed
Initial commit of v2 mapper for preview
1 parent 4d688f5 commit 93269d4

File tree

108 files changed

+17780
-3
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

108 files changed

+17780
-3
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"type": "feature",
3+
"category": "Amazon DynamoDB",
4+
"description": "Public preview version of 'dynamodb-enhanced' that has a new DynamoDb mapper library that can be used with the v2 SDK. See README.md in the module for more detailed information about this module."
5+
}

bom-internal/pom.xml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,53 @@
315315
<version>${netty-open-ssl-version}</version>
316316
<scope>test</scope>
317317
</dependency>
318+
<dependency>
319+
<groupId>com.amazonaws</groupId>
320+
<artifactId>DynamoDBLocal</artifactId>
321+
<version>${dynamodb-local.version}</version>
322+
<scope>test</scope>
323+
</dependency>
324+
<dependency>
325+
<groupId>com.almworks.sqlite4java</groupId>
326+
<artifactId>sqlite4java</artifactId>
327+
<version>${sqllite.version}</version>
328+
<scope>test</scope>
329+
</dependency>
330+
<dependency>
331+
<groupId>com.almworks.sqlite4java</groupId>
332+
<artifactId>sqlite4java-win32-x86</artifactId>
333+
<version>${sqllite.version}</version>
334+
<type>dll</type>
335+
<scope>test</scope>
336+
</dependency>
337+
<dependency>
338+
<groupId>com.almworks.sqlite4java</groupId>
339+
<artifactId>sqlite4java-win32-x64</artifactId>
340+
<version>${sqllite.version}</version>
341+
<type>dll</type>
342+
<scope>test</scope>
343+
</dependency>
344+
<dependency>
345+
<groupId>com.almworks.sqlite4java</groupId>
346+
<artifactId>libsqlite4java-osx</artifactId>
347+
<version>${sqllite.version}</version>
348+
<type>dylib</type>
349+
<scope>test</scope>
350+
</dependency>
351+
<dependency>
352+
<groupId>com.almworks.sqlite4java</groupId>
353+
<artifactId>libsqlite4java-linux-i386</artifactId>
354+
<version>${sqllite.version}</version>
355+
<type>so</type>
356+
<scope>test</scope>
357+
</dependency>
358+
<dependency>
359+
<groupId>com.almworks.sqlite4java</groupId>
360+
<artifactId>libsqlite4java-linux-amd64</artifactId>
361+
<version>${sqllite.version}</version>
362+
<type>so</type>
363+
<scope>test</scope>
364+
</dependency>
318365
</dependencies>
319366
</dependencyManagement>
320367

pom.xml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
<module>aws-sdk-java</module>
5454
<module>core</module>
5555
<module>services</module>
56+
<module>services-custom</module>
5657
<module>bom</module>
5758
<module>bom-internal</module>
5859
<module>codegen</module>
@@ -114,11 +115,14 @@
114115
<junit5.version>5.4.2</junit5.version>
115116
<hamcrest.version>1.3</hamcrest.version>
116117
<mockito.version>1.10.19</mockito.version>
118+
<mockito2.version>2.28.2</mockito2.version>
117119
<assertj.version>3.8.0</assertj.version>
118120
<guava.version>26.0-jre</guava.version>
119121
<jimfs.version>1.1</jimfs.version>
120122
<commons-lang.verson>2.3</commons-lang.verson>
121123
<netty-open-ssl-version>2.0.20.Final</netty-open-ssl-version>
124+
<dynamodb-local.version>1.11.477</dynamodb-local.version>
125+
<sqllite.version>1.0.392</sqllite.version>
122126

123127
<!-- build plugin dependencies-->
124128
<maven.surefire.version>2.21.0</maven.surefire.version>
@@ -171,7 +175,7 @@
171175
</dependencies>
172176

173177
<build>
174-
<finalName>aws-sdk-java-${project.artifactId}-${project.version}</finalName>
178+
<finalName>aws-sdk-java-${project.artifactId}-${awsjavasdk.version}</finalName>
175179
<pluginManagement>
176180
<plugins>
177181
<plugin>
@@ -667,10 +671,10 @@
667671
<use>false</use>
668672
<notree>true</notree>
669673
<nodeprecatedlist>true</nodeprecatedlist>
670-
<windowtitle>AWS SDK for Java - ${project.version}</windowtitle>
674+
<windowtitle>AWS SDK for Java - ${awsjavasdk.version}</windowtitle>
671675
<encoding>UTF-8</encoding>
672676
<docencoding>UTF-8</docencoding>
673-
<doctitle>AWS SDK for Java API Reference - ${project.version}</doctitle>
677+
<doctitle>AWS SDK for Java API Reference - ${awsjavasdk.version}</doctitle>
674678
<packagesheader>AWS SDK for Java</packagesheader>
675679
<excludePackageNames>:*.codegen:software.amazon.awssdk.services.protocol*</excludePackageNames>
676680
<groups>
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
## Overview
2+
3+
Mid-level DynamoDB mapper/abstraction for Java using the v2 AWS SDK.
4+
5+
Warning: This package is provided for preview and comment purposes only.
6+
It is not asserted to be stable or correct, and is subject to frequent
7+
breaking changes.
8+
9+
## Getting Started
10+
All the examples below use a fictional Customer class. This class is
11+
completely made up and not part of this library. Any search or key
12+
values used are also completely arbitrary.
13+
14+
### Initialization
15+
1. Create or use a java class for mapping records to and from the
16+
database table. The class does not need to conform to Java bean
17+
standards but you will need getters and setters to all the attributes
18+
you want to map. Here's an example :-
19+
```java
20+
public class Customer {
21+
private String accountId;
22+
private int subId; // you could also use Integer here
23+
private String name;
24+
private String createdDate;
25+
26+
public String getAccountId() { return this.accountId; }
27+
public void setAccountId(String accountId) { this.accountId = accountId; }
28+
29+
public int getSubId() { return this.subId; }
30+
public void setSubId(int subId) { this.subId = subId; }
31+
32+
public String getName() { return this.name; }
33+
public void setName(String name) { this.name = name; }
34+
35+
public String getCreatedDate() { return this.createdDate; }
36+
public void setCreatedDate() { this.createdDate = createdDate; }
37+
}
38+
```
39+
40+
2. Create a static TableSchema for your class. You could put this in the
41+
class itself, or somewhere else :-
42+
```java
43+
static final TableSchema<Customer> CUSTOMER_TABLE_SCHEMA =
44+
TableSchema.builder()
45+
.newItemSupplier(Customer::new) // Tells the mapper how to make new objects when reading items
46+
.attributes(
47+
string("account_id", Customer::getAccountId, Customer::setAccountId)
48+
.as(primaryPartitionKey()), // primary partition key
49+
integerNumber("sub_id", Customer::getSubId, Customer::setSubId)
50+
.as(primarySortKey()), // primary sort key
51+
string("name", Customer::getName, Customer::setName)
52+
.as(secondaryPartitionKey("customers_by_name")), // GSI partition key
53+
string("created_date", Customer::getCreatedDate, Customer::setCreatedDate)
54+
.as(secondarySortKey("customers_by_date"), secondarySortKey("customers_by_name")) // Sort key for both the LSI and the GSI
55+
)
56+
.build();
57+
```
58+
59+
3. Create a MappedDatabase object that you will use to repeatedly
60+
execute operations against all your tables :-
61+
```java
62+
MappedDatabase database = MappedDatabase.builder()
63+
.dynamoDbClient(dynamoDbClient)
64+
.build();
65+
```
66+
4. Create a MappedTable object that you will use to repeatedly execute
67+
operations against a specific table :-
68+
```java
69+
// Maps a physical table with the name 'customers_20190205' to the schema
70+
MappedTable<Customer> customerTable = database.table("customers_20190205", CUSTOMER_TABLE_SCHEMA);
71+
```
72+
73+
### Common primitive operations
74+
These all strongly map to the primitive DynamoDB operations they are
75+
named after. These examples are the most simple variants of each
76+
operation possible. These commands can be customized by using the
77+
builders provided for each command and offer most of the features
78+
available in the low-level DynamoDB SDK client.
79+
80+
```java
81+
// CreateTable
82+
customerTable.execute(CreateTable.create());
83+
84+
// GetItem
85+
Customer customer = customerTable.execute(GetItem.of(Key.of(stringValue("a123"))));
86+
87+
// UpdateItem
88+
Customer updatedCustomer = customerTable.execute(UpdateItem.of(customer));
89+
90+
// PutItem
91+
customerTable.execute(PutItem.of(customer));
92+
93+
// DeleteItem
94+
Customer deletedCustomer = customerTable.execute(DeleteItem.of(Key.of(stringValue("a123"), numberValue(456))));
95+
96+
// Query
97+
Iterable<Page<Customer>> customers = customerTable.execute(Query.of(equalTo(Key.of(stringValue("a123")))));
98+
99+
// Scan
100+
Iterable<Page<Customer>> customers = customerTable.execute(Scan.create());
101+
102+
// BatchGetItem
103+
batchResults = database.execute(BatchGetItem.of(ReadBatch.of(customerTable, GetItem.of(key1), GetItem.of(key2), GetItem.of(key3)));
104+
105+
// BatchWriteItem
106+
batchResults = database.execute(BatchWriteItem.of(WriteBatch.of(customerTable, PutItem.of(item), DeleteItem.of(key1), DeleteItem.of(key2))));
107+
108+
// TransactGetItems
109+
transactResults = mappedDatabase.execute(TransactGetItems.of(ReadTransaction.of(customerTable, GetItem.of(key1)),
110+
ReadTransaction.of(orderTable, GetItem.of(key2)));
111+
112+
// TransactWriteItems
113+
mappedDatabase.execute(TransactWriteItems.of(WriteTransaction.of(customerTable, UpdateItem.of(customer)),
114+
WriteTransaction.of(orderTable, ConditionCheck.of(orderKey, conditionExpression)));
115+
```
116+
117+
### Using secondary indices
118+
Certain operations (Query and Scan) may be executed against a secondary
119+
index. Here's an example of how to do this:
120+
```
121+
MappedIndex<Customer> customersByName = customerTable.index("customers_by_name");
122+
123+
Iterable<Page<Customer>> customersWithName = customersByName.query(equalTo(Key.of(stringValue("Smith"))));
124+
```
125+
126+
### Using extensions
127+
The mapper supports plugin extensions to provide enhanced functionality
128+
beyond the simple primitive mapped operations. Only one extension can be
129+
loaded into a MappedDatabase. Any number of extensions can be chained
130+
together in a specific order into a single extension using a
131+
ChainExtension. Extensions have two hooks, beforeWrite() and
132+
afterRead(); the former can modify a write operation before it happens,
133+
and the latter can modify the results of a read operation after it
134+
happens. Some operations such as UpdateItem perform both a write and
135+
then a read, so call both hooks.
136+
137+
#### VersionedRecordExtension
138+
139+
This extension will increment and track a record version number as
140+
records are written to the database. A condition will be added to every
141+
write that will cause the write to fail if the record version number of
142+
the actual persisted record does not match the value that the
143+
application last read. This effectively provides optimistic locking for
144+
record updates, if another process updates a record between the time the
145+
first process has read the record and is writing an update to it then
146+
that write will fail.
147+
148+
To load the extension:
149+
```java
150+
MappedDatabase database =
151+
MappedDatabase.builder()
152+
.dynamoDbClient(dynbamoDbClient)
153+
.extendWith(VersionedRecordExtension.builder().build())
154+
.build();
155+
```
156+
157+
To tell the extension which attribute to use to track the record version
158+
number tag a numeric attribute in the TableSchema with the version()
159+
AttributeTag:
160+
```java
161+
integerNumber("version", Customer::getVersion, Customer::setVersion)
162+
.as(version())
163+
```
164+
165+
## Advanced scenarios
166+
### Flat map attributes from another class
167+
If the attributes for your table record are spread across several
168+
different Java objects, either through inheritance or composition, the
169+
static TableSchema implementation gives you a method of flat mapping
170+
those attributes and rolling them up into a single schema.
171+
172+
To accomplish this using inheritance:-
173+
```java
174+
@Data
175+
public class Customer extends GenericRecord {
176+
private String name;
177+
}
178+
179+
@Data
180+
public abstract class GenericRecord {
181+
private String id;
182+
private String createdDate;
183+
}
184+
185+
private static final StaticTableSchema<GenericRecord> GENERIC_RECORD_SCHEMA =
186+
TableSchema.builder()
187+
.attributes(
188+
// The partition key will be inherited by the top level mapper
189+
string("id", GenericRecord::getId, GenericRecord::setId).as(primaryPartitionKey()),
190+
string("created_date", GenericRecord::getCreatedDate, GenericRecord::setCreatedDate))
191+
.build();
192+
193+
private static final StaticTableSchema<Customer> CUSTOMER_TABLE_SCHEMA =
194+
TableSchema.builder()
195+
.newItemSupplier(Customer::new)
196+
.attributes(
197+
string("name", Customer::getName, Customer::setName))
198+
.extend(GENERIC_RECORD_SCHEMA) // All the attributes of the GenericRecord schema are added to Customer
199+
.build();
200+
```
201+
202+
Using composition:
203+
```java
204+
@Data
205+
public class Customer{
206+
private String name;
207+
private GenericRecord recordMetadata;
208+
}
209+
210+
@Data
211+
public class GenericRecord {
212+
private String id;
213+
private String createdDate;
214+
}
215+
216+
private static final StaticTableSchema<GenericRecord> GENERIC_RECORD_SCHEMA =
217+
TableSchema.builder()
218+
.newItemSupplier(GenericRecord::new)
219+
.attributes(
220+
string("id", GenericRecord::getId, GenericRecord::setId).as(primaryPartitionKey()),
221+
string("created_date", GenericRecord::getCreatedDate, GenericRecord::setCreatedDate))
222+
.build();
223+
224+
private static final StaticTableSchema<Customer> CUSTOMER_TABLE_SCHEMA =
225+
TableSchema.builder()
226+
.newItemSupplier(Customer::new)
227+
.attributes(string("name", Customer::getName, Customer::setName))
228+
// Because we are flattening a component object, we supply a getter and setter so the
229+
// mapper knows how to access it
230+
.flatten(CUSTOMER_TABLE_SCHEMA, Customer::getRecordMetadata, Customer::setRecordMetadata)
231+
.build();
232+
```
233+
You can flatten as many different eligible classes as you like using the
234+
builder pattern. The only constraints are that attributes must not have
235+
the same name when they are being rolled together, and there must never
236+
be more than one partition key, sort key or table name.

0 commit comments

Comments
 (0)