Builder pattern on RequestBody - java

I have been developing spring boot rest api service, this is one of my rest controller method:
#RequestMapping(value = "manager", method = RequestMethod.PUT, produces = "application/json;charset=UTF-8")
#ResponseBody
public String updateManager(#RequestBody Managers manager,
#RequestParam(value = "sid", required = true, defaultValue = "") String sid) throws TimeoutException, SocketTimeoutException, SocketException {
final Integer managerId = checkSession(sid);
final String result = managerController.validateManagerData(managerId, manager.getName(), manager.getSurname(), manager.getPassword());
return result;
}
Manager controller it's the validator class(for example check email on null reference)
if (name != null) {
if (!GMoikaStringUtils.isValidStringLength(50, name)) {
throw new InvalidUserInputException("Wrong name format", CLASS_NAME, "validateManagerData", params);
}
}
if (surname != null) {
if (!GMoikaStringUtils.isValidStringLength(50, surname)) {
throw new InvalidUserInputException("Wrong surname format", CLASS_NAME, "validateManagerData", params);
}
}
After validation of data i call Manager service class
public JsonObject updateManager(Integer managerId, String name, String surname, String password) {
Managers manager = managerRepository.findOne(managerId);
if (name != null) {
manager.setName(name);
}
if (surname != null) {
manager.setSurname(surname);
}
if (password != null) {
manager.setPassword(password);
}
managerRepository.save(manager);
return manager.toJson();
}
My qustion is, i pass a lot of params into methods(name,surname,email,password etc) if i change the order , i will save wrong params into db. The first decision is to pass Manager object instead of manager params.
But in this case i will have the following scenario:
public JsonObject updateManager(Integer managerId,Managers manager2) {
Managers manager = managerRepository.findOne(managerId);
if (manager2.name != null) {
manager.setName(name);
}
if (manager2.surname != null) {
manager.setSurname(surname);
}
if (manager2.password != null) {
manager.setPassword(password);
}
managerRepository.save(manager);
return manager.toJson();
}
Two managers in one method . It's look like difficul to understand which one should be saved into db
The second decision is to use Builder pattern(BP), but i don't know is it good practise to use BP with ORM and how to do it better?

First, managers entity should be manager.
Keep the singular from to represent a class which an instance represents an individual "thing".
Two managers in one method . It's look like difficul to understand
which one should be saved into db
With this too generic naming for parameters, yes :
public JsonObject updateManager(Integer managerId, Manager manager2) {
Just be more specific, it should be fine :
public JsonObject updateManager(Integer managerId, Manager managerDataToSave) {
Or if it may make more sense, introduce a custom class to be still more specific:
For example : ManagerInfoToUpdate that will contain the information to update (name,surname,email, etc...).
And use it in this way :
public JsonObject updateManager(Integer managerId, Manager ManagerInfoToUpdate) {
The second decision is to use Builder pattern(BP), but i don't know is
it good practise to use BP with ORM and how to do it better?
ORM such as Hibernate uses reflection to create entity instance. So, it requires a no-arg constructor. It defeats the purpose of the Builder pattern.
You should probably not use it for Hibernate entities.

Related

How can I shorted my method code with any way in Spring?

Hi guys it is my addAll method and I think solving this way isn't optimize version, can you offer me a simpler version by the way. Any help into this would be appreciated!
#Override
public ApiResponse addAll(HttpHeaders headers, List<NewsDto> newsDtoList) {
for (NewsDto newsDto : Objects.requireNonNull(newsDtoList)) {
News news = new News();
if (newsDto.getUserId() != null) news.setUserId(newsDto.getUserId());
if (newsDto.getTitleRu() != null) news.setTitleRu(newsDto.getTitleRu());
if (newsDto.getTextRu() != null) news.setTextRu(newsDto.getTextRu());
if (newsDto.getTitleUz() != null) news.setTitleUz(newsDto.getTitleUz());
if (newsDto.getTextUz() != null) news.setTextUz(newsDto.getTextUz());
if (newsDto.getTitleEng() != null) news.setTitleEng(newsDto.getTitleEng());
if (newsDto.getTextEng() != null) news.setTextEng(newsDto.getTextEng());
newsRepo.save(news);
}
return new ApiResponse(true, "all list saved");
}
I try mapped with mapstruct but my entity class extend some fields at another class thats why mapstruct could't see any fields in class and I try solve this way.
Refactor your code slightly with the following considerations:
Your null checks aren't useful; if the field is null on one, it will be set to null on the other anyway. For example, in the original code you have:
if (newsDto.getUserId() != null) news.setUserId(newsDto.getUserId());
but if newsDto.getUserId() returns null then using this object on the setter doesn't change anything unless a default value is already presesnt.
So you can just write
News news = new News();
news.setUserId(newsDto.getUserId());
news.setTitleRu(newsDto.getTitleRu());
news.setTextRu(newsDto.getTextRu());
news.setTitleUz(newsDto.getTitleUz());
news.setTextUz(newsDto.getTextUz());
news.setTitleEng(newsDto.getTitleEng());
news.setTextEng(newsDto.getTextEng());
Or if you need some special logic for default values, you could make a method for that too
public String defaultValue(String raw) {
if (raw == null || raw.trim().isEmpty()) {
return null; // or return an empty string or whatever you want
}
return raw;
}
Then you can use this in your mapping method
News news = new News();
news.setUserId(defaultValue(newsDto.getUserId()));
news.setTitleRu(defaultValue(newsDto.getTitleRu()));
news.setTextRu(defaultValue(newsDto.getTextRu()));
news.setTitleUz(defaultValue(newsDto.getTitleUz()));
news.setTextUz(defaultValue(newsDto.getTextUz()));
news.setTitleEng(defaultValue(newsDto.getTitleEng()));
news.setTextEng(defaultValue(newsDto.getTextEng()));
The above code should be in its own method, something like
public News toEntity(NewsDto newsDto) {
... // see above
return news;
}
Then you can test this method for the conversion of one news dto to its respective entity class.
After that, use it in the loop you had:
#Override
public ApiResponse addAll(HttpHeaders headers, List<NewsDto> newsDtoList) {
for (NewsDto newsDto : Objects.requireNonNull(newsDtoList)) {
newsRepo.save(toEntity(newsDto));
}
return new ApiResponse(true, "all list saved");
}
You could use ModelMapper. It should map your DTO to Entity as long as the variable names match.
private static final ModelMapper modelMapper = new ModelMapper();
public static News convertNewsDTOToNewsEntity(NewsDTO newsDTO) {
return modelMapper.map(newsDTO, News.class);
}

Patch udpates and Validate RequestBody

I have been studying spring boot for a few weeks.
I am building a simple api using hibernate + jpa with a mysql database.
I have a resource call TvShowReminderResponseDTO :
public class TvShowReminderResponseDTO {
// Attributes
private Integer idTvShowReminder;
private User user;
private UserTvShow userTvShow;
private TvShowDetailsResponseDTO tvShowDetailsResponseDTO;
private Boolean completed;
private Integer currentSeason;
private Integer currentEpisode;
private Integer personalRating;
// rest of the code omittedfor brevity
}
In my controller i have a basic update PATCH endpoint, that receives the id of the tv show reminder (entity) that is stored in my database and also i receive a TvShowReminderPatchDTO with the information i want to update:
PatchDTO and Controller:
public class TvShowReminderPatchDTO {
// Attributes
private Optional<Boolean> completed;
private Optional<Integer> currentSeason;
private Optional<Integer> currentEpisode;
private Optional<Integer> personalRating;
// rest of the code omittedfor brevity
}
#PatchMapping("/{idTvShowReminder}")
public void updateTvShowReminder(#RequestBody #Valid TvShowReminderPatchDTO tvShowReminderToUpdate,
#PathVariable Integer idTvShowReminder){
tvShowReminderService.updateTvShowReminder(tvShowReminderToUpdate,idTvShowReminder);
}
Also I have my service method that is in charge of searching the TvShowReminder entity by its id, and then update the information we get from the client.
public void updateTvShowReminder(TvShowReminderPatchDTO tvShowReminderToUpdate, Integer idTvShowReminder) {
Optional<TvShowReminder> tvShowReminder = getTvShowReminder(idTvShowReminder);
TvShowReminder currentTvShowReminder = tvShowReminder.get();
if(tvShowReminderToUpdate.getCompleted() != null) {
if (tvShowReminderToUpdate.getCompleted().isPresent()) {
currentTvShowReminder.setCompleted(tvShowReminderToUpdate.getCompleted().get());
} else {
currentTvShowReminder.setCompleted(null);
}
}
if(tvShowReminderToUpdate.getCurrentSeason() != null) {
if (tvShowReminderToUpdate.getCurrentSeason().isPresent()) {
currentTvShowReminder.setCurrentSeason(tvShowReminderToUpdate.getCurrentSeason().get());
} else {
currentTvShowReminder.setCurrentSeason(null);
}
}
if(tvShowReminderToUpdate.getCurrentEpisode() != null) {
if (tvShowReminderToUpdate.getCurrentEpisode().isPresent()) {
currentTvShowReminder.setCurrentEpisode(tvShowReminderToUpdate.getCurrentEpisode().get());
} else {
currentTvShowReminder.setCurrentEpisode(null);
}
}
if(tvShowReminderToUpdate.getPersonalRating() != null) {
if (tvShowReminderToUpdate.getPersonalRating().isPresent()) {
currentTvShowReminder.setPersonalRating(tvShowReminderToUpdate.getPersonalRating().get());
} else {
currentTvShowReminder.setPersonalRating(null);
}
}
tvShowReminderRepository.save(currentTvShowReminder);
}
I have a question about the #valid annotation in the controller: i thought that it will check if the object that we send from postman for example is of type TvShowReminderPatchDTO , but i can send an entire different object and the controller will start its excecution, and the TvShowReminderPatchDTO will have all its attributes in NULL.
Whats the best way to check if the request body its in fact a TvShowReminderPatchDTO ?
I want to validate if the object we get from the Request is an instance of the TvShowReminderPatchDTO, and if not, throw an Exception.
The method that is doing the PATCH is working but its very ugly, I use optional as attributes in the TvShowReminderPatchDTO , so i can distinguish if the client wants to set a NULL (send an attribute with a null value ) or if the attribute was ommited (it does not appear on the request body) so we dont need to do anything, meaning we dont update it.
Can you guys recommend a better way to do this or improve the existing code?
Add some required fields using #NotNull annotation in your dto to help Spring understand which attributes should be present in your type
Don't use Optional. There is already JsonNullable for this purpose
public class TvShowReminderPatchDTO
{
#NotNull
private JsonNullable<Boolean> completed = JsonNullable.undefined();
}
And in controller method:
if (dto.getCompleted().isPresent()) {
object.setCompleted(dto.getCompleted().get());
}
That's it, no null-checks required, just set the value

Reuse artifacts from validation

Lets say I create a validator for NewUserRequestBean called #CheckUsernameAvailable.
The validator would perform something simple like
public boolean isValid(NewUserRequestBean request, ConstraintValidationContext context) {
String userName = request.getUserName();
User existingUser = userProviderService.getUser(userName);
if (existingUser != null) {
return false;
}
}
Is there a way to reuse the existingUser object, so as to do something like
// if (existingUser != null)
else if (existingUser.getEmailAddress() == request.getUserEmailAddress()) {
sendObjectToCaller(existingUser);
// or returnObjectToCaller(existingUser);
}
In case you are using Hibernate Validator you can take a look at dynamic payload. Your implementation of validator would look like:
#Override
public boolean isValid(NewUserRequestBean value, ConstraintValidatorContext context) {
// all the code you need
// make sure that you are working with Hibernate Validator contexts before unwrapping
if ( context instanceof HibernateConstraintValidatorContext ) {
context.unwrap( HibernateConstraintValidatorContext.class )
.withDynamicPayload( existingUser );
}
return validationResult;
}
and then you should be able to access this same payload from the constraint violation if one is raised:
Set<ConstraintViolation<NewUserRequestBean>> violations = // results of validation of your NewUserRequestBean...
// just get the violation and unwrap it to HibernateConstraintViolation.
// to stay on the safe side you should apply an instanceof check here as well before unwrapping
HibernateConstraintViolation<NewUserRequestBean> violation = violations.iterator().next()
.unwrap( HibernateConstraintViolation.class );
User existingUser = violation.getDynamicPayload( User.class );
For more info you can check the javadocs of these dynamic payload methods and also please have a look at this section in documentation on dynamic payloads

Best way to passing parameter to restTemplate.getForObject

in order to write a clean and smart code, I'm wondering what can I do to improve my actual piece of code:
public JSONObject getCustomer(final String customerId) {
if (customerId == null || customerId.equals("")) {
return null;
} else {
final RestTemplate restTemplate = new RestTemplate();
final String result = restTemplate.getForObject("http://localhost:6061/customers/" + customerId,
String.class);
return new JSONObject(result);
}
}
Especially, I didn't like the way I composed the url, neither the check on customerId's value.
I'd like to have something like JPA, where I ask some information passing a parameter, just to be clear (in pseudocode):
public JSONObject getCustomer(final String customerId) {
final RestTemplate restTemplate = new RestTemplate();
final Query query = restTemplate.query("http://localhost:6061/customers/:customerId");
query.addParameter("customerId", customerId);
JSONObject result = query.getForObject();
return result;
}
Then, if customerId would be null or some white spaces or not existing, I'd like that result would be null.
Is there a way to do this with a standard library?
Thanks
First off, I would remove the else branch and refactor the condition to:
public JSONObject getCustomer(final String customerId) {
if (isNull(customerId) || customerId.trim().isEmpty()) {
return null;
}
...
}
Second, if you have a bunch of URI variables, Spring guys recommend using a Map<String, String>:
final String templateURL = "http://localhost:6061/customers/{customerId}";
final Map<String, String> variables = new HashMap<>();
variables.put("customerId", customerId);
...
template.getForObject(templateURL, String.class, variables);
Third, the method shouldn't create a RestTemplate instance on its own. I would prefer injecting the already-tuned object into an instance field:
getTemplate().getForObject(templateURL, String.class, variables);
Finally, I would name the result more meaningful:
final String customerRepresentation = ...;
Some notes:
getCustomer actually returns a JSONObject, not a Customer.
templateURL hardcoded the base URL as well as the URL to customers.
The method does a lot of work (takes too much responsibility) - argument validation, URL construction, making a request. Try to split these responsibilities between corresponding methods.
Firstly I would rather use DTO objects to hold the response data and manipulate them rather than using a String representation of the payload. So you may change it like this. Here Jackson takes care of all the serialization and deserialization of your data.
CustomerDTO customerDTO = restTemplate
.getForEntity("http://localhost:6061/customers/{customerId}", CustomerDTO.class, customerId).getBody();
You can use javax.validators such as #Min, #NotEmpty etc at your controller to check for the empty values. A sample is given below.
#RequestMapping(value = someURL, params = {"id"})
public SomeResponse doSomething(#PathVariable(value = "id") #Size(min=1) String id)
This throws a ValidationException with a relevant error message which can be customized by you. You then need to have an error handling aspect that sets the error message in ErrorDTO object and set the status code appropriately.

Spring Partial Update Object Data Binding

We are trying to implement a special partial update function in Spring 3.2. We are using Spring for the backend and have a simple Javascript frontend. I've not been able to find a straight-forward solution to our requirements, which is The update() function should take in any number of field:values and update the persistence model accordingly.
We have in-line editing for all of our fields, so that when the user edits a field and confirms, an id and the modified field get passed to the controller as json. The controller should be able to take in any number of fields from the client (1 to n) and update only those fields.
e.g., when a user with id==1 edits his displayName, the data posted to the server looks like this:
{"id":"1", "displayName":"jim"}
Currently, we have an incomplete solution in the UserController as outlined below:
#RequestMapping(value = "/{id}", method = RequestMethod.POST )
public #ResponseBody ResponseEntity<User> update(#RequestBody User updateUser) {
dbUser = userRepository.findOne(updateUser.getId());
customObjectMerger(updateUser, dbUser);
userRepository.saveAndFlush(updateUuser);
...
}
The code here works, but has some issues: The #RequestBody creates a new updateUser, fills in the id and the displayName. CustomObjectMerger merges this updateUser with the corresponding dbUser from the database, updating the only fields included in updateUser.
The problem is that Spring populates some fields in updateUser with default values and other auto-generated field values, which, upon merging, overwrites valid data that we have in dbUser. Explicitly declaring that it should ignore these fields is not an option, as we want our update to be able to set these fields as well.
I am looking into some way to have Spring automatically merge ONLY the information explicitly sent into the update() function into the dbUser (without resetting default/auto field values). Is there any simple way to do this?
Update: I've already considered the following option which does almost what I'm asking for, but not quite. The problem is that it takes update data in as #RequestParam and (AFAIK) doesn't do JSON strings:
//load the existing user into the model for injecting into the update function
#ModelAttribute("user")
public User addUser(#RequestParam(required=false) Long id){
if (id != null) return userRepository.findOne(id);
return null;
}
....
//method declaration for using #MethodAttribute to pre-populate the template object
#RequestMapping(value = "/{id}", method = RequestMethod.POST )
public #ResponseBody ResponseEntity<User> update(#ModelAttribute("user") User updateUser){
....
}
I've considered re-writing my customObjectMerger() to work more appropriately with JSON, counting and having it take into consideration only the fields coming in from HttpServletRequest. but even having to use a customObjectMerger() in the first place feels hacky when spring provides almost exactly what I am looking, minus the lacking JSON functionality. If anyone knows of how to get Spring to do this, I'd greatly appreciate it!
I've just run into this same problem. My current solution looks like this. I haven't done much testing yet, but upon initial inspection it looks to be working fairly well.
#Autowired ObjectMapper objectMapper;
#Autowired UserRepository userRepository;
#RequestMapping(value = "/{id}", method = RequestMethod.POST )
public #ResponseBody ResponseEntity<User> update(#PathVariable Long id, HttpServletRequest request) throws IOException
{
User user = userRepository.findOne(id);
User updatedUser = objectMapper.readerForUpdating(user).readValue(request.getReader());
userRepository.saveAndFlush(updatedUser);
return new ResponseEntity<>(updatedUser, HttpStatus.ACCEPTED);
}
The ObjectMapper is a bean of type org.codehaus.jackson.map.ObjectMapper.
Hope this helps someone,
Edit:
Have run into issues with child objects. If a child object receives a property to partially update it will create a fresh object, update that property, and set it. This erases all the other properties on that object. I'll update if I come across a clean solution.
We are using #ModelAttribute to achive what you want to do.
Create a method annotated with#modelattribute which loads a user based on a pathvariable throguh a repository.
create a method #Requestmapping with a param #modelattribute
The point here is that the #modelattribute method is the initializer for the model. Then spring merges the request with this model since we declare it in the #requestmapping method.
This gives you partial update functionality.
Some , or even alot? ;) would argue that this is bad practice anyway since we use our DAOs directly in the controller and do not do this merge in a dedicated service layer. But currently we did not ran into issues because of this aproach.
I build an API that merge view objects with entities before call persiste or merge or update.
It's a first version but I think It's a start.
Just use the annotation UIAttribute in your POJO`S fields then use:
MergerProcessor.merge(pojoUi, pojoDb);
It works with native Attributes and Collection.
git: https://github.com/nfrpaiva/ui-merge
Following approach could be used.
For this scenario, PATCH method would be more appropriate since the entity will be partially updated.
In controller method, take the request body as string.
Convert that String to JSONObject. Then iterate over the keys and update matching variable with the incoming data.
import org.json.JSONObject;
#RequestMapping(value = "/{id}", method = RequestMethod.PATCH )
public ResponseEntity<?> updateUserPartially(#RequestBody String rawJson, #PathVariable long id){
dbUser = userRepository.findOne(id);
JSONObject json = new JSONObject(rawJson);
Iterator<String> it = json.keySet().iterator();
while(it.hasNext()){
String key = it.next();
switch(key){
case "displayName":
dbUser.setDisplayName(json.get(key));
break;
case "....":
....
}
}
userRepository.save(dbUser);
...
}
Downside of this approach is, you have to manually validate the incoming values.
I've a customized and dirty solution employs java.lang.reflect package. My solution worked well for 3 years with no problem.
My method takes 2 arguments, objectFromRequest and objectFromDatabase both have the type Object.
The code simply does:
if(objectFromRequest.getMyValue() == null){
objectFromDatabase.setMyValue(objectFromDatabase.getMyValue); //change nothing
} else {
objectFromDatabase.setMyValue(objectFromRequest.getMyValue); //set the new value
}
A "null" value in a field from request means "don't change it!".
-1 value for a reference column which have name ending with "Id" means "Set it to null".
You can also add many custom modifications for your different scenarios.
public static void partialUpdateFields(Object objectFromRequest, Object objectFromDatabase) {
try {
Method[] methods = objectFromRequest.getClass().getDeclaredMethods();
for (Method method : methods) {
Object newValue = null;
Object oldValue = null;
Method setter = null;
Class valueClass = null;
String methodName = method.getName();
if (methodName.startsWith("get") || methodName.startsWith("is")) {
newValue = method.invoke(objectFromRequest, null);
oldValue = method.invoke(objectFromDatabase, null);
if (newValue != null) {
valueClass = newValue.getClass();
} else if (oldValue != null) {
valueClass = oldValue.getClass();
} else {
continue;
}
if (valueClass == Timestamp.class) {
valueClass = Date.class;
}
if (methodName.startsWith("get")) {
setter = objectFromRequest.getClass().getDeclaredMethod(methodName.replace("get", "set"),
valueClass);
} else {
setter = objectFromRequest.getClass().getDeclaredMethod(methodName.replace("is", "set"),
valueClass);
}
if (newValue == null) {
newValue = oldValue;
}
if (methodName.endsWith("Id")
&& (valueClass == Number.class || valueClass == Integer.class || valueClass == Long.class)
&& newValue.equals(-1)) {
setter.invoke(objectFromDatabase, new Object[] { null });
} else if (methodName.endsWith("Date") && valueClass == Date.class
&& ((Date) newValue).getTime() == 0l) {
setter.invoke(objectFromDatabase, new Object[] { null });
}
else {
setter.invoke(objectFromDatabase, newValue);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
In my DAO class, simcardToUpdate comes from http request:
simcardUpdated = (Simcard) session.get(Simcard.class, simcardToUpdate.getId());
MyUtil.partialUpdateFields(simcardToUpdate, simcardUpdated);
updatedEntities = Integer.parseInt(session.save(simcardUpdated).toString());
The main problem lies in your following code:
#RequestMapping(value = "/{id}", method = RequestMethod.POST )
public #ResponseBody ResponseEntity<User> update(#RequestBody User updateUser) {
dbUser = userRepository.findOne(updateUser.getId());
customObjectMerger(updateUser, dbUser);
userRepository.saveAndFlush(updateUuser);
...
}
In the above functions, you call some of your private functions & classes (userRepository, customObjectMerger, ...), but give no explanation how it works or how those functions look like. So I can only guess:
CustomObjectMerger merges this updateUser with the corresponding
dbUser from the database, updating the only fields included in
updateUser.
Here we don't know what happened in CustomObjectMerger (that's your function, and you don't show it). But from what you describe, I can make a guess: you copy all the properties from updateUser to your object at database. This is absolutely a wrong way, since when Spring map the object, it will fill all the data. And you only want to update some specific properties.
There are 2 options in your case:
1) Sending all the properties (including the unchanged properties) to the server. This may cost a little more bandwidth, but you still keep your way
2) You should set some special values as the default value for the User object (for example, id = -1, age = -1...). Then in customObjectMerger you just set the value that is not -1.
If you feel the 2 above solutions aren't satisfied, consider parsing the json request yourself, and don't bother with Spring object mapping mechanism. Sometimes it just confuse a lot.
Partial updates can be solved by using #SessionAttributes functionality, which are made to do what you did yourself with the customObjectMerger.
Look at my answer here, especially the edits, to get you started:
https://stackoverflow.com/a/14702971/272180
I've done this with a java Map and some reflection magic:
public static Entidade setFieldsByMap(Map<String, Object> dados, Entidade entidade) {
dados.entrySet().stream().
filter(e -> e.getValue() != null).
forEach(e -> {
try {
Method setter = entidade.getClass().
getMethod("set"+ Strings.capitalize(e.getKey()),
Class.forName(e.getValue().getClass().getTypeName()));
setter.invoke(entidade, e.getValue());
} catch (Exception ex) { // a lot of exceptions
throw new WebServiceRuntimeException("ws.reflection.error", ex);
}
});
return entidade;
}
And the entry point:
#Transactional
#PatchMapping("/{id}")
public ResponseEntity<EntityOutput> partialUpdate(#PathVariable String entity,
#PathVariable Long id, #RequestBody Map<String, Object> data) {
// ...
return new ResponseEntity<>(obj, HttpStatus.OK);
}

Categories