Updating with Morphia Optimistic locking - java

Hi considering the following example:
Resource:
#PUT
#Path("{id}")
public Response update(#PathParam(value = "id") final String id, final Person person) {
final Person person = service.getPerson(id);
final EntityTag etag = new EntityTag(Integer.toString(person.hashCode()));
// If-Match is required
ResponseBuilder builder = request.evaluatePreconditions(etag);
if (builder != null) {
throw new DataHasChangedException("Person data has changed: " + id);
}
service.updatePerson(id, person.getName());
....
}
Service:
public void updatePerson(final String id, final String name) {
final Query<Person> findQuery = morphiaDataStore.createQuery(Person.class).filter("id ==", id);
UpdateOperations<Person> operation = morphiaDataStore.createUpdateOperations(Person.class).set("name", name);
morphiaDataStore.findAndModify(findQuery, operation );
}
Person:
#Entity("person")
public class Person {
#Id
private ObjectId id;
#Version
private Long version;
private String name;
...
}
I do check if the etag provided is the same of the person within the database. However this check is been done on the resource itself. I don't think that this is safe since the update happens after the check and another thread could have gone threw the check in the meantime. How can this be solved correctly? Any example or advise is appreciated.

Morphia already implements optimistic-locking via #Version annotation.
http://mongodb.github.io/morphia/1.3/guides/annotations/#version
#Version marks a field in an entity to control optimistic locking. If the versions change in the database while modifying an entity (including deletes) a ConcurrentModificationException will be thrown. This field will be automatically managed for you – there is no need to set a value and you should not do so. If another name beside the Java field name is desired, a name can be passed to this annotation to change the document’s field name.
I see you have already use the annotation in your example. Make sure the clients include the version of the document as part of the request so you can also pass it to morphia.
Not sure if findAndModify will be able to handle it (I would think it does). but at least I'm sure save does handle it.
Assuming the object person contains the new name and version that the client was looking at, you can do directly something like this to update the record:
morphiaDataStore.save(person);
If there was another save before this client could pick it up the versions will no longer match and a ConcurrentModificationException will be issued with this message:
Entity of class %s (id='%s',version='%d') was concurrently updated

Related

How to update only particular fields in an entity using springbooot JPA update API

Here is my code.
Entit class:
#Entity
public class Book
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long bookId;
private double bookPrice;
private String bookTitle;
private String bookAuthor;
private String bookLanguage;
private LocalDate publicationDate;
private String publisher;
private byte[] bookImage;
private long isbn;
private int bookQuantity;
Controller class:
#PutMapping("/updatebooks")
public ResponseEntity<ApiResponse> updateBook(#RequestBody BookDto bookDto)
throws DataNotFoundException
{
return ResponseEntity.ok(bookService.updateBook(bookDto));
}
Service class:
#Override
public ApiResponse updateBook(BookDto bookDto) throws DataNotFoundException
{
Book book = bookRepository.findById(bookDto.getBookId())
.orElseThrow(() -> new DataNotFoundException("Book not found"));
book.setBookAuthor(bookDto.getBookAuthor());
book.setBookLanguage(bookDto.getBookLanguage());
book.setBookPrice(bookDto.getBookPrice());
book.setBookTitle(bookDto.getBookTitle());
book.setIsbn(bookDto.getIsbn());
book.setPublicationDate(bookDto.getPublicationDate());
book.setPublisher(bookDto.getPublisher());
bookRepository.save(book);
return new ApiResponse(HttpStatus.OK.value(), "Updation successful");
}
So through postman I just want to update bookAuthor field alone and other fields has to be same as it is in the database. But when I update just one field the others are automatically assigned as null and I just want to update only one field.
Here see that i'm just updating the bookAuthor field but others are simply changing to null. So how can I update only particular fields and display the others as as it is in database.
Pre Updation DB:
Post Updation DB:
This is not really springboot jpa problem. The issue is in the way updateBook method in service has been implemented. The updateBook method is setting all the fields from dto on persisted book entity without checking if the dto.get* returns null or not.
Very simply but crude solution to this problem would be to have if(dto.get* != null) check for each field before setting the value in book entity. i.e.
if (bookDto.getBookAuthor() != null)
book.setBookAuthor(bookDto.getBookAuthor());
if (bookDto.getBookLanguage() != null)
book.setBookLanguage(bookDto.getBookLanguage());
if (bookDto.getBookPrice() != 0)
book.setBookPrice(bookDto.getBookPrice());
...
This leaves the updateBook service method generic to allow updating one or more fields without worrying about others being overwritten. If this makes your service method too noisy the dto to entity conversation part can be extracted into its own method for clarity.
For more advance usecases and if your entity/dto has more than a handful fields or nested objects as fields then this becomes cumbersome. In such scenarios you may want to handcraft a separate implementation which perhaps uses reflection to map fields from dto to entity if the field value is not null. The usage of reflection is however likely to be slow and/or error prone if not done properly.
There are however libraries and frameworks for more regular usecases which makes such conversation easier. At the simplest springframework's BeanUtils.copyProperties method provides a way to map values from one object to another and the last argument of ignored properties can be used to provide fields names to be ignored. This stackoverflow answer shows how to write a generic method that creates a list of property names to ignore if they are null in source object; and they uses this list to pass as parameter in BeanUtils.copyProperties.
public static String[] getNullPropertyNames (Object source) {
final BeanWrapper src = new BeanWrapperImpl(source);
PropertyDescriptor[] pds = src.getPropertyDescriptors();
Set<String> emptyNames = new HashSet<>();
for(PropertyDescriptor pd : pds) {
Object srcValue = src.getPropertyValue(pd.getName());
if (srcValue == null) emptyNames.add(pd.getName());
}
return emptyNames.toArray(new String[0]);
}
then in your service method;
Book book = bookRepository.findById(bookDto.getBookId())
.orElseThrow(() -> new DataNotFoundException("Book not found"));
BeanUtils.copyProperties(bookDto, book, getNullPropertyNames(bookDto));
bookRepository.save(book);
For further advanced usecases you can use frameworks such as mapstruct or modelmapper.
Don't get the fields from the DTO. Just Do findByID, set the new bookAuthor to the entity and save.
#Modifying
#Query(nativeQuery = true, value = "update book set book_author = :bookAuthor where id = :bookId")
int updateBookAuthor(#Param("bookAuthor") String bookAuthor, #Param("bookId") Long bookId);
You can do something like this in your BookRepository, then invoke from the service class like...
bookRepository.updateBookAuthor(bookAuthor, bookId)
Or modify your service class method like the following...
#Override
public ApiResponse updateBook(BookDto bookDto) throws DataNotFoundException
{
Book book = bookRepository.findById(bookDto.getBookId())
.orElseThrow(() -> new DataNotFoundException("Book not found"));
book.setBookAuthor(bookDto.getBookAuthor());
bookRepository.save(book);
return new ApiResponse(HttpStatus.OK.value(), "Updation successful");
}

Update Specific Fields with Spring Data Rest and MongoDB

I'm using Spring Data MongoDB and Spring Data Rest to create a REST API which allows GET, POST, PUT and DELETE operations on my MongoDB database and it's all working fine except for the update operations (PUT). It only works if I send the full object in the request body.
For example I have the following entity:
#Document
public class User {
#Id
private String id;
private String email;
private String lastName;
private String firstName;
private String password;
...
}
To update the lastName field, I have to send all of the user object, including the password ! which is obviously very wrong.
If I only send the field to update, all the others are set to null in my database. I even tried to add a #NotNull constraints on those fields and now the update won't even happens unless I send all of the user object's fields.
I tried searching for a solution here but I only found the following post but with no solution: How to update particular field in mongo db by using MongoRepository Interface?
Is there a way to implement this ?
Spring Data Rest uses Spring Data repositories to automatically retrieve and manipulate persistent data using Rest calls (check out https://docs.spring.io/spring-data/rest/docs/current/reference/html/#reference).
When using Spring Data MongoDB, you have the MongoOperations interface which is used as a repository for your Rest endpoints.
However MongoOperations currently does not supports specific fields updates !
PS: It will be awesome if they add this feature like #DynamicUpdate in Spring Data JPA
But this doesn't mean it can be done, here's the workaround I did when I had this issue.
Firstly let me explain what we're going to do:
We will create a controller which will override all the PUT operations so that we can implement our own update method.
Inside that update method, we will use MongoTemplate which do have the ability to update specific fields.
N.B. We don't want to re-do these steps for each model in our application, so we will retrieve which model to update dynamically. In order to do that we will create a utility class. [This is optional]
Let's start by adding the org.reflections api to our project dependency which allows us to get all the classes which have a specific annotation (#Document in our case):
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.12</version>
</dependency>
Then create a new class, called UpdateUtility and add the following methods and also replace the MODEL_PACKAGE attribute with your own package containing your entities:
public class UpdateUtility {
private static final String MODEL_PACKAGE = "com.mycompany.myproject.models";
private static boolean initialized = false;
private static HashMap<String, Class> classContext = new HashMap<>();
private static void init() {
if(!initialized) {
Reflections reflections = new Reflections(MODEL_PACKAGE);
Set<Class<?>> classes = reflections.getTypesAnnotatedWith(Document.class); // Get all the classes annotated with #Document in the specified package
for(Class<?> model : classes) {
classContext.put(model.getSimpleName().toLowerCase(), model);
}
initialized = true;
}
}
public static Class getClassFromType(String type) throws Exception{
init();
if(classContext.containsKey(type)) {
return classContext.get(type);
}
else {
throw new Exception("Type " + type + " does not exists !");
}
}
}
Using this utility class we can retreive the model class to update from it's type.
E.g: UpdateUtility.getClassFromType() will returns User.class
Now let's create our controller:
public class UpdateController {
#Autowired
private MongoTemplate mongoTemplate;
#PutMapping("/{type}/{id}")
public Object update(#RequestBody HashMap<String, Object> fields,
#PathVariable(name = "type") String type,
#PathVariable(name = "id") String id) {
try {
Class classType = UpdatorUtility.getClassFromType(type); // Get the domain class from the type in the request
Query query = new Query(Criteria.where("id").is(id)); // Update the document with the given ID
Update update = new Update();
// Iterate over the send fields and add them to the update object
Iterator iterator = fields.entrySet().iterator();
while(iterator.hasNext()) {
HashMap.Entry entry = (HashMap.Entry) iterator.next();
String key = (String) entry.getKey();
Object value = entry.getValue();
update.set(key, value);
}
mongoTemplate.updateFirst(query, update, classType); // Do the update
return mongoTemplate.findById(id, classType); // Return the updated document
} catch (Exception e) {
// Handle your exception
}
}
}
Now we're able to update the specified fields without changing the calls.
So in your case, the call would be:
PUT http://MY-DOMAIN/user/MY-USER-ID { lastName: "My new last name" }
PS: You can improve it by adding the possibility to update specific field in a nested objects...

Java/SpringBoot Web app. Insert new row with auto-incremented id column

I am trying to store a new row using a few input lines on a web app into an SQL table. My jsp has all the input rows I need. However, I need to store the new object without inputting a new Id because it's auto incremented. I'm able to call my constructor to store everything else but the id.
my code for that section so far is:
#RequestMapping(value = "/save", method = RequestMethod.POST)
public ModelAndView save
//Index connect
(#RequestParam("id") String id, #RequestParam("type") String animalType,
#RequestParam("name") String animalName, #RequestParam("age") int animalAge){
ModelAndView mv = new ModelAndView("redirect:/");
AnimalConstruct newAnimal;
newAnimal.setAnimalType(animalType);
newAnimal.setAnimalName(animalName);
newAnimal.setAnimalAge(animalAge);
animals.save(newAnimal);
mv.addObject("animalList", animals.findAll());
return mv;
So if I wanted to store "(id)11, (type)bird, (name)patty, (age)5" and I'm only making the type, name, and age inputtable, what should I do for the id? The object technically injects the id as empty I think, but then I get thrown an error. I'm very new to java and Springboot and have very weak skills in both.
The magic happens with a JPA implementation (Hibernate, for instance). Just annotate your id field like:
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
When saving the object, the id will be auto-generated and stored.
Check some similar questions: Hibernate Auto Increment ID and How to auto generate primary key ID properly with Hibernate inserting records
You should not pass the ID when you expect to create an object.
#RequestMapping(value = "/protected", method = RequestMethod.POST)
public RouteDocument doPost(#RequestBody RouteDocument route) throws ControllerException {
createNewRoute(route);
return route;
}
In the previous example, the method createNewRoute, calls the database, in my case using spring JpaTemplate to save it. The object route has an ID property that is filled by JpaTemplate.save. Consequently the doPost return object returns you the same object you passed as parameter BUT with the automatically assigned ID.
Annotate your id column in the bean with :
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private long id;
As answered by #pedrohreis above you can also use GenerationType.AUTO but only if your sole purpose is to make autoincrement id then I prefer GenerationType.IDENTITY
Also, looking forward in your project if you wanna disables batch updates on your data then you should use GenerationType.IDENTITY.
Refer : hibernate-identifiers

How can I "patch" a JPA entity?

Let's pretend a RESTful service receives a PATCH request to update one or more fields of an entity that might have tens of fields.
#Entity
public class SomeEntity {
#Id
#GeneratedValue
private Long id;
// many other fields
}
One dirty way to patch the corresponding entity is to write something like this:
SomeEntity patch = deserialize(json);
SomeEntity existing = findById(patch.getId());
if (existing != null)
{
if (patch.getField1() != null)
{
existing.setField1(patch.getField1());
}
if (patch.getField2() != null)
{
existing.setField2(patch.getField2());
}
if (patch.getField3() != null)
{
existing.setField3(patch.getField3());
}
}
But this is insane! And if I want to patch 1 to many & other associations of the entity the insanity could even become hazardous!
Is there a sane an elegant way to achieve this task?
Modify the getter's of SomeEntity and apply check, if any value is blank or null just return the corresponding entity object value.
class SomeEntity {
transient SomeEntity existing;
private String name;
public String getName(){
if((name!=null&&name.length()>0)||existing==null){
return name;
}
return existing.getName();
}
}
You can send an array containing the name of the fields you are going to patch. Then, in the server side, by reflection or any field mapping, set each field to the entity. I have already implemented that and it works, thought my best advice is this:
Don't publish an endpoint to perform a "generic" PATCH (modification), but one that performs a specific operation. For instance, if you want to modify an employee's address, publish an endpoint like:
PUT /employees/3/move
that expects a JSON with the new address {"address" : "new address"}.
Instead of reinventing the wheel by writing the logic yourself, why don't you use a mapping library like Dozer? You want to use the 'map-null' mapping property: http://dozer.sourceforge.net/documentation/exclude.html
EDIT I am not sure whether or not it would be possible to map a class onto itself. You could use an intermediary DTO, though.

Hide ID on POST in RequestBody, but return ID on created

For example I have a bean
public class Order
{
int orderID;
String name;
}
And I have a POST operation
#ApiOperation(value = "Insert a new order", response = Order.class)
#RequestMapping(value = "/addOrder", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
#ResponseBody
public Order addOrder(#Valid #RequestBody Order order)
{
//Set random id here
order.id = 'xxxxx';
Order o = orderService.insertOrder(order);
return o;
}
And in Swagger I have the following:
So my question is, how do I hide id on POST but show ID on GET?
Or should I add a description saying that even if you choose to add an ID it wont do anything and just return my random id? Just like in Kubernetes (uid)
And properties like read-only in #ApiModelProperty will solve anything?
A simple approach is to split your bean in two - one for creating a new object, and another one which extends that for data about an existing object.
e.g.
public class IncompleteOrder {
String name;
}
public class ExistingOrder extends IncompleteOrder {
int id;
}
Then have your POST method take an object of IncompleteOrder and return one of ExistingOrder. I'd also delegrate responsibility for assigning a random order id to the underlying service...
public ExistingOrder addOrder(#Valid #RequestBody IncompleteOrder order) {
ExistingOrder o = orderService.insertOrder(order);
return o;
}
The same thing could be achieved by having two completely separate classes with no inheritance relationship, which would probably be appropriate if there was a significant divergence between the information needed to create a new order from the information which is on an existing order.
An alternative is to ask what the id is actually for - why are your clients getting integer id's for anything? Ideally, if they want any information about the order they should be querying the API for the resource, and to do that they need the URI of the order rather than the integer id. So external services communicating about an order should be passing the URIs back and forth rather than ids. Perhaps you could encourage your clients to communicate with each via the URI you return in the Location header from your POST request? Then you could do away with exposing the id on your response and have a purely symmetric request / response body.

Categories