I have the following entities:
Area
Listing
They are both many-to-many:
An area can have many listings
A listing can have many areas
Both Area and Listing have other fields like name, domain, etc.
I'm using Spring Web RestController as a way to update the entities.
For example:
#PutMapping("/{id}")
public Area update(#PathVariable Long id, #RequestBody Area update) {
return areaRepository.save(update);
}
However, as an Area can have many thousands of Listings, it's not practical to pass them all in the update request when I just want to update the Area name and other basic fields in my web application.
For example, the update json in the http request would be:
{
"id" : 69,
"name" : "San Francisco",
"domain" : "foo",
...
}
When serialised, the area instance above will have a listings field equal to null (obviously) and then when saved, all association are remove from this Area.
I'm thinking that I could do a select-then-update set of operations and only update the values necessary but that is cumbersome - especially when there are many dozens of non-association fields.
The question would be: how can I try to keep to the above code and http request and not remove all of the existing Listing associations when saving the input area? Is this possible? I want to update the name and other basic fields but not the association fields.
You can use the BeanUtilBean(org.apache.commons.beanutils.BeanUtilsBean).
Step 1: Create custom beanutilbean class.
#Component
public class CustomBeanUtilsBean extends BeanUtilsBean {
#Override
public void copyProperty(Object dest, String name, Object value)
throws IllegalAccessException, InvocationTargetException {
if(value==null)return;
super.copyProperty(dest, name, value);
}
}
Step 2: In your controller while updating. first get the Area from database & use copyProperties method as shown in below.
#PutMapping("/{id}")
public Area update(#PathVariable Long id, #RequestBody Area update) {
Area areaDB = areaRepository.findOne(update.getId());
customBeanUtilsBean.copyProperties(areaDB,update);//it will set not null field values of update to areaDB.
return areaRepository.save(areaDB);
}
Hope this will helps you..:)
Since you are using spring-data-jpa repository you can write a new method that takes the changed values of Area object with #Modifying and #Query annotations on it.
In the #Query you specify the HQL query with update statement as in here
Related
I have defined two Spring boot REST resources
POST /customer
The above resource is for adding a customer with the below JSON as a request
{
"customerFirstName" : "John"
"customerLastName" : "Doe"
"customerAddress" : {
"addressLine1" : "22 XYZ Drive"
"addressLine1" : "22 suite"
"state" : "PY"
"country" : "Ind"
"zipCode" : "1235312"
}
}
Now I need to implement update the customer info, so the below resource is defined. The requirement is any information related to customers can be updated. So the Input JSON in case of an update is the same as in the case of add request. The only caveat the information that is not provided will not be updated. Only information that is provided will be updated.
PUT /customer/{customerId}
Question : I want to use Spring boot Bean request validation. However, the validation requirements are different for adding and updating resources, so not able to use the same Customer domain model. However, the domain model in both cases is exactly the same so this is causing a code duplication. How can I avoid that or is it correct to move the validation outside and code.
Example : In the case of adding a customer it is mandatory to provide a customer address, so one can use annotation like #NotNull. However, in the case of updating the customer, the customer address is not mandatory.
You should be able to use validation groups and keep a single model class. Each constraint has groups attribute that you can use to define a validation scheme. You can have a Create group that you'll use only in the POST request and ignore in the PATCH one:
interface Create {}
class Customer {
#NotNull(groups = Create.class)
private String firstName;
#NotNull(groups = Create.class)
private String lastName;
//....
}
And then as you are using Spring you'd want to take a look at #Validated annotation. This one allows you to define the particular groups you want to validate against:
#PostMapping("/customer")
public ResponseEntity<Customer> create(#RequestBody #Validated(Create.class) Customer customer) {
// do your thing
}
You can also check this part of the documentation to learn more about groups and their capabilities
I was trying to update a row in db by fetching the idByName, but instead it's adding a new row? Please help me out.
This is my controller code:
#PostMapping("/retailer/update")
public ModelAndView updateRetailer(#ModelAttribute("retailer") Retailer retailerDetails)
{
System.out.println(retailerDetails.toString());
System.out.println("method called");
UUID id=repository.findIdByName(retailerDetails.getBusinessName());
System.out.println(retailerDetails.getBusinessName());
System.out.println(id);
String name=retailerDetails.getBusinessName();
System.out.println(name);
//Retailer retailer=repository.findOne(rid);
if(id == null)
{
return null;
}
retailerDetails.setBusinessName(retailerDetails.getBusinessName());
retailerDetails.setCity(retailerDetails.getCity());
retailerDetails.setIsActive(retailerDetails.getIsActive());
retailerDetails.setStartDate(retailerDetails.getStartDate());
retailerDetails.setUrl(retailerDetails.getUrl());
repository.save(retailerDetails);
return new ModelAndView("welcome");
}
This is my Repository code:
public interface RetailerRepository extends JpaRepository<Retailer, UUID>{
#Query("SELECT r.id FROM Retailer r where r.businessName=:name")
UUID findIdByName(#Param("name") String name);
}
You should forget to set the "id" to the retailerDetails entity. Spring JPA will only execute the update action when the primary key is set.
Added the following line in your code.
retailer.setId(id);
repository.save(retailer);
You should simply fetch by BusinessName.
Repository method be:
Retailer findByBusinessName(#Param("name") String name);
updateRetailer method be like:
Retailer retailer = findByBusinessName(retailerDetails.getBusinessName());
if(retailer!=null){
retailer.setBusinessName(retailerDetails.getBusinessName());
retailer.setCity(retailerDetails.getCity());
retailer.setIsActive(retailerDetails.getIsActive());
retailer.setStartDate(retailerDetails.getStartDate());
retailer.setUrl(retailerDetails.getUrl());
repository.save(retailer);
}
Your controller looks great and so is your repository. There is something that you should know when it comes to updating an entity with hibernate. Hibernate will save(create new object) if the id for the given item is null during the transaction and hibernate will save(update the current instance in the database) if the id exist.
The flow for this update I presume is when the request to update is requested, you retrieve the object based on the specified entity then return to the view. One thing to take note of is to carry your id with your even in the view.
Make sure you include the id in a hidden input like
from there the id will be submitted in addition to the updated data to the post method.
Most update issues similar to this can be fixed by versioning entity with the #Version
Use the format of the Spring Data JPA to successfully query without writing any query.
#Repository
public interface RetailerRepository extends JpaRepository{
// Assuming there is an attribute businessName in the Retailer entity.
UUID findByBusinessName(String businessName);
}
OK so let's start self referencing object, something like this:
#Data
#Entity
public class FamilyNode {
#Id
#GeneratedValue
private long id;
private boolean orphan;
#ManyToOne
private FamilyNode parent;
}
And a standard repository rest resource like this:
#RepositoryRestResource(collectionResourceRel = "familynodes", path = "familynodes")
public interface FamilyNodeRepository extends CrudRepository<FamilyNode, Long> {
}
Now, let's assume some parent objects which I want to link with already exist with ID=1 and ID=2, each of which were created with a POST to /api/familynodes which looked like this:
{
"orphan": true,
}
If I attempt to create a new client (ID=3) with something like this using a POST request to /api/familynodes, it will work fine with the linked resource updating fine in the DB:
{
"orphan": false,
"parent": "/api/familynodes/1"
}
However, if I attempt to do a PUT with the following body to /api/familynodes/3, the parent property seems to silently do nothing and the database is not updated to reflect the new association:
{
"orphan": false,
"parent": "/api/familynodes/2"
}
Similarly (and this is the use case that I'm getting at), a PUT like this will only update the orphan property but will leave the parent untouched:
{
"orphan": true,
"parent": null
}
So you now have a record which claims to be an orphan, but still has a parent. Of course you could do subsequent REST requests to the resource URI directly but I'm trying to make rest operations atomic so that it's impossible for any single rest query to create invalid state. So now I'm struggling with how do that with what seems like a simple use case without getting into writing my own controller to handle it - am I missing a mechanism here within the realm of spring data rest?
This is the expected behaviour for PUT requests in Spring Data Rest 2.5.7 and above wherein a PUT request does not update the resource links, only the main attributes.
As detailed here by Oliver Gierke:
If we consider URIs for association fields in the payload to update those associations, the question comes up about what's supposed to happen if no URI is specified. With the current behavior, linked associations are simply not a part of the payload as they only reside in the _links block. We have two options in this scenario: wiping the associations that are not handed, which breaks the "PUT what you GET" approach. Only wiping the ones that are supplied using null would sort of blur the "you PUT the entire state of the resource".
You may use a PATCH instead of PUT to achieve the desired result in your case
I have started a new project in Spring Boot after using Grails for 4 years.
In Grails I have used properties field of an instance of a domain class to update the associate row in a db table. The assignment of domain.properties was usually done inside a service.
The properties field was set with data coming from a web form.
This approach allows to update a domain instance with a single line, instead of writing n assignemnt, where n is the number of the attributes defined in the domain class.
Now the question.. there is something similar in Spring?
I would like to do something similar in Spring:
update(Long radioId,Map properties) {
// get the radio to be update from the db
Radio radio = getRadio(radioId)
radio.properties = properties
save(radio)
}
I add some detail,
My controller
public ModelAndView updateRadio(Radio radio) {
radioService.update(radio);
return new ModelAndView("redirect:/superadmin/radio/"+radio.getIdentifier()+"/zoom");
}
My Service
#Service
public class RadioService {
...
public void update(Radio radio) {
assert radio.getId() != null;
radioRepository.save(radio);
}
...
}
Now if the web form does not explicity send all the fields defined in Radio I have problem since I will loose the value of the field already stored.
If I could write somthing like that
public void update(Map radioProperties,Long radioId) {
Radio radio = radioRepository.findById(radioId);
radio.properties = radioProperties // only properties present in this map will be update (in grails)
radioRepository.save(radio);
}
it would be great.
In the latter method only the properties in the map (ence in the web form) will be updated, and the other store field of the radio instance will be untouched.
Take a look at Spring Data JPA, it can load domain objects by id, bind incoming request parameters to domain objects and also automagic you some CRUD repositories.
Say you have a repository method to update a Document:
public Document UpdateDocument(Document document)
{
Document serverDocument = _db.Documents.Find(document.Id);
serverDocument.Title = document.Title;
serverDocument.Content = document.Content;
_db.SaveChanges();
return serverDocument;
}
In this case, the entity has two properties. When updating a Document, both of these properties are required in the JSON request, so a request to PUT /api/folder with a body of
{
"documentId" = "1",
"title" = "Updated Title"
}
would return an error because "content" was not provided. The reason I'm doing this is because, even for nullable properties and properties that the user doesn't update, it seems safer to force the client to specify these fields in the request to avoid overwriting unspecified fields with nulls serverside.
This has led me to the practice of always requiring every updatable property in PUT and POST requests, even if it means specifying null for those properties.
Is this cool, or is there a pattern/practice that I haven't learned about yet that might facilitate partial updates by sending only what is needed over the wire?
The best practice in API design is to use HTTP PATCH for partial updates.
In fact, use cases like yours are the very reason why IETF introduced it in the first place.
RFC 5789 defines it very precisely:
PATCH is used to apply partial modifications to a resource.
A new method is necessary to improve interoperability and prevent
errors. The PUT method is already defined to overwrite a resource
with a complete new body, and cannot be reused to do partial changes.
Otherwise, proxies and caches, and even clients and servers, may get
confused as to the result of the operation. POST is already used but
without broad interoperability (for one, there is no standard way to
discover patch format support).
Mark Nottingham has written a great article about the use of PATCH in API design - http://www.mnot.net/blog/2012/09/05/patch
In your case, that would be:
[AcceptVerbs("PATCH")]
public Document PatchDocument(Document document)
{
Document serverDocument = _db.Documents.Find(document.Id);
serverDocument.Title = document.Title;
serverDocument.Content = document.Content;
_db.SaveChanges();
return serverDocument;
}
Is this cool, or is there a pattern/practice that I haven't learned
about yet that might facilitate partial updates by sending only what
is needed over the wire?
A good practice of doing a POST or PUT is to only include values that you need for that specific request. In doing the UpdateDocument you should ask yourself what "really should be done here"? If you have a hundred fields on that object do you need to update all of them or only part of them. What "action" are you really trying to do?
Let's have an illustration for those questions, say we have a User object that has the following fields:
public class User {
public int Id {get;set;}
public string Username {get;set;}
public string RealName {get;set;}
public string Password {get;set;}
public string Bio {get;set;}
}
You then have two use cases:
Update the profile of a User
Update the password of a User
When you do each of those you will not, or it's a good idea to, have one update method that will do both. Instead of having a generic UpdateUser method you should have the following methods:
UpdateProfile
UpdatePassword
Methods that accepts fields that they just need, nothing more, nothing less.
public User UpdateProfile(int id, string username, string realname, string bio) {
}
public User UpdatePassword(int id, string password) {
}
Now comes the question:
I have a use case that a "user action" allows for an update on
multiple fields where some of the fields can have "no input" from the
user but I don't want to update that field in my model.
Suppose a user updates his/her profile and provided values for Username, RealName but not for Bio. But you do not want to set Bio as null or empty if it has a value already. Then that becomes a part of your application's business logic and that should be handled explicitly.
public User UpdateProfile(int id, string username, string realname, string bio) {
var user = db.Users.Find(id);
// perhaps a validation here (e.g. if user is not null)
user.Username = username;
user.RealName = realname;
if (!string.IsNullOrEmptyWHiteSpace(bio)) {
user.Bio = bio;
}
}