Update JSON Document attribute in DynamoDB with Java SDK - java

I have a DynamoDB table with 2 attributes - a String for an ID and a JSON document. From my research I've found there is not a way to specify JSON as the type for Table.updateItem() in the Java SDK. For my update I want to overwrite the JSON document completely rather than go in an update specific attributes. I also want to do an updateItem rather than putItem (since it did seem like I could use putItem since I am just overwriting the document every time) because I will eventually have some other top-level attributes in my table that I do not want to overwrite or update every time I need to do an update - I want to be able to come in and just update the document attribute and update it completely.
I've gotten something that almost works for my CRUD operations but am wondering if there's a better way. For example I'm doing something like this:
public <T extends BaseEntity> void updateObject(T newEntity, UUID uuid) {
try {
TypeReference<HashMap<String, Object>> typeRef =
new TypeReference<HashMap<String, Object>>() {};
HashMap<String, Object> dataMap =
mapper.readValue(mapper.writeValueAsString(newEntity), typeRef);
UpdateItemSpec updateItemSpec = new UpdateItemSpec()
.withPrimaryKey("Id", uuid.toString())
.withAttributeUpdate(new AttributeUpdate("data").put(dataMap))
.withReturnValues(ReturnValue.UPDATED_NEW);
UpdateItemOutcome outcome = table.updateItem(updateItemSpec);
} catch (Exception e) {}
}
I really wish I could just do something like this instead:
UpdateItemSpec updateItemSpec = new UpdateItemSpec()
.withPrimaryKey("Id", uuid.toString())
.withUpdateExpression("set #da = :d")
.withNameMap(new NameMap()
.with("#da", "data"))
.withValueMap(new ValueMap()
.withJSON(":d", objectAsStr)) // withJSON() method sadly doesn't exist
.withReturnValues(ReturnValue.UPDATED_NEW);
Any help or suggestions greatly appreciated. Thanks.

Today, it seems that ValueMap.withJSON(String) method exists.

Related

Search DynamoDB column for a specific string using DynamoDBMapper

I am using DynamoDBMapper to scan a a table, specifically a column named "title". I should be returned a list of blogs that contains a string passed into the scanExpression. This is code I wrote about a year and a half ago which I remember working. Maybe something has been updated since?
Thanks!
public List<BlogDetailsEntity> searchBlogs(String query) {
DynamoDBScanExpression scanExpression = new DynamoDBScanExpression();
scanExpression.addFilterCondition("title", new Condition()
.withComparisonOperator(ComparisonOperator.CONTAINS)
.withAttributeValueList(new AttributeValue().withS(query.toLowerCase())));
return dynamoDBMapper.scan(BlogDetailsEntity.class, scanExpression);
}
Your code is not best practice anymore. Java V1 and this mapper should be replaced with AWS SDK for Java V2.
To get the latest code for AWS, always refer to the new AWS Code Library here.
Code examples for DynamoDB using AWS SDKs
To filter a column using the V2 enhanced client (a replacement for DynamoDBMapper), you can use DynamoDbEnhancedClient. For example, assume you want to scan a table to get all Closed items on a column named archive.
You can use code like this.
// Get Open items from the DynamoDB table.
public List<WorkItem> getOpenItems() {
// Create a DynamoDbEnhancedClient.
DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()
.dynamoDbClient(getClient())
.build();
try{
// Create a DynamoDbTable object.
DynamoDbTable<Work> table = enhancedClient.table("Work", TableSchema.fromBean(Work.class));
AttributeValue attr = AttributeValue.builder()
.s("Open")
.build();
Map<String, AttributeValue> myMap = new HashMap<>();
myMap.put(":val1",attr);
Map<String, String> myExMap = new HashMap<>();
myExMap.put("#archive", "archive");
// Set the Expression so only Closed items are queried from the Work table.
Expression expression = Expression.builder()
.expressionValues(myMap)
.expressionNames(myExMap)
.expression("#archive = :val1")
.build();
ScanEnhancedRequest enhancedRequest = ScanEnhancedRequest.builder()
.filterExpression(expression)
.limit(15)
.build();
// Scan items.
Iterator<Work> results = table.scan(enhancedRequest).items().iterator();
WorkItem workItem ;
ArrayList<WorkItem> itemList = new ArrayList<>();
while (results.hasNext()) {
workItem = new WorkItem();
Work work = results.next();
workItem.setName(work.getName());
workItem.setGuide(work.getGuide());
workItem.setDescription(work.getDescription());
workItem.setStatus(work.getStatus());
workItem.setDate(work.getDate());
workItem.setId(work.getId());
// Push the workItem to the list.
itemList.add(workItem);
}
return itemList;
} catch (DynamoDbException e) {
System.err.println(e.getMessage());
System.exit(1);
}
return null;
}
You can find a complete AWS end to end developer tutorial that teaches you how to use this code to display Amazon DynamoDB items in a React client app. For example, this illustration shows Closed items.
This complete doc can be found in the code lib here:
Create an Amazon Relational Database Service item tracker

Handling data return types with Mybatis SQL Builder

I am relatively new to Java MyBatis. I came across SQL Builder class in MyBatis. However, I don't understand how to handle the result of the SELECT SQL query, especially if the columns are going to be different in each case while using SQL Builder. Is there an example which can help me understand how to write this?
Usually, I use Mapper XML files with ResultMap to retrieve the output of an SQL statement.
I figured out the way to get it to work. I am not sure if it is the correct way.
In the XML I made the following entry
<select id="readSignals" resultType="map">
${query}
</select>
The ${query} is passed from a QueryBuilder class and the resultType is set to "map". This cause myBatis to return a List> where each Map in the list is a row. The String contains the column name and Object contains the data.
I use the following code to convert the List> into JSON.
public static JSONObject convertToJSON(List<Map<String, Object>> queryData) {
JSONObject queryJSONOutput = new JSONObject();
JSONArray outputArray = new JSONArray();
queryData.stream().forEach(d -> {
JSONObject jsonObject = new JSONObject();
for (String key: d.keySet()) {
jsonObject.put(key, d.get(key));
}
outputArray.put(jsonObject);
});
queryJSONOutput.put("data", outputArray);
return queryJSONOutput;
}

Scan and filter on nested map attributes in DynamoDB with Java SDK

I have a JSON document stored in an attribute called doc that looks something like this:
{
doc:
{
"foo":
{
"bar": "baz"
}
}
}
I'd like to be able to do a table scan and filter/search on data.foo.bar == "baz". I'm using the Java SDK and have tried the following code but it doesn't seem to work for a sub-map of a document:
String filterExpression = "#d.#f.#b = :val";
Map<String, String> nameMap = new HashMap();
nameMap.put("#d", "doc");
nameMap.put("#f", "foo");
nameMap.put("#b", "bar");
Map valueMap = new HashMap();
valueMap.put(":val", "baz");
ItemCollection<ScanOutcome> items = table.scan(
new ScanSpec()
.withFilterExpression(filterExpression)
.withNameMap(nameMap)
.withValueMap(valueMap));
EDIT - I have found that this works:
String filterExpression = "#d.foo.bar = :val";
Where I only have a single ExpressionAttributeNames for the first attribute it works. Any thoughts why it doesn't work with 3 ExpressionAttributeNames? What if by some chance I needed 3, i.e. they were reserved words?
Any help or suggestions greatly appreciated. Thanks.

How to update a Map or a List on AWS DynamoDB document API?

The new AWS DynamoDB document API allows 2 new data types that correspond directly to the underlying JSON representation: Map (aka JSON object) and List (aka JSON array).
However, I can't find a way to update attributes of these data types without completely overwriting them. In contrast, a Number attribute can be updated by ADDing another number, so in Java you can do something like:
new AttributeUpdate("Some numeric attribute").addNumeric(17);
Similarly you can addElements to an attribute of a Set data type. (In the old API you would use AttributeAction.ADD for both purposes.)
But for a Map or a List, it seems you must update the previous value locally, then PUT it instead of that value, for example in Java:
List<String> list = item.getList("Some list attribute");
list.add("new element");
new AttributeUpdate("Some list attribute").put(list);
This is much less readable, and under some circumstances much less efficient.
So my questions are:
Is there a way to update an attribute of a Map or a List data type without overwriting the previous value? For example, to add an element to a List, or to put an element in a Map?
How would you implement it using the Java API?
Do you know of plans to support this in the future?
Please take a look at UpdateExpression in the UpdateItem API
For example given an item with a list:
{
"hashkey": {"S" : "my_key"},
"my_list" : {"L":
[{"N":"3"},{"N":"7"} ]
}
You can update the list with code like the following:
UpdateItemRequest request = new UpdateItemRequest();
request.setTableName("myTableName");
request.setKey(Collections.singletonMap("hashkey",
new AttributeValue().withS("my_key")));
request.setUpdateExpression("list_append(:prepend_value, my_list)");
request.setExpressionAttributeValues(
Collections.singletonMap(":prepend_value",
new AttributeValue().withN("1"))
);
dynamodb.updateItem(request);`
You can also append to the list by reversing the order of the arguments in the list_append expression.
An expression like: SET user.address.zipcode = :zip would address a JSON map element combined with expression attribute values {":zip" : {"N":"12345"}}
Base on DynamoDB examples, this also work (scala)
val updateItemSpec:UpdateItemSpec = new UpdateItemSpec()
.withPrimaryKey("hashkey", my_key)
.withUpdateExpression("set my_list = list_append(:prepend_value, my_list)")
.withValueMap(new ValueMap()
.withList(":prepend_value", "1"))
.withReturnValues(ReturnValue.UPDATED_NEW)
println("Updating the item...")
val outcome: UpdateItemOutcome = table.updateItem(updateItemSpec)
println("UpdateItem succeeded:\n" + outcome.getItem.toJSONPretty)
A generic function to add or update a key/value pairs. attribute updateColumn should be of type map.
Update tableName attribute name should be passed as attributeName under key:value pairs where primaryKey = primaryKeyValue
public boolean insertKeyValue(String tableName, String primaryKey, String
primaryKeyValue, String attributeName, String newKey, String newValue) {
//Configuration to connect to DynamoDB
Table table = dynamoDB.getTable(tableName);
boolean insertAppendStatus = false;
try {
//Updates when map is already exist in the table
UpdateItemSpec updateItemSpec = new UpdateItemSpec()
.withPrimaryKey(primaryKey, primaryKeyValue)
.withReturnValues(ReturnValue.ALL_NEW)
.withUpdateExpression("set #columnName." + newKey + " = :columnValue")
.withNameMap(new NameMap().with("#columnName", attributeName))
.withValueMap(new ValueMap().with(":columnValue", newValue))
.withConditionExpression("attribute_exists("+ attributeName +")");
table.updateItem(updateItemSpec);
insertAppendStatus = true;
//Add map column when it's not exist in the table
} catch (ConditionalCheckFailedException e) {
HashMap<String, String> map = new HashMap<>();
map.put(newKey, newValue);
UpdateItemSpec updateItemSpec = new UpdateItemSpec()
.withPrimaryKey(primaryKey,primaryKeyValue)
.withReturnValues(ReturnValue.ALL_NEW)
.withUpdateExpression("set #columnName = :m")
.withNameMap(new NameMap().with("#columnName", attributeName))
.withValueMap(new ValueMap().withMap(":m", map));
table.updateItem(updateItemSpec);
insertAppendStatus = true;
} catch(Exception e) {
e.printStackTrace();
}
return insertAppendStatus;
}

How to perform an updateItem operation with a JSON payload on a Dynamo DB table

The Dynamo DB document API allows put operation using a json payload
Item item = Item.fromJSON(payload);
table.putItem(item);
However I couldn't find a similar way for performing an updateItem with a Json payload.
Is there a Dynamo DB support for that?
Here is a snippet as to how you would do it in Javascript.
let updateExpression = 'SET ';
let expressionAttributeValues = {};
const keys = Object.keys(payload);
for (const key of keys) {
updateExpression += `${key}=:${key},`;
expressionAttributeValues[`:${key}`] = payload[key];
}
updateExpression=updateExpression.slice(0,-1);
let params = {
TableName : 'your_table_name',
Key: {
id: id
},
UpdateExpression : updateExpression,
ExpressionAttributeValues : expressionAttributeValues,
ReturnValues: "ALL_NEW"
}
db.update(params,(err, result)=>{});
I was struggling with this for a while in the end you have to use a map.
UpdateItemOutcome updateItemOutcome = table.updateItem(
new UpdateItemSpec()
.withPrimaryKey("id", "yourId")
.withUpdateExpression("SET document.field = :field")
.withValueMap(new ValueMap()
.withMap(":field", "map of key value pairs that will get serialized to json")));
The UpdateItemSpec currently doesn't accept Item as input -- you will have to parse the json content first (presumably via Item.fromJSON) and then use either AttributeUpdate or UpdateExpression to specify the update action on each individual field (SET, ADD etc.)
Here is the documentation to using UpdateExpression to update an item http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/JavaDocumentAPIItemCRUD.html#JavaDocumentAPIItemUpdate

Categories

Resources