How to give DynamoDB update expression(if_not_exists) in TransactionaWriteRequest - java

I am new to DynamoDB and working on a dynamo project. I am trying to update the item amount in a transaction with condition if_not_exists() with TransactionWriteRequest in DynamoDB Mapper.
As per the Doc, transactionWriteRequest.updateItem() takes DynamoDBTransactionWriteExpression which doesn't have any UpdateExpression. Class definition is attached bellow.,
Wanted to know How can i provide the if_not_exists() in DynamoDBTransactionWriteExpression to update the item in a transaction. Or there is no way to do this in a transactionWrite.
Please help here.
Thanks in advance

Judging from the snippet you shared it seems you are using Java SDK v1. Below is a code snippet which has 1 PutItem and 1 UpdateItem combined in a single TransactWrite request.
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
final String ORDER_TABLE_NAME = "test1";
/*
Update Item with condition
*/
HashMap<String,AttributeValue> myPk =
new HashMap<String,AttributeValue>();
myPk.put("pk", new AttributeValue("pkValue1"));
Map<String, AttributeValue> expressionAttributeValues = new HashMap<>();
expressionAttributeValues.put(":new_status", new AttributeValue("SOLD"));
Update markItemSold = new Update()
.withTableName(ORDER_TABLE_NAME)
.withKey(myPk)
.withUpdateExpression("SET ProductStatus = if_not_exists(createdAt, :new_status)")
.withExpressionAttributeValues(expressionAttributeValues)
.withReturnValuesOnConditionCheckFailure(ReturnValuesOnConditionCheckFailure.ALL_OLD);
/*
Put Item
*/
HashMap<String, AttributeValue> orderItem = new HashMap<>();
orderItem.put("pk", new AttributeValue("pkValue2"));
orderItem.put("OrderTotal", new AttributeValue("100"));
Put createOrder = new Put()
.withTableName(ORDER_TABLE_NAME)
.withItem(orderItem)
.withReturnValuesOnConditionCheckFailure(ReturnValuesOnConditionCheckFailure.ALL_OLD);
/*
Transaction
*/
Collection<TransactWriteItem> actions = Arrays.asList(
new TransactWriteItem().withUpdate(markItemSold),
new TransactWriteItem().withPut(createOrder));
TransactWriteItemsRequest placeOrderTransaction = new TransactWriteItemsRequest()
.withTransactItems(actions)
.withReturnConsumedCapacity(ReturnConsumedCapacity.TOTAL);
try {
client.transactWriteItems(placeOrderTransaction);
System.out.println("Transaction Successful");
} catch (ResourceNotFoundException rnf) {
System.err.println("One of the table involved in the transaction is not found" + rnf.getMessage());
} catch (InternalServerErrorException ise) {
System.err.println("Internal Server Error" + ise.getMessage());
} catch (TransactionCanceledException tce) {
System.out.println("Transaction Canceled " + tce.getMessage());
} catch (AmazonServiceException e){
System.out.println(e.getMessage());
}

With the v2 version of the SDK you can do it like this
var table =
enhancedClient.table(<table name>, TableSchema.fromClass(DynamoEntity.class));
var transactWriteItemsEnhancedRequest = TransactWriteItemsEnhancedRequest
.builder()
.addUpdateItem(table,
TransactUpdateItemEnhancedRequest.builder(LoadTestEntity.class)
.item(<entity>)
.conditionExpression(Expression.builder().expression("attribute_not_exists(ID)").build())
.build())
.build();
enhancedClient.transactWriteItems(transactWriteItemsEnhancedRequest);
You might need to play around with the expression builder, I haven't tested it.

Related

Modify HashMap when CompletableFuture is finished

I have a multi-module system, where one module handles my database storage. This is the method which saves a document:
public CompletableFuture<?> runTransaction() {
return CompletableFuture.runAsync(() -> {
TransactionBody txnBody = (TransactionBody<String>) () -> {
MongoCollection<Document> collection = transaction.getDatabase().getCollection(transaction.getCollection().toString());
collection.insertOne(session, Document.parse(json));
return "Completed";
};
try {
session.withTransaction(txnBody);
} catch (MongoException ex) {
throw new UncheckedMongoException(ex);
}
});
}
the json instance is passed down in the object constructor. However, since this will be used by several modules, with each individual caching system, I'm trying to figure out how the caller can modify data structure, if this method completed without any errors.
For example
public void createClan(Transaction transaction, int id, int maxPlayers) {
MongoTransaction mongoTransaction = (MongoTransaction) transaction;
Clan clan = new Clan(id, maxPlayers);
String json = gson.toJson(clan);
TransactionExecutor executor = new MongoTransactionExecutor(mongoTransaction, json);
executor.runTransaction(); //Returns the completableFuture instance generated by the method. Modify hashmap here.
}
I've tried reading the docs, however it was a bit confusing, any help is appreciated!
As given in the comments, two options can be considered.
First option is to convert the async nature into sync nature using CompletableFuture#get. This way, the code execution is in a blocking context.
public void createClan(Transaction transaction, int id, int maxPlayers) {
MongoTransaction mongoTransaction = (MongoTransaction) transaction;
Clan clan = new Clan(id, maxPlayers);
String json = gson.toJson(clan);
TransactionExecutor executor = new MongoTransactionExecutor(mongoTransaction, json);
try {
Object obj = executor.runTransaction().get();
// HashMap update here
} catch(Exception e) {
//handle exceptions
}
}
Second option is to keep the async nature as is and chain using thenRun (there are many then options available). This way is more a non-blocking context.
public void createClan(Transaction transaction, int id, int maxPlayers) {
MongoTransaction mongoTransaction = (MongoTransaction) transaction;
final Clan clan = new Clan(id, maxPlayers);
String json = gson.toJson(clan);
TransactionExecutor executor = new MongoTransactionExecutor(mongoTransaction, json);
try {
executor.runTransaction().thenRun(() -> updateHashMap(clan));
} catch(Exception e) {
//handle exceptions
}
}

Vaadin 14 LTS Grid refresh after adding new data not working

I'm trying to refresh grid after adding an row into it, but it's not working. Here is my Event Listener on my UI Code : Edited Full code.
Grid<TransactionModel> transactionData = new Grid<>(TransactionModel.class);
try {
// transactionData.setItems(transactionServices.getTransactionTable());
List<TransactionModel> transactionList = transactionServices.getTransactionTable();
ListDataProvider<TransactionModel> transactionDataProvider = new ListDataProvider<>(transactionList);
transactionData.setDataProvider(transactionDataProvider);
transactionData.setColumns("id", "transactionTimestamp", "srcAccountId", "dstAccountId", "amount");
Grid.Column<TransactionModel> idColumn = transactionData.getColumnByKey("id");
Grid.Column<TransactionModel> srcAccountIdColumn = transactionData.getColumnByKey("srcAccountId");
Grid.Column<TransactionModel> dstAccountIdColumn = transactionData.getColumnByKey("dstAccountId");
HeaderRow filterRow2 = transactionData.appendHeaderRow();
TransactionFilterModel transactionFilterModel = new TransactionFilterModel();
transactionDataProvider.setFilter(transaction -> transactionFilterModel.find(transaction));
// Filter srcAccountId
TextField idField = new TextField();
idField.addValueChangeListener(event -> {
transactionFilterModel.setId(event.getValue());
transactionDataProvider.refreshAll();
});
idField.setValueChangeMode(ValueChangeMode.EAGER);
filterRow2.getCell(idColumn).setComponent(idField);
idField.setSizeFull();
idField.getElement().setAttribute("focus-terget", "");
// Filter srcAccountId
TextField srcAccountIdField = new TextField();
srcAccountIdField.addValueChangeListener(event -> {
transactionFilterModel.setSrcAccountId(event.getValue());
transactionDataProvider.refreshAll();
});
srcAccountIdField.setValueChangeMode(ValueChangeMode.EAGER);
filterRow2.getCell(srcAccountIdColumn).setComponent(srcAccountIdField);
srcAccountIdField.setSizeFull();
srcAccountIdField.getElement().setAttribute("focus-terget", "");
// Filter dstAccountId
TextField dstAccountIdField = new TextField();
dstAccountIdField.addValueChangeListener(event -> {
transactionFilterModel.setDstAccountId(event.getValue());
transactionDataProvider.refreshAll();
});
dstAccountIdField.setValueChangeMode(ValueChangeMode.EAGER);
filterRow2.getCell(dstAccountIdColumn).setComponent(dstAccountIdField);
dstAccountIdField.setSizeFull();
dstAccountIdField.getElement().setAttribute("focus-terget", "");
transactionData.setWidth("50%");
} catch (JsonProcessingException | EndpointException ex) {
Logger.getLogger(MainView.class.getName()).log(Level.SEVERE, null, ex);
}
// Event Listener
submitButton.addClickListener(e -> {
System.out.println("Submitted !");
AccountModel submittedModel = new AccountModel();
if (accountModelBinder.writeBeanIfValid(submittedModel)) {
try {
accountServices.registerAccount(submittedModel);
accountIdTextField.clear();
nameTextField.clear();
addressTextField.clear();
birthDateDatePicker.clear();
allowNegativeBalanceButtonGroup.clear();
} catch (EndpointException | JsonProcessingException ez) {
Logger.getLogger(MainView.class.getName()).log(Level.SEVERE, null, ez);
}
}
accountData.getDataProvider().refreshAll(); // <- REFRESH
});
And for service I'm using rest, here is the accountservice code:
public List<AccountModel> getAccountTable() throws JsonProcessingException, EndpointException {
List<AccountModel> datalog = new JsonResponseReader(restMockvaEndpoint.send(new EndpointRequestBuilder()
.method("GET")
.resource("/account")
.build()
)).getContentTable(AccountModel.class).getData();
return datalog;
}
public AccountModel registerAccount(AccountModel accountModel) throws JsonProcessingException, EndpointException{
AccountModel account = new JsonResponseReader(restMockvaEndpoint.send(new EndpointRequestBuilder()
.method("POST")
.content(Json.getWriter().writeValueAsBytes(accountModel), MediaType.APPLICATION_JSON)
.resource("/account")
.build())).getContentObject(AccountModel.class);
return account;
}
Edited : Add registerAccount.
The problem is when I click submitButton for adding new data, the grid doesn't refresh. Any ideas?
For this to work with a ListDataProvider, you would have to modify the underlying list (add/remove items).
Now that you call refreshAll(), it just reads the list you passed again, and as it still contains the same items, nothing changes. It does not know to fetch the items from your service again.
There are a few solutions that I can think of:
1. Manually add the new item to the list (it will then appear at the end of the grid):
accountList.add(submittedModel);
...
// This instructs the grid to read the list again
accountData.getDataProvider().refreshAll();
If your accountServices.registerAccount method returns the newly saved item, you might want to add that one instead.
2. Set the items again
You could fetch the items again and set a new data provider. You can just use setItems(...) then, which uses a ListDataProvider under the hood.
// Run this both when first creating the grid, and again after the new item is saved.
// This time you don't need to call refreshAll()
List<AccountModel> accountList = accountServices.getAccountTable();
accountData.setItems(accountList);
3. Use a lazy data provider
When you use a lazy data provider, for example from callbacks, then calling refreshAll() executes those callbacks again to fetch new items.
In this case you need to implement the needed service methods, and it requires a bit more work if you need sorting or filtering.
this.setDataProvider(DataProvider.fromCallbacks(
query -> myAccountService.getAccounts(query.getOffset(), query.getLimit()).stream(),
query -> myAccountService.countAccounts()
));

Trying to insert the Element into ArrayList but, instead of inserting its updating with the current value

I have a below piece of code where I fetch the json data and pass it to the another method. Data will be keep changing on daily basis. Here, I want to retain my old Data, but somehow I am unable to do it.
Code to save the value:
json = getAllHistory(settings.getRapidView(),sprint.getId(),
settings.getCredentials(),settings.getBaseUrl());
List<History> historyList = new ArrayList<>();
Double completedIssues = ClientUtil.getJsonValue(json,sprint.getId(),"completedIssues");
Double allIssues = ClientUtil.getJsonValue(json,sprint.getId(),"allIssues");
Double remainingIssues = completedIssues-allIssues;
if (remainingIssues > 0) {
History history = new History();
history.setMiliseconds(ZonedDateTime.now().toInstant().toEpochMilli());
history.setCompletedIssues(completedIssues);
history.setAllIssues(allIssues);
history.setRemainingIssues(remainingIssues);
historyList.add(history);
sprintdata.gethistory().addAll(historyList);
sprintdata.setHistory(historyList);
}
Code to make the Rest call:
public static String getAllHistory(String rapidView, Long sprintId, String base64Credentials,String baseUrl) {
try
{
String query = String.format(GET_URL_DATA, rapidView, sprintId);
query=baseUrl+query;
HttpEntity<String> entity = new HttpEntity<String>(getHeader(base64Credentials));
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> result = restTemplate.exchange(query, HttpMethod.GET, entity, String.class);
String outputJson= result.getBody();
return outputJson;
}
catch (Exception e)
{
// TODO: handle exception
return null;
}
}
Code to get the specific Json value:
public static Double getJsonValue(String json, Long sprintId, String field) {
try{
return new GsonBuilder().
create().
fromJson(json, JsonObject.class).
getAsJsonObject("contents").
getAsJsonObject(field).
get("value").
getAsDouble();
}
catch(Exception ex)
{
return null;
}
}
I can't find the error on my own, so please help me.
I apologize for my mistake.
A list of one element is created.
Then to the old history list of sprintdata: all items of the new list are added (1):
sprintdata.gethistory().addAll(historyList);
Then the old sprintdata history list is replaced with the new one of 1 element:
sprintdata.setHistory(historyList);
So the sole thing to do would be: add one element to the old history list.
sprintdata.gethistory().add(history);

DynamoDB - how to get Primary Key (which is random id) from database to make endpoind class?

I've made method that I use to edit Item from database.
This is how my method looks:
public Product editProduct(PrimaryKey primaryKey, Product content) {
UpdateItemSpec updateItemSpec = new UpdateItemSpec().withPrimaryKey(primaryKey).withValueMap(createValueMap(content));
UpdateItemOutcome itemOutcome = databaseController.getTable(PRODUCT_TABLE).updateItem(updateItemSpec);
return convertToProduct(itemOutcome);
}
private Map<String, Object> createValueMap(Product content) {
Map<String, Object> result = new HashMap<>();
result.put("name", content.getName());
result.put("calories", content.getCalories());
result.put("fat", content.getFat());
result.put("carbo", content.getCarbo());
result.put("protein", content.getProtein());
result.put("productKinds", content.getProductKinds());
result.put("author", content.getAuthor());
result.put("media", content.getMedia());
result.put("approved", content.getApproved());
return result;
}
private Product convertToProduct(UpdateItemOutcome itemOutcome) {
Product product = new Product();
product.setName(itemOutcome.getItem().get("name").toString());
product.setCalories(itemOutcome.getItem().getInt("calories"));
product.setFat(itemOutcome.getItem().getDouble("fat"));
product.setCarbo(itemOutcome.getItem().getDouble("carbo"));
product.setProtein(itemOutcome.getItem().getDouble("protein"));
product.setProductKinds(itemOutcome.getItem().getList("productKinds"));
ObjectMapper objectMapper = new ObjectMapper();
try {
Author productAuthor = objectMapper.readValue(itemOutcome.getItem().getString("author"), Author.class);
product.setAuthor(productAuthor);
} catch (IOException e) {
e.printStackTrace();
}
try {
Media productMedia = objectMapper.readValue(itemOutcome.getItem().getString("media"), Media.class);
product.setMedia(productMedia);
} catch (IOException e) {
e.printStackTrace();
}
return product;
}
Now I want to create endpoint class for this method but I have problem, I need to get primarykey as parameter (it's looks like this for example: 2567763a-d21e-4146-8d61-9d52c2561fc0) and I don't know how to do this.
At the moment my class looks like that:
public class EditProductLambda implements RequestHandler<Map<String, Object>, ApiGatewayResponse> {
private LambdaLogger logger;
#Override
public ApiGatewayResponse handleRequest(Map<String, Object> input, Context context) {
logger = context.getLogger();
logger.log(input.toString());
try{
Product product = RequestUtil.parseRequest(input, Product.class);
//PrimaryKey primaryKey = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
KitchenService kitchenService = new KitchenService(new DatabaseController(context, Regions.EU_CENTRAL_1), logger);
Product editedProduct = kitchenService.editProduct(primaryKey, product);
return ResponseUtil.generateResponse(HttpStatus.SC_CREATED, editedProduct);
} catch (IllegalArgumentException e){
return ResponseUtil.generateResponse(HttpStatus.SC_BAD_REQUEST, e.getMessage());
}
}
Can someone give me some advice how to do that? Or maybe my method is done wrong?
So first you have to create a trigger to Lambda function and ideal prefer here would be an API gateway. You can pass your data as query string or as a request body to API gateway.
You can use body mapping template in the integration request section of API gateway and get request body/query string. Construct a new json at body mapping template, which will have data from request body/query string. As we are adding body mapping template your business logic will get the json we have constructed at body mapping template.
Inside body mapping template to get query string please do ,
$input.params('querystringkey')
For example inside body mapping template (If using query string),
#set($inputRoot = $input.path('$'))
{
"primaryKey" : "$input.params('$.primaryKey')"
}
if passing data as body then,
#set($inputRoot = $input.path('$'))
{
"primaryKey" : "$input.path('$.primaryKey')"
}
Please read https://aws.amazon.com/blogs/compute/tag/mapping-templates/ for more details on body mapping template

Update model - Play 2.4.3

I have the following code in my update function.
I use the form that I pre-fill with data but when I submit the form i CANNOT update the existing object even if I set client.id manually.
Is it correct to use the commented code in order to first get the object from DB and then update every field one by one?
Form<Client> clientForm = form(Client.class).bindFromRequest();
Map<String, String> data = clientForm.data();
if (clientForm.hasErrors()) {
return badRequest(addEditClient.render("Edit Client", "Update Client", clientID, false, clientForm));
} else {
// do stuff
Client client = clientForm.get();
client.contact = User.findByID("1");
for(ClientTask ct : client.taskSet) {
Task t = Task.findByID(ct.t.id);
ct.t = t;
}
client.id = clientID;
//Client c = Client.findByID(clientID);
//c.name = "test12";
//etc...
//c.save();
client.save();
return GO_HOME;
}

Categories