Spring REST #RequestMapping practices - java

#RestController
#RequestMapping(value = "/players")
public class REST {
#RequestMapping(method = RequestMethod.GET)
public List<Player> getAll() {
return service.getAll();
}
#RequestMapping(method = RequestMethod.GET, params = {"team", "score"})
public List<Player> getByPlayerAndScore(#RequestParam(value = "team") String team,
#RequestParam(value = "score", required = false) int score) {
return service.getByPlayerAndScore(team, score);
}
}
Q1: I am expecting first method to work for url "/players" (worked as expected) and second method to work for url's ("/players?team=xyz", "/players?team=xyz&score=1000"). spring used method1 for "/players?team=xyz". Even i specified score as optional, unless i specify 2 params, spring is is not using 2nd method. How to solve this and what is best way of writing controller methods to handle these type of requests where user can send different sets of available params (like param1&param2, only param1, only param2 etc).
Q2: For the 2nd type of query with different sets of params, how to write database queries in DAO layer. should i write separate methods each with a different query or one method with multiple if statements(like if user sent 'team' add team to DB query, if user sent 'score' add it to DB query ...)

Your second mapping explicitly specifies that both the team and score request parameters must be present, and they aren't in your example of /players?team=xyz. The fact that you wrote required = false on the binding for the method parameter is irrelevant--the #RequestMapping explicitly says that the parameter must be present in the request. (Note that you can have a parameter but no value, as in the common Spring Security URL of /login?error. This would match params = { "error" } but would have no value.)
It might be simpler to use a regular if statement in a single controller method.
Alternately, you may want to look into Spring's Querydsl integration, which allows you to interpret query parameters as a Predicate that can be passed directly to a Spring Data finder method.
Also, look into the new composite convenience annotations such as #GetMapping, which will make your code a bit simpler to read.

Related

Mapstruct: passing one additional parameter in order to propagate it

MyOM and MyEntity objects have an id as a field
The function map() works. If I set the id parameter, then my MyEntity in map() receives this parameter.
I want to do the same thing with mapDTOs(). When it receives a value for id it must be propagated to all calls of map() during the loop.
How can I do this?
#Mapping(target = "id", expression = "java(id)")
MyEntity map(MyOM om, String id);
#Mapping(target = ??, expression = ??)
List<MyEntity> mapDTOs(List<MyOM> dtos, String id);
The additional parameter should be annotated with #Context if meant to be passed to other mapping methods. The annotation was introduced in the 1.2 version, more info can be found in MapStruct documentation.
Your method should be declared without any #Mapping annotation as:
List<MyEntity> mapDTOs(List<MyOM> dtos, #Context String id);;
Since #Context parameters are not meant to be used as source parameters, a proxy method should be added to point MapStruct to the right single object mapping method to make it work.
default MyEntity mapContext(MyOM om, #Context String id) {
return map(om, id);
}
In case there is no proxy method, MapStruct would generate a new method for mapping a single object without mapping id because the existing one doesn't contain a second parameter marked as #Context.
This solution is simple but perhaps doesn't use #Context in the right way, for a better understanding of #Context usage check Filip's answer.

Springboot/Thymeleaf - How to execute a different SQL query based on the URL?

I would like to be able to display different data based on the previous category that was selected. For example, I currently have categories, that once clicked should redirect a user to a new page that displays all the relevant information for that category. The url should look something like localhost:8080/category/321 where the ID of that category is last. I need to find a way to execute a different sql query depending on the URL/category that was selected. For example, if category 1 is selected, I would like all comments that have a category ID of 1 to be displayed using a statement like
SELECT * FROM Comments WHERE CategoryID='the category id of the category that was selected';
I have used the findAll() method elsewhere in my application to display all data, but I am unsure how to perform specific queries based on the URL. I have also looked briefly into findByID() Thanks
You can add additional methods in your repositories. For your case something like:
List<Comment> findByCategoryID(Long categoryId);
Spring will resolve the query using method name.
Or with jpql:
#Query("SELECT c FROM Comment AS c WHERE c.CategoryID = :categoryId")
List<Request> findByCategoryID(Long categoryId);
Or use findAll overload which works with Example. Java doc - here.
Example:
Comment comment = new Comment;
comment.setCategoryId(1);
List<Comment> comments = repository.findAll(Example.of(comment));
You need to make a distinction between your controller and your repository. The controller is what is responsible for everything related to HTTP and HTML, so parsing the URL, generating the HTML, etc... The repository is the class that is responsible for querying the database.
A controller would typically look like this:
#Controller
#RequestMapping("/comments")
public class CommentsController {
#GetMapping
public String listAllComments(Model model) {
// Normally, you don't go directly to the repository,
// but use a service in between, but I show it like this
// to make a bit easier to follow
List<Comment> comments = repository.findAll();
model.addAttribute("comments", comments);
return "index"; // This references the Thymeleaf template. By default in a Spring Boot appliation, this would be `src/main/resources/templates/index.html`
}
#GetMapping("/{id}"
public String singleComment(#PathVariable("id") Long id, Model model) {
// Spring will automatically populate `id` with the value of the URL
// Now you can use the id to query the database
Comment comment = repository.findById(id).orElseThrow();
model.addAttribute("comment", comment);
return "single-comment";
}
}
That second method would handle a URL of the form /comments/123.
In your example, if comments have a category, then most likely, you would use a query parameter, and not a path variable. In that case, your controller method would be:
#GetMapping
public String commentsByCategory(#QueryParameter("categoryId")Long categoryId, Model model) {
List<Comments> comments = repository.findAllByCategoryId(categoryId);
model.addAttribute("comments", comments);
return "comments-by-category";
}
Then the URL would be /comments?categoryId=123
For the repository itself, be sure to read up on query methods in the documentation: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods

Do I need a separate object for every #RequestBody in Spring Boot

So far I have done one (REST) project using Spring Boot and liked it a lot. The one thing I found to be somewhat devious was my understanding of #RequestBody.
Suppose I have the POST method below to login a user. My user entity may contain attributes other than just the username and password I'd like the post-request to have. In this case I saw no other option but to make an extra object (LoginRequest) to hold the data for the incoming data.
#RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<User> login(#RequestBody LoginRequest request) {
User p = null;
if (request != null) {
p = User.login(request.getEmail(), request.getPassword()); // validates and returns user if exists
if (p != null){
return new ResponseEntity<User>(p, HttpStatus.OK);
}
}
throw new IllegalArgumentException("Password or email incorrect");
}
Similarly, I'd like the #ResponseBody to return a minimized version of the User object where for example the password is excluded.
What are some of the standard approaches to this issue? Do I really have to make a separate object for every 'json-view'? I did some REST stuff in python before and here I would just have a Dictionary containing the request attributes. Any similar approaches?
Create a new entity if you will persist or operate on it
If you need a custom view for existing entity (add/remove fields) DTO with custom serialization can be used
There maybe a case when you don't want to create DTO because you will not reuse it anywhere, but need some quick solution for custom response you can use Map<String, Object> - the key will be used for JSON field name, and Object is for the value, for example:
#RequestMapping(method = RequestMethod.POST)
public Map<String, Object> login(#RequestParam Integer p1) {
Map<String, Object> map = new HashMap<>();
map.put("p1", p1);
map.put("somethingElse", "456");
return map;
}
JSON response:
{
"p1": p1value,
"somethingElse": "456"
}
3rd case will suite you if you're not building too complex bodies with nested objects which should be customized based on some conditions. Use 2nd option in the opposite case.
There are two approaches to this, for both Request and Response -
1. Use Same entities with extra params and populate only what you need. You can check for nulls.
2. Use separate Data Transfer Objects (DTOs) - These basically can be used when entities have different fields than the object you need to transfer.
Personally, I like the first approach to save the effort of mapping entities to DTOs and back. But, sometimes when DTOs need to be totally different, we need to use the second approach. Eg. APIs for dashboards numbers and reports.
HTH

Spring And Hibernate - generic entity updates

I have a very simple task,
I have a "User" Entity.
This user has tons of fields, for example :
firstName
age
country
.....
My goal is to expose a simple controller for update:
#RequestMapping(value = "/mywebapp/updateUser")
public void updateUser(data)
I would like clients to call my controller with updates that might include one or more fields to be updated.
What are the best practices to implement such method?
One naive solution will be to send from the client the whole entity, and in the server just override all fields, but that seems very inefficient.
another naive and bad solution might be the following:
#Transactional
#RequestMapping(value = "/mywebapp/updateUser")
public void updateUser(int userId, String[] fieldNames, String[] values) {
User user = this.userDao.findById(userId);
for (int i=0 ; i < fieldsNames.length ; i++) {
String fieldName = fieldsName[i];
switch(fieldName) {
case fieldName.equals("age") {
user.setAge(values[i]);
}
case fieldName.equals("firstName") {
user.setFirstName(values[i]);
}
....
}
}
}
Obviously these solutions aren't serious, there must be a more robust\generic way of doing that (reflection maybe).
Any ideas?
I once did this genetically using Jackson. It has a very convenient ObjectMapper.readerForUpdating(Object) method that can read values from a JsonNode/Tree onto an existing object.
The controller/service
#PATCH
#Transactional
public DomainObject partialUpdate (Long id, JsonNode data) {
DomainObject o = repository.get(id);
return objectMapper.readerForUpdating(o).readValue(data);
}
That was it. We used Jersey to expose the services as REST Web services, hence the #PATCH annotation.
As to whether this is a controller or a service: it handles raw transfer data (the JsonNode), but to work efficiently it needs to be transactional (Changes made by the reader are flushed to the database when the transaction commits. Reading the object in the same transaction allows hibernate to dynamically update only the changed fields).
If your User entity doesn't contains any security fields like login or password, you can simply use it as model attribute. In this case all fields will be updated automatically from the form inputs, those fields that are not supose to be updated, like id should be hidden fields on the form.
If you don't want to expose all your entity propeties to the presentation layer you can use pojo aka command to mapp all needed fields from user entity
BWT It is really bad practice to make your controller methods transactional. You should separate your application layers. You need to have service. This is the layer where #Transactional annotation belongs to. You do all the logic there before crud operations.

Read only fields in spring-roo or spring-web-mvc

I have what appears to be a common problem within spring-mvc. Several of my domain object have fields that are not updatable so in my view I am not binding these fields.
For competeness sake The way these are excluded from the view is by editing the spring-roo scaffolded view setting the render attribute on the parameter to false.
As spring-mvc creates a new instance of the object rather than updating the existing object these fields are null. This means however that the object fails its validation before the control reaches the controller.
A lot of my entities will have extra fields that are not updatable in the view so I'd like to be able to come up with a generic solution rather than continually doing the same work over and over again (violating DRY).
How can one allow validation to occur in a consistent manner if fields are omitted from the view?
#RequestMapping(method = RequestMethod.PUT, produces = "text/html")
public String UserController.update(#Valid User user, BindingResult bindingResult, Model uiModel, HttpServletRequest httpServletRequest) {
if (bindingResult.hasErrors()) {
populateEditForm(uiModel, user);
return "admin/users/update";
}
uiModel.asMap().clear();
user.merge();
return "redirect:/admin/users/" + encodeUrlPathSegment(user.getId().toString(), httpServletRequest);
}
Possible Solutions:
Omit #Valid annotation from the controller.
Pros
Easy to implement.
Easy to understand.
Cons
Means changing the controller method for every update on every object.
Validation is not occuring in the same place as all of the rest of the application.
No easy way to return the binding errors back to the view (need to validate the object afterwards)
Add Custom Validator for methods that need omitted fields
Example:
#InitBinder
public void initBinder(WebDataBinder binder, HttpServletRequest request) {
if (request.getMethod().equals("PUT")) {
binder.setDisallowedFields("registrationDate", "password");
Validator validator = binder.getValidator();
Validator userUpdateValidator = new UserUpdateValidator();
binder.setValidator(userUpdateValidator);
}
}
Pros
Clear flow.
Cons
Suffers wildly from DRY problems. This means that If the domain object is altered in any way I need to revalidate.
Field validation is not the same as Hibernate validation when saving.
No tangible benefits over omitting validation and manually validating.
Would consider if?
Custom validator could delegate to standard JSR-303 validator but just omit fields.
Remove JSR-303 annotations from the domain object
Not an option this means that there is no validation on an object before saving. Worse I believe it will affect the DDL that is producted for database, removing constraints from the DB itself. Only put in here for completeness sake
Lookup domain object before validation occurs
The idea of this solution is to lookup the existing domain object before updating. Copying any not null fields to the old object from the request.
Pros
- The validation can go through the normal cycle.
- The validation doesn't need to change depending on what method you are implying.
Cons
Database access before hitting the controller has a bit of a smell.
I can't see any way to implement this.
Won't work for fields that need to be omitted during other stages of the object lifecycle. For example if adding a timestamp during creation.
I would like to know how to implement either a validator that delegates to the standard JSR-303 validator or alternatively how to lookup the object before modifying it. Or if anyone has any other possible solutions?
Either of these solutions allow for the treatment to be consistent over multiple objects.
Hopefully either would allow for added annotations such as.
#RooCreateOnly which means the domain object could be annotated as such leaving all the validation definitions in the one place.
The last option can be achieved with the #ModelAttribute annotation.
Create a method that returns your domain object and add the #ModelAttribute annotation to it. Then add the same annotation to the domain object argument of the method where you want to use that object. Spring will first load the object from the ModelAttribute method then merge it with the posted data.
Example:
#ModelAttribute("foobar")
public User fetchUser() {
return loadUser();
}
#RequestMapping(method = RequestMethod.PUT, produces = "text/html")
public String update(#ModelAttribute("foobar") #Valid User user, BindingResult bindingResult, Model uiModel, HttpServletRequest httpServletRequest) {
return etc();
}
You can use the disabled property for the input tags in your jspx file containing the form for the fields that you want to mark as read-only.
Also make sure you clear the z attribute relating the field so that Roo will ignore the tag if there is any change made to the entity later on.
Cheers!
I'm posting another answer totally unrelated to my previous one.
There is another solution: wrap your domain object into special form object that only expose the fields you want to validate.
Example:
public class UserForm {
private final User user = new User();
// User has many fields, but here we only want lastName
#NotEmpty // Or whatever validation you want
public String getLastName() {
return this.user.getLastName();
}
public void setLastName(String lastName) {
this.user.setLastName(lastName);
}
public User getUser() {
return this.user;
}
}

Categories