I am working on a JPA/Jersey web app and want to know if there is a better way to update a record. Currently, I am doing:
#PUT
#Path("update/{id}")
#Produces("application/json")
#Consumes("application/x-www-form-urlencoded")
public Response createDevice(
#PathParam("id") int id,
#FormParam("name") String name,
#FormParam("type") int type
/*MultivaluedMap<String, String> formParams*/
) {
try {
Devices newDevice = entityManager.find(Devices.class, id);
if(name==null){name=newDevice.getName();}
if(type != newDevice.getType()){newDevice.setType(type);}
newDevice.setName(name);
//newDevice.setType(type);
entityManager.merge(newDevice);
return Response.status(201).entity(new ResponseObject("success")).build();
} finally {
entityManager.close();
}
}
This works, but if my Devices table had more fields, I would have to check for equality of ALL fields with the values on the original object to see if they've changed, so that my
entityManager.merge(newDevice);
will only change the values passed in.
Is there a better way to do this?
Related
I seem to be unable to store a simple object to cosmos db?
this is the database model.
public class HbModel
{
public Guid id { get; set; }
public string FormName { get; set; }
public Dictionary<string, object> Form { get; set; }
}
and this is how I store the data into the database
private static void SeedData(HbModelContext dbContext)
{
var cosmosClient = dbContext.Database.GetCosmosClient();
cosmosClient.ClientOptions.AllowBulkExecution = true;
if (dbContext.Set<HbModel>().FirstOrDefault() == null)
{
// No items could be picked hence try seeding.
var container = cosmosClient.GetContainer("hb", "hb_forms");
HbModel first = new HbModel()
{
Id = Guid.NewGuid(),//Guid.Parse(x["guid"] as string),
FormName = "asda",//x["name"] as string,
Form = new Dictionary<string, object>() //
}
string partitionKey = await GetPartitionKey(container.Database, container.Id);
var response = await container.CreateItemAsync(first, new PartitionKey(partitionKey));
}
else
{
Console.WriteLine("Already have data");
}
}
private static async Task<string> GetPartitionKey(Database database, string containerName)
{
var query = new QueryDefinition("select * from c where c.id = #id")
.WithParameter("#id", containerName);
using var iterator = database.GetContainerQueryIterator<ContainerProperties>(query);
while (iterator.HasMoreResults)
{
foreach (var container in await iterator.ReadNextAsync())
{
return container.PartitionKeyPath;
}
}
return null;
}
but when creating the item I get this error message
A host error has occurred during startup operation '3b06df1f-000c-4223-a374-ca1dc48d59d1'.
[2022-07-11T15:02:12.071Z] Microsoft.Azure.Cosmos.Client: Response status code does not indicate success: BadRequest (400); Substatus: 0; ActivityId: 24bac0ba-f1f7-411f-bc57-3f91110c4528; Reason: ();.
Value cannot be null. (Parameter 'provider')
no idea why it fails?
the data should not be formatted incorreclty?
It also fails in case there is data in the dictionary.
What is going wrong?
There are several things wrong with the attached code.
You are enabling Bulk but you are not following the Bulk pattern
cosmosClient.ClientOptions.AllowBulkExecution = true is being set, but you are not parallelizing work. If you are going to use Bulk, make sure you are following the documentation and creating lists of concurrent Tasks. Reference: https://learn.microsoft.com/azure/cosmos-db/sql/tutorial-sql-api-dotnet-bulk-import#step-6-populate-a-list-of-concurrent-tasks. Otherwise don't use Bulk.
You are blocking threads.
The call to container.CreateItemAsync(first, new PartitionKey("/__partitionKey")).Result; is a blocking call, this can lead you to deadlocks. When using async operations (such as CreateItemAsync) please use the async/await pattern. Reference: https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md#avoid-using-taskresult-and-taskwait
The PartitionKey parameter should be the value not the definition.
On the call container.CreateItemAsync(first, new PartitionKey("/__partitionKey")) the Partition Key (second parameter) should be the value. Assuming your container has a Partition Key Definition of /__partitionKey then your documents should have a __partitionKey property and you should pass the Value in this parameter of such property in the current document. Reference: https://learn.microsoft.com/azure/cosmos-db/sql/troubleshoot-bad-request#wrong-partition-key-value
Optionally, if your documents do not contain such a value, just remove the parameter from the call:
container.CreateItemAsync(first)
Be advised though that this solution will not scale, you need to design your database with Partitioning in mind: https://learn.microsoft.com/azure/cosmos-db/partitioning-overview#choose-partitionkey
Missing id
The model has Id but Cosmos DB requires id, make sure the content of the document contains id when serialized.
Good day I have created a simple Web API for my app.
I was able to successfully make a POST method without conflict, and also GET without parameters(https://www.something.com/api/something/) works too, but when I insert parameter for my GET (https://www.something.com/api/something/1) it gives me a 404 on POSTMAN. When I try PUT Method, and I don't put a parameter, it gives me a 405. And 404 when I put a parameter. Below is my code.
I'm using MongoDB for my database.
database has _id, and category as Partition key.
_id also works as id(Property name JSON)
Controller
// To get a specific record
[HttpGet("{id:length(24)}")]
public ActionResult< SomeModel > Get(string id)
{
var some = _someThing.Get(id);
if (some == null)
{
return NotFound();
}
return some;
}
// For Updating a record
[HttpPut("{id:length(24)}")]
public IActionResult Update(string id, SomeModel pModel)
{
var something = _someModel.Get(id);
if (something == null)
{
return NotFound();
}
_someModel.Update(id, pModel);
return NoContent();
}
Services
// For Finding a specific record
public SomeModel Get(string id) =>
_scores.Find< SomeModel >(scores => scores.id == id).FirstOrDefault();
// For Updating record
public void Update(string id, SomeModel newScore) =>
_scores.ReplaceOne(scores => scores.id == id, newScore);
From this Microsoft page section about route constraints:
Length: Matches a string with the specified length or within a specified range of lengths.
It looks to me like your controller is expecting an ID of 24 characters long.
Try changing it to this:
[HttpGet("{id:length(1,24)}")]
Or using minlength or maxlength instead.
My controller looks like this:
#PostMapping("/event/{id}")
public String save(#PathVariable("id") long id, #Valid Form form, BindingResult bindingResult ) {
if (!bindingResult.hasErrors()) {
//No errors
//No return
}
return "redirect:/event/{id}";
}
My #GetMapping is:
#GetMapping("/event/{id}")
public ModelAndView eventDetail(#PathVariable("id") long id) {
ModelAndView model = new ModelAndView("event/details");
Event event = eventoRepository.findById(id).get();
model.addObject("event", evento);
model.addObject("guests", event.getGuests());
model.addObject("guest",new Guest());
return model;
}
I know that "${#fields.hasErrors('*')}" is always false because redirect. (right?)
How return to this path /event/{id} without redirect?
I know that "${#fields.hasErrors('*')}" is always false because redirect. (right?)
Right. It looks like you always redirect. Because of that a method annotated with #GetMapping("/event/{id}") is called, the form most likely is reseted to fresh state and there are no more errors making expression always false.
How return to this path /event/{id} without redirect?
Simply return name of the view (template) containing the form. Most likely it's the same what's returned by method annotated with #GetMapping("/event/{id}").
You should follow an approach from this guide. Return without redirect if the form contains error, and redirect otherwise.
Edit:
You should also provide additional objects to the model. Instead of populating model in each method (Get, Post etc.) you may extract common objects to a method annotated with #ModelAttribute. According to javadoc such a method can accept similar parameters as methods annotated with #RequestMapping.
For your case something like this should work fine:
#ModelAttribute
void supplyModel(#PathVariable("id") long id, Model model) {
Event event = eventoRepository.findById(id).get();
model.addAttribute("event", evento);
model.addAttribute("guests", event.getGuests());
model.addAttribute("guest",new Guest());
}
#GetMapping("/event/{id}")
public String eventDetail(#PathVariable("id") long id) {
return "event/details";
}
#PostMapping("/event/{id}")
public String save(#PathVariable("id") long id, #Valid Form form, BindingResult bindingResult ) {
if (bindingResult.hasErrors()) {
// Has errors
return "event/details";
}
// No errors
return "redirect:/event/" + id;
}
I have a an ArrayList<Color> colorList for my list view with an ArrayAdapter. My POJO like this:
public class Color {
int id;
String name;
//getter setter
}
Everything is fetched from the server. so the id of each color object will match an id in the DB table.
In my ArrayAdapter's getView I am setting the tag with an id from the database.
holder.imageButton.setTag(item.getId()); //color id from database
holder.imageButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
int id = (Integer) v.getTag();
new DeleteColor(id).execute();
}
});
In the above code I am sending the clicked item's id to the server for deletion
What is the easy way to remove an item from my listView?
What I'm doing right now is:
class DeleteColor extends AsyncTask<String, String, String> {
int id;
public DeleteColor (int id) {
this.id = id;
}
#Override
protected String doInBackground (String ... args) {
MyManager.INSTANCE.getService().deleteColor(id, new Callback<Integer>() {
#Override
public void success(Integer id, Response response) {
//loop through the colorlist to find which one to remove
for (int i = 0; i < colorList.size(); i++) {
Color c = colorList.get(i);
if (c.getId() == id) {
colorList.remove(c);
adapter.notifyDataSetChanged();
break;
}
}
}
#Override
public void failure(RetrofitError retrofitError) {
}
});
return "";
}
}
As you can see, I am looping through the entire colorList to find out the one that has the id I want removed and then removing it. Is this a good approach to achieve this?
Question
How can I avoid looping through the entire colorList to find the one that needs to be deleted.
You have the id of the object you want to remove from the list.
Use that id to get the object, then use the ArrayList method indexOf(Object object) to find the index of the object in your list and remove it.
There technically is no way to directly access an item in a list.
In school we built a list in Java ourselves and it consisted of different entries that were connected one to another. But the first only was connected to the second and that in turn only to the third. So you couldn't even access the object on the second place, without beginning at the top. To access anything you had to iterate the list.
I just read Sound Conception's answer and I'm pretty sure that the indexOf(Object)-method itself iterates through the whole list (unless the Java developers did some magic, which actually could be. ;) I'm not a professional and haven't looked into the code of that method). But your manual looping probably is the most efficient way.
I don't think, there is a practical difference in execution time. So you might want to use Sound Conception's method to keep the code simple. It's totally up to you!
There is one another way if you set the item position as tag on your view.....
Then v.getId() will give you the position clicked and that you can directly remove from your list as arraylist.remove(positionclicked);
I have a class that looks as follows
class Person {
Long id;
String firstName;
int age;
}
and my input either looks like this:
{ "id": null, "firstName": "John", "age": 10 }
or like this:
{ "id": 123 }
The first variant represents a "new" (non-persisted) person and the second refers to a person by its database id.
If id is non-null, I would like to load the object from database during deserialization, otherwise fallback on regular parsing and deserialize it as a new object.
What I've tried: I currently have a JsonDeserializer for database-deserialization, but as I understand it, there is no way to "fall back" on regular parsing. According to this answer I should use a TypeAdapterFactory and the getDelegateAdapter. My problem with this approach is that I'm given a JsonReader (and not for instance a JsonElement) so I can't determine if the input contains a valid id without consuming input.
Any suggestions on how to solve this?
I don't know if I understand your question correctly, but if you already have a JsonDeserializer, you should have a method like this one in there:
public Person deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) { ... }
In this method you have the object context of type JsonDeserializationContext, which allows you to invoke default deserialization on a specified object.
So, you could do something like inside your custom deserializer:
//If id is null...
Person person = context.deserialize(json, Person.class);
See JsonDeserializationContext documentation.
I think I managed to figure this out with the help of the answer over here.
Here is a working type adapter factory:
new TypeAdapterFactory() {
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
final TypeAdapter<JsonElement> elementAdapter =
gson.getAdapter(JsonElement.class);
// Are we asked to parse a person?
if (!type.getType().equals(Person.class))
return null;
return new TypeAdapter<T>() {
#Override
public T read(JsonReader reader) throws IOException {
JsonElement tree = elementAdapter.read(reader);
JsonElement id = tree.getAsJsonObject().get("id");
if (id == null)
return delegate.fromJsonTree(tree);
return (T) findObj(id.getAsLong());
}
#Override
public void write(JsonWriter writer, T obj) throws IOException {
delegate.write(writer, obj);
}
};
}
}
I haven't fully tested it yet and I'll get back and revise it if needed. (Posting it now to open up for feed back on the approach.)